package org.gcube.informationsystem.collector.impl.xmlstorage.exist; import org.xmldb.api.base.XMLDBException; import org.xmldb.api.base.Collection; import org.xmldb.api.base.Database; import org.xmldb.api.base.ResourceSet; import org.xmldb.api.DatabaseManager; import org.xmldb.api.modules.XMLResource; import org.xmldb.api.modules.CollectionManagementService; import org.exist.xmldb.DatabaseInstanceManager; import org.exist.storage.DBBroker; import org.gcube.common.core.utils.logging.GCUBELog; import org.gcube.informationsystem.collector.impl.persistence.AggregatorPersistentResource; import org.gcube.informationsystem.collector.impl.persistence.PersistentResource; import org.gcube.informationsystem.collector.impl.persistence.PersistentResource.RESOURCETYPE; import org.gcube.informationsystem.collector.impl.xmlstorage.exist.XMLStorageManager; import org.gcube.informationsystem.collector.impl.xmlstorage.exist.XQuery; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; ; /** * A thread safe manager to interact with the XML Storage repository. * * @author Manuele Simi (ISTI-CNR) * */ public class XMLStorageManager { protected static String URI = "xmldb:exist://"; protected static String driver = "org.exist.xmldb.DatabaseImpl"; private static GCUBELog logger = new GCUBELog(XMLStorageManager.class); private Database database; // private Collection currentCollection; private Collection rootCollection; private Collection profilesRootCollection; // lock for write operations private Lock writeLock; // flag to warn when the DB is locked private boolean locked = false; private static final String PROPERTIES_COLLECTION_NAME = "Properties"; protected static String PROFILES_COLLECTION_NAME = "Profiles"; /** * Creates a new manager * */ public XMLStorageManager() { logger.debug("Creating a new XMLStorageManager"); writeLock = new ReentrantLock(); } /** * Initializes the local XML Storage repository * * @throws Exception * if the connection to eXist or its initialization fail */ public void initialize() throws Exception { try { logger.info("connecting to eXist DB..."); // this.printEnv(); // lock the instance writeLock.lock(); // register/create the DB instance Class cl = Class.forName(driver); this.database = (Database) cl.newInstance(); database.setProperty("create-database", "true"); DatabaseManager.registerDatabase(this.database); // try to load the collections for props and profiles logger.debug("Initializing the root collection"); this.rootCollection = DatabaseManager.getCollection(URI + DBBroker.ROOT_COLLECTION, "admin", "admin"); if (this.rootCollection == null) { logger.error("invalid root collection!"); throw new Exception("unable to load root collection"); } logger.debug("Initializing the collection Profiles"); this.profilesRootCollection = this.rootCollection.getChildCollection(XMLStorageManager.PROFILES_COLLECTION_NAME); if (this.profilesRootCollection == null) { logger.debug("Creating Profiles collection"); this.profilesRootCollection = this.createCollection(this.rootCollection, XMLStorageManager.PROFILES_COLLECTION_NAME); logger.debug("Profiles collection created"); } if (this.profilesRootCollection == null) { throw new Exception("Unable to load/create Profiles collection"); } this.rootCollection.setProperty("pretty", "true"); this.rootCollection.setProperty("encoding", "UTF-8"); this.profilesRootCollection.setProperty("pretty", "true"); this.profilesRootCollection.setProperty("encoding", "UTF-8"); } catch (XMLDBException edb) { logger.error("unable to initialize XML storage ", edb); throw new Exception("unable to initialize XML storage"); } catch (Exception e) { logger.debug("unable to initialize XML storage ", e); throw new Exception("unable to initialize XML storage"); } catch (java.lang.NoClassDefFoundError ncdfe) { logger.debug("unable to initialize XML storage", ncdfe); throw new Exception("unable to initialize XML storage"); } finally { writeLock.unlock(); } } /** * Shutdowns the XML Storage repository * * @return true if the operation succeed */ public boolean shutdown() { writeLock.lock(); try { DatabaseInstanceManager manager = (DatabaseInstanceManager) rootCollection.getService("DatabaseInstanceManager", "1.0"); manager.shutdown(); } catch (XMLDBException edb) { logger.error("Unable to shutdown XML storage"); logger.error("" + edb.getCause()); } finally { writeLock.unlock(); } return true; } /** * Loads a collection. If it does not exist, the collection is created. * * @param parentCollection * the parent collection of the collection to load * @param collectionName * name of the collection to load * @return the collection */ private Collection loadCollection(Collection parentCollection, String collectionName) { // set the current collection Collection currentCollection = null; try { currentCollection = parentCollection.getChildCollection(collectionName); if (currentCollection == null) { // the collection does not exist, it is created logger.info("Creating a new collection " + collectionName + "..."); currentCollection = this.createCollection(parentCollection, collectionName); } currentCollection.setProperty("pretty", "true"); currentCollection.setProperty("encoding", "UTF-8"); } catch (XMLDBException edb) { logger.error("failed to create collection " + collectionName + "!"); logger.error("" + edb.getCause()); } return currentCollection; } /** * Discards the current collection * */ private void resetCollection(Collection currentCollection) { try { currentCollection.close(); } catch (XMLDBException edb) { // Catch any issues with closing the exception. logger.error("unable to close collection " + edb.getMessage()); } currentCollection = null; } /** * Loads the collection containing the WS-ResourceProperties documents. It must be used when * quering/storing/updating WS-ResourceProperties documents * * @return the Collection */ public Collection loadPropertiesCollection() { logger.debug("Loading collection Properties... "); return this.loadCollection(this.rootCollection, XMLStorageManager.PROPERTIES_COLLECTION_NAME); } /** * Loads from the children of the Profile Collection, the collection identified by the given * name. It must be used when quering/storing/updating a particular kind of profile * * @param collectionName * the child collection of the Profile collection to load * @return the Collection */ public Collection loadProfileCollection(String collectionName) { logger.debug("Loading collection " + collectionName + "... "); return this.loadCollection(this.profilesRootCollection, collectionName); } /** * Loads the parent collection of all collections containing resources profiles. It must be used * when quering all the profiles at the same time * * @return the Collection */ public Collection loadAllProfilesCollection() { logger.debug("Loading all profiles collection... "); return this.loadCollection(this.rootCollection, XMLStorageManager.PROFILES_COLLECTION_NAME); } /** * Loads the root collection. It must be used when quering all the information maintained by the * DB instance at the same time * * @return the Collection */ public Collection loadAllCollections() { Collection currentCollection = null; logger.debug("Loading all collections... "); // return this.loadCollection(this.rootCollection, // XMLStorageManager.PROFILES_COLLECTION_NAME); try { currentCollection = DatabaseManager.getCollection(URI + DBBroker.ROOT_COLLECTION, "admin", "admin"); } catch (XMLDBException edb) { logger.error("Failed to load all collections!"); logger.error("", edb); } return currentCollection; } /** * Stores a AggregatorPersistentResource in the current collection. If the resource already * exists in the storage, it is updated. * * @param resource * the resource to store * @throws Exception * if the storing fails */ public void storeResource(PersistentResource resource) throws Exception { Collection currentCollection = null; if (resource.getType() == RESOURCETYPE.Profile) { // the entry contains a gCube resource profile currentCollection = this.loadProfileCollection(resource.getProfileType()); } else { // the entry contains generic properties currentCollection = this.loadPropertiesCollection(); } if (currentCollection == null) { logger.error("Unable to open the Collection"); return; } writeLock.lock(); this.locked = true; try { XMLResource document = (XMLResource) currentCollection.createResource(resource.getID(), "XMLResource"); document.setContent(resource.toString()); logger.debug("Storing/updating resource " + document.getId() + " in collection " + currentCollection.getName() + "..."); currentCollection.storeResource(document); logger.debug("...done"); } catch (XMLDBException edb) { logger.error("Failed to store resource " + resource.getID()); logger.error("" + edb.errorCode + " " + edb.getMessage(), edb); } catch (Exception e) { logger.error("" + e.getMessage(), e); } finally { this.resetCollection(currentCollection); writeLock.unlock(); this.locked = false; } } /** * * @return true if the connection to eXist is locked */ public boolean isLocked() { return this.locked; } /** * Retrieves a resource from the storage given its ID * * @param resourceID * @return * @throws Exception */ public AggregatorPersistentResource retrieveResourceFromID(String resourceID) throws Exception { XMLResource res = null; Collection currentCollection = this.loadAllCollections(); try { res = (XMLResource) currentCollection.getResource(resourceID); if (res == null) logger.warn("Resource " + resourceID + " not found!"); } catch (XMLDBException edb) { logger.error("Failed to retrieve document " + resourceID); logger.error("" + edb.errorCode + " " + edb.getMessage(), edb); throw new Exception(); } return new AggregatorPersistentResource(res); } /** * Retrieves a resource from the storage given its ID * * @param resourceID * @return * @throws Exception */ public AggregatorPersistentResource retrievePropertyResourceFromID(String resourceID) throws Exception { XMLResource res = null; Collection currentCollection = this.loadPropertiesCollection(); try { res = (XMLResource) currentCollection.getResource(resourceID); if (res == null) logger.warn("Resource " + resourceID + " not found!"); } catch (XMLDBException edb) { logger.error("Failed to retrieve document " + resourceID); logger.error("" + edb.errorCode + " " + edb.getMessage(), edb); throw new Exception(); } return new AggregatorPersistentResource(res); } /** * * @param xpathquery * @return */ public AggregatorPersistentResource executeXPathQuery(String xpathquery) { AggregatorPersistentResource res = null; // ArrayList results = new // ArrayList(); /* * try { // get query-service XPathQueryServiceImpl service = (XPathQueryServiceImpl) * currentCollection.getService("XPathQueryService", "1.0"); // set pretty-printing on * service.setProperty(OutputKeys.INDENT, "yes"); service.setProperty(OutputKeys.ENCODING, * "UTF-8"); ResourceSet set = service.query(xpathquery); logger.debug("number of returned * documents: " + set.getSize()); ResourceIterator i = set.getIterator(); * while(i.hasMoreResources()) { res = new AggregatorPersistentResource((XMLResource) * i.nextResource()); System.out.println("DILIGENT resource " + i + " " + res.toString()); } * * for (int i = 0; i < (int) set.getSize(); i++) { res = new * AggregatorPersistentResource((XMLResource) set.getResource((long) i)); * System.out.println("DILIGENT resource " + i + " " + res.toString()); } } catch * (XMLDBException edb) { logger.error("failed to execute Xpath query " + xpathquery); * edb.printStackTrace(); } catch (Exception e) { logger.error("exception " + xpathquery); * logger.error(e.getStackTrace()); } */ return res; } /** * Executes the given query on the current collection or on the root collection if any was * previously loaded * * @param query * - the query to run * @return */ public ResourceSet executeXQuery(XQuery query) { boolean retry = true; int attempts = 0, max_attempts = 3; ResourceSet result = null; Collection currentCollection = null; currentCollection = this.loadAllCollections(); while ((retry) && (attempts < max_attempts)) { try { // wait until the DB is unlocked while (State.storage_manager.isLocked()) Thread.sleep(1000); // execute query and get results in ResourceSet if (currentCollection == null) result = query.execute(this.rootCollection); else result = query.execute(currentCollection); retry = false; } catch (XMLDBException edb) { logger.error("Failed to execute XQuery " + query.toString()); logger.error("Error details: " + edb.errorCode + " " + edb.getMessage(), edb); // if the cause is a NullPointer, this can be due to a temporary // lock on the database instance if (edb.getCause() instanceof java.lang.NullPointerException) { retry = true; attempts++; logger.warn("Trying a new attempt for query execution"); } else retry = false; } catch (Exception e) { logger.error("", e); // if the cause is a NullPointer, this can be due to a temporary // lock on the database instance if (e instanceof java.lang.NullPointerException) { retry = true; attempts++; logger.warn("Trying a new attempt for query execution"); } else retry = false; } } this.resetCollection(currentCollection); return result; } /** * Deletes a WS-ResourceProperties resource identified by the given ID */ synchronized public void retrieveAndDeleteResourceFromID(String resourceID) throws Exception { if (resourceID == null) { logger.warn("Invalid resource ID"); return; } Collection propCollection = this.loadPropertiesCollection(); if (propCollection == null) { logger.error("Unable to load collection Properties!"); throw new Exception(); } try { logger.info("Trying to remove resource " + resourceID + " from collection " + propCollection.getName()); deleteResource(resourceID, propCollection); } catch (XMLDBException edb) { logger.error("Failed to remove the resource from the storage! "); logger.error("", edb); throw new Exception(); } finally { this.resetCollection(propCollection); } } /** * Deletes a Profile resource identified by the given ID */ synchronized public void retrieveAndDeleteProfileFromID(String profileID, String profileType) throws Exception { if (profileID == null) { logger.warn("Invalid profile ID"); return; } Collection profileCollection = this.loadProfileCollection(profileType); if (profileCollection == null) { logger.error("Uunable to load collection Profile!"); throw new Exception("unable to load collection Profile"); } try { logger.info("Trying to remove profile '" + profileID + "' from collection " + profileCollection.getName()); deleteResource(profileID, profileCollection); } catch (XMLDBException edb) { logger.error("Failed to remove the profile from the storage! "); logger.error("", edb); throw new Exception(); } finally { this.resetCollection(profileCollection); } } /** * Deletes the resource with the given ID from the local storage * * @param resourceID * - the ID of the resource * @param col * - the collection from which the resource has to be removed * @throws Exception */ private void deleteResource(String resourceID, Collection col) throws Exception { XMLResource res = null; // lock the instance this.writeLock.lock(); this.locked = true; try { res = (XMLResource) col.getResource(resourceID); if (res == null) logger.warn("Resource " + resourceID + " not found!"); else { col.removeResource(res); logger.info("Resource successfully removed"); } } catch (XMLDBException edb) { logger.error("Failed to retrieve resource " + resourceID); logger.error("", edb); throw new Exception(); } finally { this.resetCollection(col); this.writeLock.unlock(); this.locked = false; } } /** * Prints all the IDs of the Resources stored in the DB instance * */ public void printResourcesIDs() { String[] ress; Collection currentCollection = null; currentCollection = this.loadAllCollections(); try { if (currentCollection == null) { ress = this.rootCollection.listResources(); } else { ress = currentCollection.listResources(); } for (int i = 0; i < ress.length; i++) { logger.debug("Resource ID:" + ress[i]); } } catch (XMLDBException edb) { logger.error("Failed to read resource IDs ", edb); } finally { this.resetCollection(currentCollection); } } /** * Creates a new collection * * @param collectionName * @return the create Collection object */ private Collection createCollection(Collection parentCollection, String collectionName) { Collection col = null; this.writeLock.lock(); try { CollectionManagementService mgtService = (CollectionManagementService) parentCollection.getService("CollectionManagementService", "1.0"); col = mgtService.createCollection(collectionName); } catch (XMLDBException edb) { logger.error("Failed to create collection " + collectionName, edb); } finally { this.writeLock.unlock(); } return col; } /** * Delete the collection named PROPERTIES_COLLECTION_NAME from the storage */ public void deleteAllProperties() { this.writeLock.lock(); try { logger.info("Trying to delete the collection " + XMLStorageManager.PROPERTIES_COLLECTION_NAME + "..."); CollectionManagementService mgtService = (CollectionManagementService) rootCollection.getService("CollectionManagementService", "1.0"); mgtService.removeCollection(XMLStorageManager.PROPERTIES_COLLECTION_NAME); logger.info("Collection deleted"); } catch (XMLDBException edb) { logger.warn("Unable to delete the collection " + XMLStorageManager.PROPERTIES_COLLECTION_NAME + ": " + edb.toString()); } finally { this.writeLock.unlock(); } } public String[] listAllPropertiesIDs() { return listAllColletionIDs(this.loadPropertiesCollection()); } private String[] listAllColletionIDs(Collection collection) { String[] ids = null; String collectionName = ""; try { collectionName = collection.getName(); logger.debug("Retrieving all resource IDs from collection " + collectionName); ids = new String[collection.getResourceCount()]; ids = collection.listResources(); logger.debug("Retrieved " + ids.length + " elements"); } catch (XMLDBException edb) { logger.warn("Unable to retrieve ids from collection " + collectionName + " " + edb.toString()); } return ids; } /** * Loads a file content in a String * * @param file * path and name of the file to read * @return the file content * @throws IOException */ protected static String readFile(String file) throws IOException { BufferedReader f = new BufferedReader(new FileReader(file)); String line; StringBuffer content = new StringBuffer(); while ((line = f.readLine()) != null) content.append(line); f.close(); return content.toString(); } }