Mercurial > dive4elements > framework
comparison artifact-database/src/main/java/org/dive4elements/artifactdatabase/Backend.java @ 473:d0ac790a6c89 dive4elements-move
Moved directories to org.dive4elements
author | Sascha L. Teichmann <teichmann@intevation.de> |
---|---|
date | Thu, 25 Apr 2013 10:57:18 +0200 |
parents | artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java@e79ad624f94c |
children | 415df0fc4fa1 |
comparison
equal
deleted
inserted
replaced
472:783cc1b6b615 | 473:d0ac790a6c89 |
---|---|
1 /* | |
2 * Copyright (c) 2010, 2011 by Intevation GmbH | |
3 * | |
4 * This program is free software under the LGPL (>=v2.1) | |
5 * Read the file LGPL.txt coming with the software for details | |
6 * or visit http://www.gnu.org/licenses/ if it does not exist. | |
7 */ | |
8 package de.intevation.artifactdatabase; | |
9 | |
10 import de.intevation.artifacts.Artifact; | |
11 import de.intevation.artifacts.ArtifactCollection; | |
12 import de.intevation.artifacts.ArtifactCollectionFactory; | |
13 import de.intevation.artifacts.ArtifactDatabase.ArtifactLoadedCallback; | |
14 import de.intevation.artifacts.ArtifactFactory; | |
15 import de.intevation.artifacts.ArtifactSerializer; | |
16 import de.intevation.artifacts.CollectionItem; | |
17 import de.intevation.artifacts.User; | |
18 import de.intevation.artifacts.UserFactory; | |
19 | |
20 import de.intevation.artifacts.common.utils.StringUtils; | |
21 import de.intevation.artifacts.common.utils.XMLUtils; | |
22 import de.intevation.artifacts.common.utils.LRUCache; | |
23 | |
24 import de.intevation.artifactdatabase.db.SQLExecutor; | |
25 import de.intevation.artifactdatabase.db.SQL; | |
26 | |
27 import java.sql.SQLException; | |
28 import java.sql.Timestamp; | |
29 import java.sql.Types; | |
30 | |
31 import java.util.List; | |
32 import java.util.ArrayList; | |
33 import java.util.Date; | |
34 import java.util.HashMap; | |
35 | |
36 import java.util.concurrent.CopyOnWriteArrayList; | |
37 | |
38 import org.apache.log4j.Logger; | |
39 | |
40 import org.w3c.dom.Document; | |
41 | |
42 /** | |
43 * The backend implements the low level layer used to store artifacts | |
44 * in a SQL database. | |
45 * | |
46 * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a> | |
47 */ | |
48 public class Backend | |
49 implements DatabaseCleaner.ArtifactReviver | |
50 { | |
51 private static Logger logger = Logger.getLogger(Backend.class); | |
52 | |
53 /** | |
54 * The SQL statement to create new artifact id inside the database. | |
55 */ | |
56 public String SQL_NEXT_ID; | |
57 | |
58 /** | |
59 * The SQL statement to insert an artifact into the database. | |
60 */ | |
61 public String SQL_INSERT; | |
62 | |
63 /** | |
64 * The SQL statement to update some columns of an existing | |
65 * artifact in the database. | |
66 */ | |
67 public String SQL_UPDATE; | |
68 | |
69 /** | |
70 * The SQL statement to touch the access time of an | |
71 * artifact inside the database. | |
72 */ | |
73 public String SQL_TOUCH; | |
74 | |
75 /** | |
76 * The SQL statement to load an artifact by a given | |
77 * identifier from the database. | |
78 */ | |
79 public String SQL_LOAD_BY_GID; | |
80 | |
81 /** | |
82 * The SQL statement to get the database id of an artifact | |
83 * identified by the identifier. | |
84 */ | |
85 public String SQL_GET_ID; | |
86 | |
87 /** | |
88 * The SQL statement to replace the content of an | |
89 * existing artifact inside the database. | |
90 */ | |
91 public String SQL_REPLACE; | |
92 | |
93 // USER SQL | |
94 | |
95 public String SQL_USERS_NEXT_ID; | |
96 public String SQL_USERS_INSERT; | |
97 public String SQL_USERS_SELECT_ID_BY_GID; | |
98 public String SQL_USERS_SELECT_GID; | |
99 public String SQL_USERS_SELECT_ACCOUNT; | |
100 public String SQL_USERS_DELETE_ID; | |
101 public String SQL_USERS_DELETE_COLLECTIONS; | |
102 public String SQL_USERS_SELECT_ALL; | |
103 public String SQL_USERS_COLLECTIONS; | |
104 public String SQL_USERS_COLLECTION_IDS; | |
105 public String SQL_USERS_DELETE_ALL_COLLECTIONS; | |
106 public String SQL_ARTIFACTS_IN_ONLY_COLLECTION_ONLY; | |
107 public String SQL_OUTDATE_ARTIFACTS_COLLECTION; | |
108 public String SQL_UPDATE_COLLECTION_TTL; | |
109 public String SQL_UPDATE_COLLECTION_NAME; | |
110 public String SQL_OUTDATE_ARTIFACTS_USER; | |
111 public String SQL_DELETE_USER_COLLECTION_ITEMS; | |
112 public String SQL_COLLECTIONS_NEXT_ID; | |
113 public String SQL_COLLECTIONS_INSERT; | |
114 public String SQL_COLLECTIONS_SELECT_USER; | |
115 public String SQL_COLLECTIONS_SELECT_ALL; | |
116 public String SQL_COLLECTIONS_SELECT_GID; | |
117 public String SQL_COLLECTIONS_CREATION_TIME; | |
118 public String SQL_COLLECTIONS_ID_BY_GID; | |
119 public String SQL_COLLECTIONS_OLDEST_ARTIFACT; | |
120 public String SQL_DELETE_COLLECTION_ITEMS; | |
121 public String SQL_DELETE_COLLECTION; | |
122 public String SQL_COLLECTION_CHECK_ARTIFACT; | |
123 public String SQL_COLLECTION_ITEMS_ID_NEXTVAL; | |
124 public String SQL_COLLECTION_ITEMS_INSERT; | |
125 public String SQL_COLLECTION_GET_ATTRIBUTE; | |
126 public String SQL_COLLECTION_SET_ATTRIBUTE; | |
127 public String SQL_COLLECTION_ITEM_GET_ATTRIBUTE; | |
128 public String SQL_COLLECTION_ITEM_SET_ATTRIBUTE; | |
129 public String SQL_COLLECTIONS_TOUCH_BY_GID; | |
130 public String SQL_COLLECTION_ITEM_ID_CID_AID; | |
131 public String SQL_COLLECTION_ITEM_OUTDATE_ARTIFACT; | |
132 public String SQL_COLLECTION_ITEM_DELETE; | |
133 public String SQL_COLLECTIONS_TOUCH_BY_ID; | |
134 public String SQL_COLLECTION_ITEMS_LIST_GID; | |
135 public String SQL_ALL_ARTIFACTS; | |
136 | |
137 /** The singleton.*/ | |
138 protected static Backend instance; | |
139 | |
140 protected SQLExecutor sqlExecutor; | |
141 | |
142 protected List<BackendListener> listeners; | |
143 | |
144 protected DBConfig config; | |
145 | |
146 /** | |
147 * The database cleaner. Reference is stored here because | |
148 * the cleaner is woken up if the backend finds an outdated | |
149 * artifact. This artifact should be removed as soon as | |
150 * possible. | |
151 */ | |
152 protected DatabaseCleaner cleaner; | |
153 | |
154 /** | |
155 * To revive an artifact from the bytes coming from the database | |
156 * we need the artifact factory which references the artifact | |
157 * serializer which is able to do the reviving job. | |
158 */ | |
159 protected FactoryLookup factoryLookup; | |
160 | |
161 /** | |
162 * Little helper interface to decouple the ArtifactDatabase | |
163 * from the Backend. A ArtifactDatabase should depend on a | |
164 * Backend but a Backend not from an ArtifactDatabase. | |
165 */ | |
166 public interface FactoryLookup { | |
167 | |
168 /** | |
169 * Returns an ArtifactFactory which is bound to a given name. | |
170 * @param factoryName The name of the artifact factory. | |
171 * @return The ArtifactFactory bound to the factory name or | |
172 * null if not matching factory is found. | |
173 */ | |
174 ArtifactFactory getArtifactFactory(String factoryName); | |
175 | |
176 } // interface FactoryLookup | |
177 | |
178 /** | |
179 * Inner class that brigdes between the persisten form of the | |
180 * artifact and the living one inside the artifact database. | |
181 * After the describe(), feed(), advance() and out() operations | |
182 * of the artifact it must be possible to write to modified artifact | |
183 * back into the database. | |
184 */ | |
185 public final class PersistentArtifact | |
186 { | |
187 private int id; | |
188 private Artifact artifact; | |
189 private ArtifactSerializer serializer; | |
190 private Long ttl; | |
191 | |
192 /** | |
193 * Cronstructor to create a persistent artifact. | |
194 * @param artifact The living artifact. | |
195 * @param serializer The serializer to store the artifact | |
196 * after the operations. | |
197 * @param ttl The time to life of the artifact. | |
198 * @param id The database id of the artifact. | |
199 */ | |
200 public PersistentArtifact( | |
201 Artifact artifact, | |
202 ArtifactSerializer serializer, | |
203 Long ttl, | |
204 int id | |
205 ) { | |
206 this.id = id; | |
207 this.artifact = artifact; | |
208 this.serializer = serializer; | |
209 this.ttl = ttl; | |
210 } | |
211 | |
212 public int getId() { | |
213 return id; | |
214 } | |
215 | |
216 /** | |
217 * Returns the wrapped living artifact. | |
218 * @return the living artifact. | |
219 */ | |
220 public Artifact getArtifact() { | |
221 return artifact; | |
222 } | |
223 | |
224 /** | |
225 * Returns the serialized which is able to write a | |
226 * modified artifact back into the database. | |
227 * @return The serializer. | |
228 */ | |
229 public ArtifactSerializer getSerializer() { | |
230 return serializer; | |
231 } | |
232 | |
233 /** | |
234 * The time to life of the artifact. | |
235 * @return The time to live. | |
236 */ | |
237 public Long getTTL() { | |
238 return ttl; | |
239 } | |
240 | |
241 /** | |
242 * Stores the living artifact back into the database. | |
243 */ | |
244 public void store() { | |
245 if (logger.isDebugEnabled()) { | |
246 logger.debug("storing artifact id = " + getId()); | |
247 } | |
248 Backend.this.store(this); | |
249 } | |
250 | |
251 /** | |
252 * Only touches the access time of the artifact. | |
253 */ | |
254 public void touch() { | |
255 if (logger.isDebugEnabled()) { | |
256 logger.debug("touching artifact id = " + getId()); | |
257 } | |
258 Backend.this.touch(this); | |
259 } | |
260 } // class ArtifactWithId | |
261 | |
262 /** | |
263 * Default constructor | |
264 */ | |
265 public Backend() { | |
266 listeners = new CopyOnWriteArrayList<BackendListener>(); | |
267 } | |
268 | |
269 public Backend(DBConfig config) { | |
270 this(); | |
271 this.config = config; | |
272 sqlExecutor = new SQLExecutor(config.getDBConnection()); | |
273 setupSQL(config.getSQL()); | |
274 } | |
275 | |
276 /** | |
277 * Constructor to create a backend with a link to the database cleaner. | |
278 * @param cleaner The clean which periodically removes outdated | |
279 * artifacts from the database. | |
280 */ | |
281 public Backend(DBConfig config, DatabaseCleaner cleaner) { | |
282 this(config); | |
283 this.cleaner = cleaner; | |
284 } | |
285 | |
286 public DBConfig getConfig() { | |
287 return config; | |
288 } | |
289 | |
290 /** | |
291 * Returns the singleton of this Backend. | |
292 * | |
293 * @return the backend. | |
294 */ | |
295 public static synchronized Backend getInstance() { | |
296 if (instance == null) { | |
297 instance = new Backend(DBConfig.getInstance()); | |
298 } | |
299 | |
300 return instance; | |
301 } | |
302 | |
303 protected void setupSQL(SQL sql) { | |
304 SQL_NEXT_ID = sql.get("artifacts.id.nextval"); | |
305 SQL_INSERT = sql.get("artifacts.insert"); | |
306 SQL_UPDATE = sql.get("artifacts.update"); | |
307 SQL_TOUCH = sql.get("artifacts.touch"); | |
308 SQL_LOAD_BY_GID = sql.get("artifacts.select.gid"); | |
309 SQL_GET_ID = sql.get("artifacts.get.id"); | |
310 SQL_REPLACE = sql.get("artifacts.replace"); | |
311 SQL_USERS_NEXT_ID = sql.get("users.id.nextval"); | |
312 SQL_USERS_INSERT = sql.get("users.insert"); | |
313 SQL_USERS_SELECT_ID_BY_GID = sql.get("users.select.id.by.gid"); | |
314 SQL_USERS_SELECT_GID = sql.get("users.select.gid"); | |
315 SQL_USERS_SELECT_ACCOUNT = sql.get("users.select.account"); | |
316 SQL_USERS_DELETE_ID = sql.get("users.delete.id"); | |
317 SQL_USERS_DELETE_COLLECTIONS = sql.get("users.delete.collections"); | |
318 SQL_USERS_SELECT_ALL = sql.get("users.select.all"); | |
319 SQL_USERS_COLLECTIONS = sql.get("users.collections"); | |
320 SQL_USERS_COLLECTION_IDS = sql.get("users.collection.ids"); | |
321 SQL_USERS_DELETE_ALL_COLLECTIONS = | |
322 sql.get("users.delete.collections"); | |
323 SQL_ARTIFACTS_IN_ONLY_COLLECTION_ONLY = | |
324 sql.get("artifacts.in.one.collection.only"); | |
325 SQL_OUTDATE_ARTIFACTS_COLLECTION = | |
326 sql.get("outdate.artifacts.collection"); | |
327 SQL_UPDATE_COLLECTION_TTL = sql.get("collections.update.ttl"); | |
328 SQL_UPDATE_COLLECTION_NAME = sql.get("collections.update.name"); | |
329 SQL_OUTDATE_ARTIFACTS_USER = sql.get("outdate.artifacts.user"); | |
330 SQL_DELETE_USER_COLLECTION_ITEMS = | |
331 sql.get("delete.user.collection.items"); | |
332 SQL_COLLECTIONS_NEXT_ID = sql.get("collections.id.nextval"); | |
333 SQL_COLLECTIONS_INSERT = sql.get("collections.insert"); | |
334 SQL_COLLECTIONS_SELECT_USER = sql.get("collections.select.user"); | |
335 SQL_COLLECTIONS_SELECT_ALL = sql.get("collections.select.all"); | |
336 SQL_COLLECTIONS_SELECT_GID = sql.get("collections.select.by.gid"); | |
337 SQL_COLLECTIONS_CREATION_TIME = sql.get("collection.creation.time"); | |
338 SQL_COLLECTIONS_OLDEST_ARTIFACT = sql.get("collections.artifacts.oldest"); | |
339 SQL_COLLECTIONS_ID_BY_GID = sql.get("collections.id.by.gid"); | |
340 SQL_DELETE_COLLECTION_ITEMS = sql.get("delete.collection.items"); | |
341 SQL_DELETE_COLLECTION = sql.get("delete.collection"); | |
342 SQL_COLLECTION_CHECK_ARTIFACT = sql.get("collection.check.artifact"); | |
343 SQL_COLLECTION_ITEMS_ID_NEXTVAL = | |
344 sql.get("collection.items.id.nextval"); | |
345 SQL_COLLECTION_ITEMS_INSERT = sql.get("collection.items.insert"); | |
346 SQL_COLLECTION_GET_ATTRIBUTE = sql.get("collection.get.attribute"); | |
347 SQL_COLLECTION_SET_ATTRIBUTE = sql.get("collection.set.attribute"); | |
348 SQL_COLLECTION_ITEM_GET_ATTRIBUTE = | |
349 sql.get("collection.item.get.attribute"); | |
350 SQL_COLLECTION_ITEM_SET_ATTRIBUTE = | |
351 sql.get("collection.item.set.attribute"); | |
352 SQL_COLLECTIONS_TOUCH_BY_GID = sql.get("collections.touch.by.gid"); | |
353 SQL_COLLECTION_ITEM_ID_CID_AID = sql.get("collection.item.id.cid.aid"); | |
354 SQL_COLLECTION_ITEM_OUTDATE_ARTIFACT = | |
355 sql.get("collection.item.outdate.artifact"); | |
356 SQL_COLLECTION_ITEM_DELETE = sql.get("collection.item.delete"); | |
357 SQL_COLLECTIONS_TOUCH_BY_ID = sql.get("collections.touch.by.id"); | |
358 SQL_COLLECTION_ITEMS_LIST_GID = sql.get("collection.items.list.gid"); | |
359 SQL_ALL_ARTIFACTS = sql.get("all.artifacts"); | |
360 } | |
361 | |
362 public void addListener(BackendListener listener) { | |
363 listeners.add(listener); | |
364 logger.debug("# listeners: " + listeners.size()); | |
365 } | |
366 | |
367 public void addAllListeners(List<BackendListener> others) { | |
368 listeners.addAll(others); | |
369 logger.debug("# listeners: " + listeners.size()); | |
370 } | |
371 | |
372 /** | |
373 * Sets the factory lookup mechanism to decouple ArtifactDatabase | |
374 * and Backend. | |
375 * @param factoryLookup | |
376 */ | |
377 public void setFactoryLookup(FactoryLookup factoryLookup) { | |
378 this.factoryLookup = factoryLookup; | |
379 } | |
380 | |
381 /** | |
382 * Sets the database cleaner explicitly. | |
383 * @param cleaner The database cleaner | |
384 */ | |
385 public void setCleaner(DatabaseCleaner cleaner) { | |
386 this.cleaner = cleaner; | |
387 } | |
388 | |
389 /** | |
390 * Returns a new unique identifier to external identify | |
391 * the artifact across the system. This implementation | |
392 * uses random UUIDs v4 to achieve this target. | |
393 * @return the new identifier | |
394 */ | |
395 public String newIdentifier() { | |
396 // TODO: check database for collisions. | |
397 return StringUtils.newUUID(); | |
398 } | |
399 | |
400 public boolean isValidIdentifier(String identifier) { | |
401 return StringUtils.checkUUID(identifier); | |
402 } | |
403 | |
404 /** | |
405 * Stores a new artifact into the database. | |
406 * @param artifact The artifact to be stored | |
407 * @param factory The factory which build the artifact | |
408 * @param ttl The initial time to life of the artifact. | |
409 * @return A persistent wrapper around the living | |
410 * artifact to be able to write modification later. | |
411 * @throws Exception Thrown if something went wrong with the | |
412 * storage process. | |
413 */ | |
414 public PersistentArtifact storeInitially( | |
415 Artifact artifact, | |
416 ArtifactFactory factory, | |
417 Long ttl | |
418 ) | |
419 throws Exception | |
420 { | |
421 return new PersistentArtifact( | |
422 artifact, | |
423 factory.getSerializer(), | |
424 ttl, | |
425 insertDatabase(artifact, factory, ttl)); | |
426 } | |
427 | |
428 /** | |
429 * Stores an artifact into database if it does not exist there. | |
430 * If it exists there it is only updated. | |
431 * @param artifact The artifact to store/update. | |
432 * @param factory The factory which created the artifact. | |
433 * @param ttl The initial time to live of the artifact. | |
434 * @return A persistent version of the artifact to be able | |
435 * to store a modification later. | |
436 * @throws Exception Thrown if something went wrong during | |
437 * storing/updating. | |
438 */ | |
439 public PersistentArtifact storeOrReplace( | |
440 Artifact artifact, | |
441 ArtifactFactory factory, | |
442 Long ttl | |
443 ) | |
444 throws Exception | |
445 { | |
446 return new PersistentArtifact( | |
447 artifact, | |
448 factory.getSerializer(), | |
449 ttl, | |
450 storeOrReplaceDatabase(artifact, factory, ttl)); | |
451 } | |
452 | |
453 /** | |
454 * Implementors of this interface are able to process the raw | |
455 * artifact data from the database for loading. | |
456 */ | |
457 public interface ArtifactLoader { | |
458 | |
459 /** | |
460 * Creates a custom object from the raw artifact database data. | |
461 * @param factory The factory that created this artifact. | |
462 * @param ttl The current time to life of the artifact. | |
463 * @param bytes The raw artifact bytes from the database. | |
464 * @param id The database id of the artifact. | |
465 * @return The custom object created by the implementation. | |
466 */ | |
467 Object load(ArtifactFactory factory, Long ttl, byte [] bytes, int id); | |
468 | |
469 } // interface ArtifactLoader | |
470 | |
471 /** | |
472 * Fetches an artifact from the database identified by the | |
473 * given identifier. | |
474 * @param identifer The identifier of the artifact. | |
475 * @return A persistent wrapper around the found artifact | |
476 * to be able to write back a modifaction later or null | |
477 * if no artifact is found for this identifier. | |
478 */ | |
479 public PersistentArtifact getArtifact(String identifer) { | |
480 | |
481 return (PersistentArtifact)loadArtifact( | |
482 identifer, | |
483 new ArtifactLoader() { | |
484 | |
485 public Object load( | |
486 ArtifactFactory factory, | |
487 Long ttl, | |
488 byte [] bytes, | |
489 int id | |
490 ) { | |
491 ArtifactSerializer serializer = factory.getSerializer(); | |
492 | |
493 Artifact artifact = serializer.fromBytes(bytes); | |
494 | |
495 return artifact == null | |
496 ? null | |
497 : new PersistentArtifact(artifact, serializer, ttl, id); | |
498 } | |
499 }); | |
500 } | |
501 | |
502 /** | |
503 * More general loading mechanism for artifacts. The concrete | |
504 * load processing is delegated to the given loader. | |
505 * @param identifer The identifier of the artifact. | |
506 * @param loader The loader which processes the raw database data. | |
507 * @return The object created by the loader. | |
508 */ | |
509 public Object loadArtifact( | |
510 final String identifer, | |
511 final ArtifactLoader loader | |
512 ) { | |
513 if (!isValidIdentifier(identifer)) { | |
514 return null; | |
515 } | |
516 | |
517 if (factoryLookup == null) { | |
518 logger.error("factory lookup == null"); | |
519 return false; | |
520 } | |
521 | |
522 final Object [] loaded = new Object[1]; | |
523 | |
524 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
525 public boolean doIt() throws SQLException { | |
526 prepareStatement(SQL_LOAD_BY_GID); | |
527 stmnt.setString(1, identifer); | |
528 | |
529 result = stmnt.executeQuery(); | |
530 | |
531 if (!result.next()) { | |
532 return false; | |
533 } | |
534 | |
535 int id = result.getInt(1); | |
536 long ttlX = result.getLong(2); | |
537 Long ttl = result.wasNull() ? null : ttlX; | |
538 | |
539 String factoryName = result.getString(3); | |
540 | |
541 ArtifactFactory factory = factoryLookup | |
542 .getArtifactFactory(factoryName); | |
543 | |
544 if (factory == null) { | |
545 logger.error("factory '" + factoryName + "' not found"); | |
546 return false; | |
547 } | |
548 | |
549 byte [] bytes = result.getBytes(4); | |
550 | |
551 loaded[0] = loader.load(factory, ttl, bytes, id); | |
552 return true; | |
553 } | |
554 }; | |
555 | |
556 return exec.runRead() ? loaded[0] : null; | |
557 } | |
558 | |
559 /** | |
560 * Called if the load mechanism found an outdated artifact. | |
561 * It wakes up the database cleaner. | |
562 * @param id The id of the outdated artifact. | |
563 */ | |
564 protected void artifactOutdated(int id) { | |
565 if (logger.isDebugEnabled()) { | |
566 logger.info("artifactOutdated: id = " + id); | |
567 } | |
568 if (cleaner != null) { | |
569 cleaner.wakeup(); | |
570 } | |
571 } | |
572 | |
573 public Artifact reviveArtifact(String factoryName, byte [] bytes) { | |
574 if (factoryLookup == null) { | |
575 logger.error("reviveArtifact: factory lookup == null"); | |
576 return null; | |
577 } | |
578 ArtifactFactory factory = factoryLookup | |
579 .getArtifactFactory(factoryName); | |
580 | |
581 if (factory == null) { | |
582 logger.error( | |
583 "reviveArtifact: no factory '" + factoryName + "' found"); | |
584 return null; | |
585 } | |
586 | |
587 ArtifactSerializer serializer = factory.getSerializer(); | |
588 | |
589 return serializer.fromBytes(bytes); | |
590 } | |
591 | |
592 /** | |
593 * Internal method to store/replace an artifact inside the database. | |
594 * If an artifact with the given identifier does not exists it is | |
595 * created else only the content data is updated. | |
596 * @param artifact The artifact to be store/update inside the database. | |
597 * @param factory The factory that created the artifact. | |
598 * @param ttl The initial time to life of the artifact. | |
599 * @return The database id of the stored/updated artifact. | |
600 */ | |
601 protected int storeOrReplaceDatabase( | |
602 final Artifact artifact, | |
603 final ArtifactFactory factory, | |
604 final Long ttl | |
605 ) { | |
606 final String uuid = artifact.identifier(); | |
607 | |
608 if (!isValidIdentifier(uuid)) { | |
609 throw new RuntimeException("No valid UUID"); | |
610 } | |
611 | |
612 final int [] id = new int[1]; | |
613 final boolean [] stored = new boolean[1]; | |
614 | |
615 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
616 public boolean doIt() throws SQLException { | |
617 | |
618 prepareStatement(SQL_GET_ID); | |
619 stmnt.setString(1, uuid); | |
620 result = stmnt.executeQuery(); | |
621 | |
622 Integer ID = result.next() | |
623 ? Integer.valueOf(result.getInt(1)) | |
624 : null; | |
625 | |
626 reset(); | |
627 | |
628 if (stored[0] = ID != null) { // already in database | |
629 prepareStatement(SQL_REPLACE); | |
630 | |
631 if (ttl == null) { | |
632 stmnt.setNull(1, Types.BIGINT); | |
633 } | |
634 else { | |
635 stmnt.setLong(1, ttl.longValue()); | |
636 } | |
637 | |
638 stmnt.setString(2, factory.getName()); | |
639 stmnt.setBytes( | |
640 3, | |
641 factory.getSerializer().toBytes(artifact)); | |
642 id[0] = ID.intValue(); | |
643 stmnt.setInt(4, id[0]); | |
644 } | |
645 else { // new artifact | |
646 prepareStatement(SQL_NEXT_ID); | |
647 result = stmnt.executeQuery(); | |
648 | |
649 if (!result.next()) { | |
650 logger.error("No id generated"); | |
651 return false; | |
652 } | |
653 | |
654 reset(); | |
655 | |
656 prepareStatement(SQL_INSERT); | |
657 | |
658 id[0] = result.getInt(1); | |
659 stmnt.setInt(1, id[0]); | |
660 stmnt.setString(2, uuid); | |
661 if (ttl == null) { | |
662 stmnt.setNull(3, Types.BIGINT); | |
663 } | |
664 else { | |
665 stmnt.setLong(3, ttl.longValue()); | |
666 } | |
667 | |
668 stmnt.setString(4, factory.getName()); | |
669 | |
670 stmnt.setBytes( | |
671 5, | |
672 factory.getSerializer().toBytes(artifact)); | |
673 } | |
674 stmnt.execute(); | |
675 conn.commit(); | |
676 return true; | |
677 } | |
678 }; | |
679 | |
680 if (!exec.runWrite()) { | |
681 throw new RuntimeException("failed insert artifact into database"); | |
682 } | |
683 | |
684 if (stored[0]) { | |
685 fireStoredArtifact(artifact); | |
686 } | |
687 else { | |
688 fireCreatedArtifact(artifact); | |
689 } | |
690 | |
691 return id[0]; | |
692 } | |
693 | |
694 /** | |
695 * Internal method to store an artifact inside the database. | |
696 * @param artifact The artifact to be stored. | |
697 * @param factory The factory which created the artifact. | |
698 * @param ttl The initial time to live of the artifact. | |
699 * @return The database id of the stored artifact. | |
700 */ | |
701 protected int insertDatabase( | |
702 final Artifact artifact, | |
703 final ArtifactFactory factory, | |
704 final Long ttl | |
705 ) { | |
706 final int [] id = new int[1]; | |
707 | |
708 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
709 public boolean doIt() throws SQLException { | |
710 prepareStatement(SQL_NEXT_ID); | |
711 result = stmnt.executeQuery(); | |
712 | |
713 if (!result.next()) { | |
714 logger.error("No id generated"); | |
715 return false; | |
716 } | |
717 | |
718 id[0] = result.getInt(1); | |
719 | |
720 reset(); | |
721 prepareStatement(SQL_INSERT); | |
722 | |
723 String uuid = artifact.identifier(); | |
724 stmnt.setInt(1, id[0]); | |
725 stmnt.setString(2, uuid); | |
726 if (ttl == null) { | |
727 stmnt.setNull(3, Types.BIGINT); | |
728 } | |
729 else { | |
730 stmnt.setLong(3, ttl.longValue()); | |
731 } | |
732 | |
733 stmnt.setString(4, factory.getName()); | |
734 | |
735 stmnt.setBytes( | |
736 5, | |
737 factory.getSerializer().toBytes(artifact)); | |
738 | |
739 stmnt.execute(); | |
740 | |
741 conn.commit(); | |
742 return true; | |
743 } | |
744 }; | |
745 | |
746 if (!exec.runWrite()) { | |
747 throw new RuntimeException("failed insert artifact into database"); | |
748 } | |
749 | |
750 fireCreatedArtifact(artifact); | |
751 | |
752 return id[0]; | |
753 } | |
754 | |
755 protected void fireCreatedArtifact(Artifact artifact) { | |
756 for (BackendListener listener: listeners) { | |
757 listener.createdArtifact(artifact, this); | |
758 } | |
759 } | |
760 | |
761 /** | |
762 * Touches the access timestamp of a given artifact to prevent | |
763 * that it will be removed from the database by the database cleaner. | |
764 * @param artifact The persistent wrapper around the living artifact. | |
765 */ | |
766 public void touch(final PersistentArtifact artifact) { | |
767 sqlExecutor.new Instance() { | |
768 public boolean doIt() throws SQLException { | |
769 prepareStatement(SQL_TOUCH); | |
770 stmnt.setInt(1, artifact.getId()); | |
771 stmnt.execute(); | |
772 conn.commit(); | |
773 return true; | |
774 } | |
775 }.runWrite(); | |
776 } | |
777 | |
778 /** | |
779 * Writes modification of an artifact back to the database. | |
780 * @param artifact The persistent wrapper around a living | |
781 * artifact. | |
782 */ | |
783 public void store(final PersistentArtifact artifact) { | |
784 boolean success = sqlExecutor.new Instance() { | |
785 public boolean doIt() throws SQLException { | |
786 prepareStatement(SQL_UPDATE); | |
787 stmnt.setInt(2, artifact.getId()); | |
788 | |
789 byte [] bytes = artifact | |
790 .getSerializer() | |
791 .toBytes(artifact.getArtifact()); | |
792 | |
793 stmnt.setBytes(1, bytes); | |
794 stmnt.execute(); | |
795 conn.commit(); | |
796 return true; | |
797 } | |
798 }.runWrite(); | |
799 | |
800 if (success) { | |
801 fireStoredArtifact(artifact.getArtifact()); | |
802 } | |
803 } | |
804 | |
805 protected void fireStoredArtifact(Artifact artifact) { | |
806 for (BackendListener listener: listeners) { | |
807 listener.storedArtifact(artifact, this); | |
808 } | |
809 } | |
810 | |
811 | |
812 public User createUser( | |
813 final String name, | |
814 final String account, | |
815 final Document role, | |
816 final UserFactory factory, | |
817 final Object context | |
818 ) { | |
819 final User [] user = new User[1]; | |
820 | |
821 final byte [] roleData = XMLUtils.toByteArray(role, true); | |
822 | |
823 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
824 public boolean doIt() throws SQLException { | |
825 | |
826 prepareStatement(SQL_USERS_NEXT_ID); | |
827 result = stmnt.executeQuery(); | |
828 | |
829 if (!result.next()) { | |
830 return false; | |
831 } | |
832 | |
833 int id = result.getInt(1); | |
834 | |
835 reset(); | |
836 | |
837 String identifier = newIdentifier(); | |
838 | |
839 prepareStatement(SQL_USERS_INSERT); | |
840 | |
841 stmnt.setInt(1, id); | |
842 stmnt.setString(2, identifier); | |
843 stmnt.setString(3, name); | |
844 stmnt.setString(4, account); | |
845 | |
846 if (roleData == null) { | |
847 stmnt.setNull(5, Types.BIGINT); | |
848 } | |
849 else { | |
850 stmnt.setBytes(5, roleData); | |
851 } | |
852 | |
853 stmnt.execute(); | |
854 conn.commit(); | |
855 | |
856 user[0] = factory.createUser( | |
857 identifier, name, account, role, context); | |
858 return true; | |
859 } | |
860 }; | |
861 | |
862 boolean success = exec.runWrite(); | |
863 | |
864 if (success) { | |
865 fireCreatedUser(user[0]); | |
866 return user[0]; | |
867 } | |
868 | |
869 return null; | |
870 } | |
871 | |
872 protected void fireCreatedUser(User user) { | |
873 for (BackendListener listener: listeners) { | |
874 listener.createdUser(user, this); | |
875 } | |
876 } | |
877 | |
878 public boolean deleteUser(final String identifier) { | |
879 | |
880 if (!isValidIdentifier(identifier)) { | |
881 return false; | |
882 } | |
883 | |
884 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
885 public boolean doIt() throws SQLException { | |
886 prepareStatement(SQL_USERS_SELECT_ID_BY_GID); | |
887 | |
888 stmnt.setString(1, identifier); | |
889 result = stmnt.executeQuery(); | |
890 | |
891 if (!result.next()) { // No such user | |
892 return false; | |
893 } | |
894 | |
895 int id = result.getInt(1); | |
896 | |
897 reset(); | |
898 | |
899 // outdate the artifacts exclusively used by the user | |
900 | |
901 prepareStatement(SQL_OUTDATE_ARTIFACTS_USER); | |
902 stmnt.setInt(1, id); | |
903 stmnt.setInt(2, id); | |
904 stmnt.execute(); | |
905 | |
906 reset(); | |
907 | |
908 // delete the collection items of the user | |
909 | |
910 prepareStatement(SQL_DELETE_USER_COLLECTION_ITEMS); | |
911 stmnt.setInt(1, id); | |
912 stmnt.execute(); | |
913 | |
914 reset(); | |
915 | |
916 // delete the collections of the user | |
917 | |
918 prepareStatement(SQL_USERS_DELETE_COLLECTIONS); | |
919 stmnt.setInt(1, id); | |
920 stmnt.execute(); | |
921 | |
922 reset(); | |
923 | |
924 // delete the user | |
925 | |
926 prepareStatement(SQL_USERS_DELETE_ID); | |
927 stmnt.setInt(1, id); | |
928 stmnt.execute(); | |
929 | |
930 conn.commit(); | |
931 return true; | |
932 } | |
933 }; | |
934 | |
935 boolean success = exec.runWrite(); | |
936 | |
937 if (success) { | |
938 fireDeletedUser(identifier); | |
939 } | |
940 | |
941 return success; | |
942 } | |
943 | |
944 protected void fireDeletedUser(String identifier) { | |
945 for (BackendListener listener: listeners) { | |
946 listener.deletedUser(identifier, this); | |
947 } | |
948 } | |
949 | |
950 public User getUser( | |
951 final String identifier, | |
952 final UserFactory factory, | |
953 final Object context | |
954 ) { | |
955 if (!isValidIdentifier(identifier)) { | |
956 logger.debug("Invalid UUID: '" + identifier + "'"); | |
957 return null; | |
958 } | |
959 | |
960 final User [] user = new User[1]; | |
961 | |
962 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
963 public boolean doIt() throws SQLException { | |
964 prepareStatement(SQL_USERS_SELECT_GID); | |
965 stmnt.setString(1, identifier); | |
966 result = stmnt.executeQuery(); | |
967 if (!result.next()) { // no such user | |
968 return false; | |
969 } | |
970 // omit id | |
971 String name = result.getString(2); | |
972 String account = result.getString(3); | |
973 byte [] roleData = result.getBytes(4); | |
974 | |
975 Document role = null; | |
976 if (roleData != null) { | |
977 role = XMLUtils.fromByteArray(roleData, true); | |
978 } | |
979 | |
980 user[0] = factory.createUser( | |
981 identifier, name, account, role, context); | |
982 return true; | |
983 } | |
984 }; | |
985 | |
986 return exec.runRead() ? user[0] : null; | |
987 } | |
988 | |
989 /** | |
990 * Find/Get user by account. | |
991 */ | |
992 public User findUser( | |
993 final String account, | |
994 final UserFactory factory, | |
995 final Object context | |
996 ) { | |
997 | |
998 final User [] user = new User[1]; | |
999 logger.debug("Trying to find user by account " + account); | |
1000 | |
1001 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
1002 public boolean doIt() throws SQLException { | |
1003 prepareStatement(SQL_USERS_SELECT_ACCOUNT); | |
1004 stmnt.setString(1, account); | |
1005 result = stmnt.executeQuery(); | |
1006 if (!result.next()) { // no such user | |
1007 logger.debug("No user found."); | |
1008 return false; | |
1009 } | |
1010 String identifier = result.getString(1); | |
1011 String name = result.getString(2); | |
1012 String account = result.getString(3); | |
1013 byte [] roleData = result.getBytes(4); | |
1014 | |
1015 Document role = null; | |
1016 if (roleData != null) { | |
1017 role = XMLUtils.fromByteArray(roleData, true); | |
1018 } | |
1019 | |
1020 user[0] = factory.createUser( | |
1021 identifier, name, account, role, context); | |
1022 return true; | |
1023 } | |
1024 }; | |
1025 | |
1026 return exec.runRead() ? user[0] : null; | |
1027 } | |
1028 | |
1029 public User [] getUsers( | |
1030 final UserFactory factory, | |
1031 final Object context | |
1032 ) { | |
1033 final ArrayList<User> users = new ArrayList<User>(); | |
1034 | |
1035 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
1036 public boolean doIt() throws SQLException { | |
1037 prepareStatement(SQL_USERS_SELECT_ALL); | |
1038 result = stmnt.executeQuery(); | |
1039 | |
1040 while (result.next()) { | |
1041 // omit id | |
1042 String identifier = result.getString(2); | |
1043 String name = result.getString(3); | |
1044 String account = result.getString(4); | |
1045 byte [] roleData = result.getBytes(5); | |
1046 | |
1047 Document role = XMLUtils.fromByteArray(roleData, true); | |
1048 User user = factory.createUser( | |
1049 identifier, name, account, role, context); | |
1050 users.add(user); | |
1051 } | |
1052 return true; | |
1053 } | |
1054 }; | |
1055 | |
1056 return exec.runRead() | |
1057 ? users.toArray(new User[users.size()]) | |
1058 : null; | |
1059 } | |
1060 | |
1061 public ArtifactCollection createCollection( | |
1062 final String ownerIdentifier, | |
1063 final String name, | |
1064 final ArtifactCollectionFactory factory, | |
1065 final Document attribute, | |
1066 final Object context | |
1067 ) { | |
1068 if (name == null) { | |
1069 logger.debug("Name is null"); | |
1070 return null; | |
1071 } | |
1072 | |
1073 if (!isValidIdentifier(ownerIdentifier)) { | |
1074 logger.debug("Invalid owner id: '" + ownerIdentifier + "'"); | |
1075 return null; | |
1076 } | |
1077 | |
1078 final ArtifactCollection [] collection = new ArtifactCollection[1]; | |
1079 | |
1080 final byte [] data = XMLUtils.toByteArray(attribute, true); | |
1081 | |
1082 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
1083 public boolean doIt() throws SQLException { | |
1084 // fetch owner id | |
1085 prepareStatement(SQL_USERS_SELECT_ID_BY_GID); | |
1086 stmnt.setString(1, ownerIdentifier); | |
1087 result = stmnt.executeQuery(); | |
1088 | |
1089 if (!result.next()) { // no such user | |
1090 return false; | |
1091 } | |
1092 | |
1093 int ownerId = result.getInt(1); | |
1094 reset(); | |
1095 | |
1096 // fetch new collection seq number. | |
1097 prepareStatement(SQL_COLLECTIONS_NEXT_ID); | |
1098 result = stmnt.executeQuery(); | |
1099 | |
1100 if (!result.next()) { // no identifier generated | |
1101 return false; | |
1102 } | |
1103 | |
1104 int id = result.getInt(1); | |
1105 reset(); | |
1106 | |
1107 String identifier = newIdentifier(); | |
1108 | |
1109 prepareStatement(SQL_COLLECTIONS_INSERT); | |
1110 | |
1111 stmnt.setInt(1, id); | |
1112 stmnt.setString(2, identifier); | |
1113 stmnt.setString(3, name); | |
1114 stmnt.setInt(4, ownerId); | |
1115 | |
1116 // XXX: A bit odd: we don't have a collection, yet. | |
1117 Long ttl = factory.timeToLiveUntouched(null, context); | |
1118 | |
1119 if (ttl == null) { | |
1120 stmnt.setNull(5, Types.BIGINT); | |
1121 } | |
1122 else { | |
1123 stmnt.setLong(5, ttl); | |
1124 } | |
1125 | |
1126 if (data == null) { | |
1127 stmnt.setNull(6, Types.BINARY); | |
1128 } | |
1129 else { | |
1130 stmnt.setBytes(6, data); | |
1131 } | |
1132 | |
1133 stmnt.execute(); | |
1134 conn.commit(); | |
1135 | |
1136 reset(); | |
1137 | |
1138 // fetch creation time from database | |
1139 // done this way to use the time system | |
1140 // of the database. | |
1141 | |
1142 prepareStatement(SQL_COLLECTIONS_CREATION_TIME); | |
1143 stmnt.setInt(1, id); | |
1144 | |
1145 result = stmnt.executeQuery(); | |
1146 | |
1147 Date creationTime = null; | |
1148 | |
1149 if (result.next()) { | |
1150 Timestamp timestamp = result.getTimestamp(1); | |
1151 creationTime = new Date(timestamp.getTime()); | |
1152 } | |
1153 | |
1154 collection[0] = factory.createCollection( | |
1155 identifier, name, creationTime, ttl, attribute, context); | |
1156 | |
1157 if (collection[0] != null) { | |
1158 // XXX: Little hack to make the listeners happy | |
1159 collection[0].setUser(new DefaultUser(ownerIdentifier)); | |
1160 } | |
1161 | |
1162 return true; | |
1163 } | |
1164 }; | |
1165 | |
1166 boolean success = exec.runWrite(); | |
1167 | |
1168 if (success) { | |
1169 fireCreatedCollection(collection[0]); | |
1170 return collection[0]; | |
1171 } | |
1172 return null; | |
1173 } | |
1174 | |
1175 protected void fireCreatedCollection(ArtifactCollection collection) { | |
1176 for (BackendListener listener: listeners) { | |
1177 listener.createdCollection(collection, this); | |
1178 } | |
1179 } | |
1180 | |
1181 public ArtifactCollection getCollection( | |
1182 final String collectionId, | |
1183 final ArtifactCollectionFactory collectionFactory, | |
1184 final UserFactory userFactory, | |
1185 final Object context | |
1186 ) { | |
1187 if (!isValidIdentifier(collectionId)) { | |
1188 logger.debug("collection id is not valid: " + collectionId); | |
1189 return null; | |
1190 } | |
1191 | |
1192 final ArtifactCollection[] ac = new ArtifactCollection[1]; | |
1193 | |
1194 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
1195 public boolean doIt() throws SQLException { | |
1196 | |
1197 prepareStatement(SQL_COLLECTIONS_SELECT_GID); | |
1198 stmnt.setString(1, collectionId); | |
1199 | |
1200 result = stmnt.executeQuery(); | |
1201 if (!result.next()) { | |
1202 logger.debug("No such collection"); | |
1203 return false; | |
1204 } | |
1205 | |
1206 String collectionName = result.getString(2); | |
1207 String ownerId = result.getString(3); | |
1208 Date creationTime = | |
1209 new Date(result.getTimestamp(4).getTime()); | |
1210 Date lastAccess = | |
1211 new Date(result.getTimestamp(5).getTime()); | |
1212 Document attr = | |
1213 XMLUtils.fromByteArray(result.getBytes(6), true); | |
1214 long ttl = result.getLong(7); | |
1215 | |
1216 ArtifactCollection collection = | |
1217 collectionFactory.createCollection( | |
1218 collectionId, | |
1219 collectionName, | |
1220 creationTime, | |
1221 ttl, | |
1222 attr, | |
1223 context); | |
1224 | |
1225 if (ownerId != null) { | |
1226 collection.setUser(new LazyBackendUser( | |
1227 ownerId, userFactory, Backend.this, context)); | |
1228 } | |
1229 | |
1230 ac[0] = collection; | |
1231 | |
1232 return true; | |
1233 } | |
1234 }; | |
1235 | |
1236 return exec.runRead() ? ac[0] : null; | |
1237 } | |
1238 | |
1239 public ArtifactCollection [] listCollections( | |
1240 final String ownerIdentifier, | |
1241 final Document data, | |
1242 final ArtifactCollectionFactory collectionFactory, | |
1243 final UserFactory userFactory, | |
1244 final Object context | |
1245 ) { | |
1246 if (ownerIdentifier != null | |
1247 && !isValidIdentifier(ownerIdentifier)) { | |
1248 logger.debug("Invalid owner id: '" + ownerIdentifier + "'"); | |
1249 return null; | |
1250 } | |
1251 | |
1252 final ArrayList<ArtifactCollection> collections = | |
1253 new ArrayList<ArtifactCollection>(); | |
1254 | |
1255 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
1256 | |
1257 public boolean doIt() throws SQLException { | |
1258 | |
1259 if (ownerIdentifier != null) { | |
1260 prepareStatement(SQL_COLLECTIONS_SELECT_USER); | |
1261 stmnt.setString(1, ownerIdentifier); | |
1262 } | |
1263 else { | |
1264 prepareStatement(SQL_COLLECTIONS_SELECT_ALL); | |
1265 } | |
1266 | |
1267 result = stmnt.executeQuery(); | |
1268 | |
1269 HashMap<String, LazyBackendUser> users = | |
1270 new HashMap<String, LazyBackendUser>(); | |
1271 | |
1272 while (result.next()) { | |
1273 String collectionIdentifier = result.getString(1); | |
1274 String collectionName = result.getString(2); | |
1275 Date creationTime = | |
1276 new Date(result.getTimestamp(3).getTime()); | |
1277 String userIdentifier = result.getString(4); | |
1278 long ttl = result.getLong(5); | |
1279 | |
1280 ArtifactCollection collection = | |
1281 collectionFactory.createCollection( | |
1282 collectionIdentifier, | |
1283 collectionName, | |
1284 creationTime, | |
1285 ttl, | |
1286 data, | |
1287 context); | |
1288 | |
1289 if (userIdentifier != null) { | |
1290 LazyBackendUser user = users.get(userIdentifier); | |
1291 if (user == null) { | |
1292 user = new LazyBackendUser( | |
1293 userIdentifier, userFactory, | |
1294 Backend.this, context); | |
1295 users.put(userIdentifier, user); | |
1296 } | |
1297 collection.setUser(user); | |
1298 } | |
1299 | |
1300 collections.add(collection); | |
1301 } | |
1302 return true; | |
1303 } | |
1304 }; | |
1305 | |
1306 return exec.runRead() | |
1307 ? collections.toArray(new ArtifactCollection[collections.size()]) | |
1308 : null; | |
1309 } | |
1310 | |
1311 | |
1312 public String getMasterArtifact(final String collectionId) { | |
1313 if (!isValidIdentifier(collectionId)) { | |
1314 logger.debug("Invalid collection id: '" + collectionId + "'"); | |
1315 return null; | |
1316 } | |
1317 final String [] uuid = new String[1]; | |
1318 | |
1319 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
1320 public boolean doIt() throws SQLException { | |
1321 // Fetch masters (oldest artifact) id. | |
1322 prepareStatement(SQL_COLLECTIONS_OLDEST_ARTIFACT); | |
1323 stmnt.setString(1, collectionId); | |
1324 stmnt.setMaxRows(1); // | |
1325 result = stmnt.executeQuery(); | |
1326 if (!result.next()) { | |
1327 logger.debug("No such collection: " + collectionId); | |
1328 return false; | |
1329 } | |
1330 uuid[0] = result.getString(1); | |
1331 if (logger.isDebugEnabled()) { | |
1332 logger.debug("getMasterArtifact result.getString " + | |
1333 uuid[0]); | |
1334 } | |
1335 return true; | |
1336 } | |
1337 }; | |
1338 return exec.runRead() ? uuid[0] : null; | |
1339 } | |
1340 | |
1341 public boolean deleteCollection(final String collectionId) { | |
1342 if (!isValidIdentifier(collectionId)) { | |
1343 logger.debug("Invalid collection id: '" + collectionId + "'"); | |
1344 return false; | |
1345 } | |
1346 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
1347 public boolean doIt() throws SQLException { | |
1348 // fetch collection id | |
1349 prepareStatement(SQL_COLLECTIONS_ID_BY_GID); | |
1350 stmnt.setString(1, collectionId); | |
1351 result = stmnt.executeQuery(); | |
1352 if (!result.next()) { | |
1353 logger.debug("No such collection: " + collectionId); | |
1354 return false; | |
1355 } | |
1356 int id = result.getInt(1); | |
1357 reset(); | |
1358 | |
1359 // outdate artifacts that are only in this collection | |
1360 logger.info("Outdate Artifacts that belong to collection: " + id); | |
1361 | |
1362 prepareStatement(SQL_OUTDATE_ARTIFACTS_COLLECTION); | |
1363 stmnt.setInt(1, id); | |
1364 stmnt.setInt(2, id); | |
1365 stmnt.execute(); | |
1366 reset(); | |
1367 | |
1368 // delete the collection items | |
1369 prepareStatement(SQL_DELETE_COLLECTION_ITEMS); | |
1370 stmnt.setInt(1, id); | |
1371 stmnt.execute(); | |
1372 reset(); | |
1373 | |
1374 // delete the collection | |
1375 prepareStatement(SQL_DELETE_COLLECTION); | |
1376 stmnt.setInt(1, id); | |
1377 stmnt.execute(); | |
1378 conn.commit(); | |
1379 return true; | |
1380 } | |
1381 }; | |
1382 boolean success = exec.runWrite(); | |
1383 | |
1384 if (success) { | |
1385 fireDeletedCollection(collectionId); | |
1386 } | |
1387 | |
1388 return success; | |
1389 } | |
1390 | |
1391 protected void fireDeletedCollection(String identifier) { | |
1392 for (BackendListener listener: listeners) { | |
1393 listener.deletedCollection(identifier, this); | |
1394 } | |
1395 } | |
1396 | |
1397 public Document getCollectionAttribute(final String collectionId) { | |
1398 if (!isValidIdentifier(collectionId)) { | |
1399 logger.debug("collection id is not valid: " + collectionId); | |
1400 } | |
1401 | |
1402 final byte[][] data = new byte[1][1]; | |
1403 | |
1404 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
1405 public boolean doIt() throws SQLException { | |
1406 prepareStatement(SQL_COLLECTION_GET_ATTRIBUTE); | |
1407 stmnt.setString(1, collectionId); | |
1408 result = stmnt.executeQuery(); | |
1409 if (!result.next()) { | |
1410 logger.debug("No such collection."); | |
1411 return false; | |
1412 } | |
1413 | |
1414 data[0] = result.getBytes(1); | |
1415 return true; | |
1416 } | |
1417 }; | |
1418 | |
1419 return exec.runRead() | |
1420 ? XMLUtils.fromByteArray(data[0], true) | |
1421 : null; | |
1422 } | |
1423 | |
1424 public boolean setCollectionAttribute( | |
1425 final String collectionId, | |
1426 Document attribute | |
1427 ) { | |
1428 if (!isValidIdentifier(collectionId)) { | |
1429 logger.debug("collection id is not valid: " + collectionId); | |
1430 return false; | |
1431 } | |
1432 | |
1433 final byte [] data = XMLUtils.toByteArray(attribute, true); | |
1434 | |
1435 boolean success = sqlExecutor.new Instance() { | |
1436 public boolean doIt() throws SQLException { | |
1437 | |
1438 // set the column in collection items | |
1439 prepareStatement(SQL_COLLECTION_SET_ATTRIBUTE); | |
1440 if (data == null) { | |
1441 stmnt.setNull(1, Types.BINARY); | |
1442 } | |
1443 else { | |
1444 stmnt.setBytes(1, data); | |
1445 } | |
1446 stmnt.setString(2, collectionId); | |
1447 stmnt.execute(); | |
1448 reset(); | |
1449 | |
1450 // touch the collection | |
1451 prepareStatement(SQL_COLLECTIONS_TOUCH_BY_GID); | |
1452 stmnt.setString(1, collectionId); | |
1453 stmnt.execute(); | |
1454 | |
1455 conn.commit(); | |
1456 return true; | |
1457 } | |
1458 }.runWrite(); | |
1459 | |
1460 if (success) { | |
1461 fireChangedCollectionAttribute(collectionId, attribute); | |
1462 } | |
1463 | |
1464 return success; | |
1465 } | |
1466 | |
1467 protected void fireChangedCollectionAttribute( | |
1468 String collectionId, | |
1469 Document document | |
1470 ) { | |
1471 for (BackendListener listener: listeners) { | |
1472 listener.changedCollectionAttribute(collectionId, document, this); | |
1473 } | |
1474 } | |
1475 | |
1476 public Document getCollectionItemAttribute( | |
1477 final String collectionId, | |
1478 final String artifactId | |
1479 ) { | |
1480 if (!isValidIdentifier(collectionId)) { | |
1481 logger.debug("collection id is not valid: " + collectionId); | |
1482 return null; | |
1483 } | |
1484 if (!isValidIdentifier(artifactId)) { | |
1485 logger.debug("artifact id is not valid: " + artifactId); | |
1486 return null; | |
1487 } | |
1488 | |
1489 final byte [][] data = new byte[1][1]; | |
1490 | |
1491 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
1492 public boolean doIt() throws SQLException { | |
1493 prepareStatement(SQL_COLLECTION_ITEM_GET_ATTRIBUTE); | |
1494 stmnt.setString(1, collectionId); | |
1495 stmnt.setString(2, artifactId); | |
1496 result = stmnt.executeQuery(); | |
1497 if (!result.next()) { | |
1498 logger.debug("No such collection item"); | |
1499 return false; | |
1500 } | |
1501 data[0] = result.getBytes(1); | |
1502 return true; | |
1503 } | |
1504 }; | |
1505 | |
1506 return exec.runRead() | |
1507 ? XMLUtils.fromByteArray(data[0], true) | |
1508 : null; | |
1509 } | |
1510 | |
1511 public boolean setCollectionItemAttribute( | |
1512 final String collectionId, | |
1513 final String artifactId, | |
1514 Document attribute | |
1515 ) { | |
1516 if (!isValidIdentifier(collectionId)) { | |
1517 logger.debug("collection id is not valid: " + collectionId); | |
1518 return false; | |
1519 } | |
1520 if (!isValidIdentifier(artifactId)) { | |
1521 logger.debug("artifact id is not valid: " + artifactId); | |
1522 return false; | |
1523 } | |
1524 | |
1525 final byte [] data = XMLUtils.toByteArray(attribute, true); | |
1526 | |
1527 boolean success = sqlExecutor.new Instance() { | |
1528 public boolean doIt() throws SQLException { | |
1529 | |
1530 // set the column in collection items | |
1531 prepareStatement(SQL_COLLECTION_ITEM_SET_ATTRIBUTE); | |
1532 if (data == null) { | |
1533 stmnt.setNull(1, Types.BINARY); | |
1534 } | |
1535 else { | |
1536 stmnt.setBytes(1, data); | |
1537 } | |
1538 stmnt.setString(2, collectionId); | |
1539 stmnt.setString(3, artifactId); | |
1540 stmnt.execute(); | |
1541 reset(); | |
1542 | |
1543 // touch the collection | |
1544 prepareStatement(SQL_COLLECTIONS_TOUCH_BY_GID); | |
1545 stmnt.setString(1, collectionId); | |
1546 stmnt.execute(); | |
1547 | |
1548 conn.commit(); | |
1549 return true; | |
1550 } | |
1551 }.runWrite(); | |
1552 | |
1553 if (success) { | |
1554 fireChangedCollectionItemAttribute( | |
1555 collectionId, artifactId, attribute); | |
1556 } | |
1557 | |
1558 return success; | |
1559 } | |
1560 | |
1561 protected void fireChangedCollectionItemAttribute( | |
1562 String collectionId, | |
1563 String artifactId, | |
1564 Document document | |
1565 ) { | |
1566 for (BackendListener listener: listeners) { | |
1567 listener.changedCollectionItemAttribute( | |
1568 collectionId, artifactId, document, this); | |
1569 } | |
1570 } | |
1571 | |
1572 public boolean addCollectionArtifact( | |
1573 final String collectionId, | |
1574 final String artifactId, | |
1575 final Document attribute | |
1576 ) { | |
1577 if (!isValidIdentifier(collectionId)) { | |
1578 logger.debug("Invalid collection id: '" + collectionId + "'"); | |
1579 return false; | |
1580 } | |
1581 | |
1582 if (!isValidIdentifier(artifactId)) { | |
1583 logger.debug("Invalid artifact id: '" + artifactId + "'"); | |
1584 return false; | |
1585 } | |
1586 | |
1587 final byte [] data = XMLUtils.toByteArray(attribute, true); | |
1588 | |
1589 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
1590 public boolean doIt() throws SQLException { | |
1591 // fetch artifact id | |
1592 prepareStatement(SQL_GET_ID); | |
1593 stmnt.setString(1, artifactId); | |
1594 result = stmnt.executeQuery(); | |
1595 if (!result.next()) { | |
1596 logger.debug("No such artifact: " + artifactId); | |
1597 return false; | |
1598 } | |
1599 int aid = result.getInt(1); | |
1600 reset(); | |
1601 | |
1602 // fetch collection id | |
1603 prepareStatement(SQL_COLLECTIONS_ID_BY_GID); | |
1604 stmnt.setString(1, collectionId); | |
1605 result = stmnt.executeQuery(); | |
1606 if (!result.next()) { | |
1607 logger.debug("No such collection: " + collectionId); | |
1608 } | |
1609 int cid = result.getInt(1); | |
1610 reset(); | |
1611 | |
1612 // check if artifact is already in collection | |
1613 prepareStatement(SQL_COLLECTION_CHECK_ARTIFACT); | |
1614 stmnt.setInt(1, aid); | |
1615 stmnt.setInt(2, cid); | |
1616 result = stmnt.executeQuery(); | |
1617 if (result.next()) { | |
1618 logger.debug("artifact already in collection"); | |
1619 return false; | |
1620 } | |
1621 reset(); | |
1622 | |
1623 // fetch fresh id for new collection item | |
1624 prepareStatement(SQL_COLLECTION_ITEMS_ID_NEXTVAL); | |
1625 result = stmnt.executeQuery(); | |
1626 if (!result.next()) { | |
1627 logger.debug("no collection item id generated"); | |
1628 return false; | |
1629 } | |
1630 int ci_id = result.getInt(1); | |
1631 reset(); | |
1632 | |
1633 // insert new collection item | |
1634 prepareStatement(SQL_COLLECTION_ITEMS_INSERT); | |
1635 stmnt.setInt(1, ci_id); | |
1636 stmnt.setInt(2, cid); | |
1637 stmnt.setInt(3, aid); | |
1638 | |
1639 if (data == null) { | |
1640 stmnt.setNull(4, Types.BINARY); | |
1641 } | |
1642 else { | |
1643 stmnt.setBytes(4, data); | |
1644 } | |
1645 stmnt.execute(); | |
1646 conn.commit(); | |
1647 | |
1648 return true; | |
1649 } | |
1650 }; | |
1651 boolean success = exec.runWrite(); | |
1652 | |
1653 if (success) { | |
1654 fireAddedArtifactToCollection(artifactId, collectionId); | |
1655 } | |
1656 | |
1657 return success; | |
1658 } | |
1659 | |
1660 protected void fireAddedArtifactToCollection( | |
1661 String artifactId, | |
1662 String collectionId | |
1663 ) { | |
1664 for (BackendListener listener: listeners) { | |
1665 listener.addedArtifactToCollection( | |
1666 artifactId, collectionId, this); | |
1667 } | |
1668 } | |
1669 | |
1670 public boolean removeCollectionArtifact( | |
1671 final String collectionId, | |
1672 final String artifactId | |
1673 ) { | |
1674 if (!isValidIdentifier(collectionId)) { | |
1675 logger.debug("Invalid collection id: '" + collectionId + "'"); | |
1676 return false; | |
1677 } | |
1678 | |
1679 boolean success = sqlExecutor.new Instance() { | |
1680 public boolean doIt() throws SQLException { | |
1681 | |
1682 // fetch id, collection id and artitfact id | |
1683 prepareStatement(SQL_COLLECTION_ITEM_ID_CID_AID); | |
1684 stmnt.setString(1, collectionId); | |
1685 stmnt.setString(2, artifactId); | |
1686 result = stmnt.executeQuery(); | |
1687 if (!result.next()) { | |
1688 logger.debug("No such collection item"); | |
1689 return false; | |
1690 } | |
1691 int id = result.getInt(1); | |
1692 int cid = result.getInt(2); | |
1693 int aid = result.getInt(3); | |
1694 reset(); | |
1695 | |
1696 // outdate artifact iff it is only in this collection | |
1697 prepareStatement(SQL_COLLECTION_ITEM_OUTDATE_ARTIFACT); | |
1698 stmnt.setInt(1, aid); | |
1699 stmnt.setInt(2, cid); | |
1700 stmnt.setInt(3, aid); | |
1701 stmnt.execute(); | |
1702 reset(); | |
1703 | |
1704 // delete collection item | |
1705 prepareStatement(SQL_COLLECTION_ITEM_DELETE); | |
1706 stmnt.setInt(1, id); | |
1707 stmnt.execute(); | |
1708 reset(); | |
1709 | |
1710 // touch collection | |
1711 prepareStatement(SQL_COLLECTIONS_TOUCH_BY_ID); | |
1712 stmnt.setInt(1, cid); | |
1713 stmnt.execute(); | |
1714 | |
1715 conn.commit(); | |
1716 return true; | |
1717 } | |
1718 }.runWrite(); | |
1719 | |
1720 if (success) { | |
1721 fireRemovedArtifactFromCollection(artifactId, collectionId); | |
1722 } | |
1723 | |
1724 return success; | |
1725 } | |
1726 | |
1727 protected void fireRemovedArtifactFromCollection( | |
1728 String artifactId, | |
1729 String collectionId | |
1730 ) { | |
1731 for (BackendListener listener: listeners) { | |
1732 listener.removedArtifactFromCollection( | |
1733 artifactId, collectionId, this); | |
1734 } | |
1735 } | |
1736 | |
1737 public CollectionItem [] listCollectionArtifacts( | |
1738 final String collectionId | |
1739 ) { | |
1740 if (!isValidIdentifier(collectionId)) { | |
1741 logger.debug("Invalid collection id: '" + collectionId + "'"); | |
1742 return null; | |
1743 } | |
1744 | |
1745 final ArrayList<CollectionItem> collectionItems = | |
1746 new ArrayList<CollectionItem>(); | |
1747 | |
1748 SQLExecutor.Instance exec = sqlExecutor.new Instance() { | |
1749 public boolean doIt() throws SQLException { | |
1750 prepareStatement(SQL_COLLECTION_ITEMS_LIST_GID); | |
1751 stmnt.setString(1, collectionId); | |
1752 result = stmnt.executeQuery(); | |
1753 while (result.next()) { | |
1754 CollectionItem item = new DefaultCollectionItem( | |
1755 result.getString(1), | |
1756 result.getBytes(2)); | |
1757 collectionItems.add(item); | |
1758 } | |
1759 return true; | |
1760 } | |
1761 }; | |
1762 | |
1763 return exec.runRead() | |
1764 ? collectionItems.toArray( | |
1765 new CollectionItem[collectionItems.size()]) | |
1766 : null; | |
1767 } | |
1768 | |
1769 | |
1770 public boolean setCollectionTTL(final String uuid, final Long ttl) { | |
1771 if (!isValidIdentifier(uuid)) { | |
1772 logger.debug("Invalid collection id: '" + uuid + "'"); | |
1773 return false; | |
1774 } | |
1775 | |
1776 return sqlExecutor.new Instance() { | |
1777 public boolean doIt() throws SQLException { | |
1778 prepareStatement(SQL_UPDATE_COLLECTION_TTL); | |
1779 if (ttl == null) { | |
1780 stmnt.setNull(1, Types.BIGINT); | |
1781 } | |
1782 else { | |
1783 stmnt.setLong(1, ttl); | |
1784 } | |
1785 stmnt.setString(2, uuid); | |
1786 stmnt.execute(); | |
1787 conn.commit(); | |
1788 | |
1789 return true; | |
1790 } | |
1791 }.runWrite(); | |
1792 } | |
1793 | |
1794 | |
1795 public boolean setCollectionName(final String uuid, final String name) { | |
1796 if (!isValidIdentifier(uuid)) { | |
1797 logger.debug("Invalid collection id: '" + uuid + "'"); | |
1798 return false; | |
1799 } | |
1800 | |
1801 boolean success = sqlExecutor.new Instance() { | |
1802 public boolean doIt() throws SQLException { | |
1803 prepareStatement(SQL_UPDATE_COLLECTION_NAME); | |
1804 stmnt.setString(1, name); | |
1805 stmnt.setString(2, uuid); | |
1806 stmnt.execute(); | |
1807 conn.commit(); | |
1808 | |
1809 return true; | |
1810 } | |
1811 }.runWrite(); | |
1812 | |
1813 if (success) { | |
1814 fireSetCollectionName(uuid, name); | |
1815 } | |
1816 | |
1817 return success; | |
1818 } | |
1819 | |
1820 protected void fireSetCollectionName(String identifier, String name) { | |
1821 for (BackendListener listener: listeners) { | |
1822 listener.setCollectionName(identifier, name); | |
1823 } | |
1824 } | |
1825 | |
1826 public boolean loadAllArtifacts(final ArtifactLoadedCallback alc) { | |
1827 | |
1828 logger.debug("loadAllArtifacts"); | |
1829 | |
1830 if (factoryLookup == null) { | |
1831 logger.error("factory lookup == null"); | |
1832 return false; | |
1833 } | |
1834 | |
1835 boolean success = sqlExecutor.new Instance() { | |
1836 @Override | |
1837 public boolean doIt() throws SQLException { | |
1838 // a little cache to avoid too much deserializations. | |
1839 LRUCache<String, Artifact> alreadyLoaded = | |
1840 new LRUCache<String, Artifact>(200); | |
1841 | |
1842 prepareStatement(SQL_ALL_ARTIFACTS); | |
1843 result = stmnt.executeQuery(); | |
1844 while (result.next()) { | |
1845 String userId = result.getString("u_gid"); | |
1846 String collectionId = result.getString("c_gid"); | |
1847 String collectionName = result.getString("c_name"); | |
1848 String artifactId = result.getString("a_gid"); | |
1849 String factoryName = result.getString("factory"); | |
1850 Date collectionCreated = | |
1851 new Date(result.getTimestamp("c_creation").getTime()); | |
1852 Date artifactCreated = | |
1853 new Date(result.getTimestamp("a_creation").getTime()); | |
1854 | |
1855 Artifact artifact = alreadyLoaded.get(artifactId); | |
1856 | |
1857 if (artifact != null) { | |
1858 alc.artifactLoaded( | |
1859 userId, | |
1860 collectionId, collectionName, | |
1861 collectionCreated, | |
1862 artifactId, artifactCreated, artifact); | |
1863 continue; | |
1864 } | |
1865 | |
1866 ArtifactFactory factory = factoryLookup | |
1867 .getArtifactFactory(factoryName); | |
1868 | |
1869 if (factory == null) { | |
1870 logger.error("factory '" + factoryName + "' not found"); | |
1871 continue; | |
1872 } | |
1873 | |
1874 byte [] bytes = result.getBytes("data"); | |
1875 | |
1876 artifact = factory.getSerializer().fromBytes(bytes); | |
1877 | |
1878 if (artifact != null) { | |
1879 alc.artifactLoaded( | |
1880 userId, | |
1881 collectionId, collectionName, collectionCreated, | |
1882 artifactId, artifactCreated, artifact); | |
1883 } | |
1884 | |
1885 alreadyLoaded.put(artifactId, artifact); | |
1886 } | |
1887 return true; | |
1888 } | |
1889 }.runRead(); | |
1890 | |
1891 if (logger.isDebugEnabled()) { | |
1892 logger.debug("loadAllArtifacts success: " + success); | |
1893 } | |
1894 | |
1895 return success; | |
1896 } | |
1897 | |
1898 @Override | |
1899 public void killedArtifacts(List<String> identifiers) { | |
1900 logger.debug("killedArtifacts"); | |
1901 for (BackendListener listener: listeners) { | |
1902 listener.killedArtifacts(identifiers, this); | |
1903 } | |
1904 } | |
1905 | |
1906 @Override | |
1907 public void killedCollections(List<String> identifiers) { | |
1908 logger.debug("killedCollections"); | |
1909 for (BackendListener listener: listeners) { | |
1910 listener.killedCollections(identifiers, this); | |
1911 } | |
1912 } | |
1913 } | |
1914 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 : |