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 :

http://dive4elements.wald.intevation.org