636 lines
15 KiB
Java
636 lines
15 KiB
Java
package eu.dnetlib.xml.database.exist; // NOPMD
|
|
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.io.FileWriter;
|
|
import java.io.IOException;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.*;
|
|
import java.util.Map.Entry;
|
|
import java.util.concurrent.locks.Lock;
|
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
import java.util.zip.ZipEntry;
|
|
import java.util.zip.ZipOutputStream;
|
|
|
|
import org.apache.commons.logging.Log;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.exist.collections.CollectionConfiguration;
|
|
import org.exist.util.DatabaseConfigurationException;
|
|
import org.exist.xmldb.DatabaseImpl;
|
|
import org.exist.xmldb.DatabaseInstanceManager;
|
|
import org.exist.xmldb.EXistResource;
|
|
import org.exist.xmldb.XmldbURI;
|
|
import org.springframework.beans.factory.annotation.Required;
|
|
import org.springframework.context.Lifecycle;
|
|
import org.xmldb.api.DatabaseManager;
|
|
import org.xmldb.api.base.*;
|
|
import org.xmldb.api.base.Collection;
|
|
import org.xmldb.api.modules.CollectionManagementService;
|
|
import org.xmldb.api.modules.XPathQueryService;
|
|
|
|
import eu.dnetlib.miscutils.datetime.DateUtils;
|
|
import eu.dnetlib.xml.database.Trigger;
|
|
|
|
import eu.dnetlib.xml.database.XMLDatabase;
|
|
|
|
/**
|
|
* eXist database wrapper.
|
|
*
|
|
* @author marko
|
|
*
|
|
*/
|
|
public class ExistDatabase implements XMLDatabase, Lifecycle { // NOPMD by marko
|
|
|
|
/**
|
|
* logger.
|
|
*/
|
|
private static final Log log = LogFactory.getLog(ExistDatabase.class); // NOPMD
|
|
|
|
/**
|
|
* eXist collection configuration special file.
|
|
*/
|
|
public static final String COLLECTION_XCONF = "collection.xconf";
|
|
|
|
/**
|
|
* exist xml resource type code.
|
|
*/
|
|
private static final String XMLRESOURCE = "XMLResource";
|
|
|
|
/**
|
|
* collection name to trigger instance map.
|
|
*
|
|
* all triggers declared here will be registered at startup.
|
|
*
|
|
*/
|
|
private Map<String, Trigger> triggerConf = new HashMap<>();
|
|
|
|
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
|
|
|
|
private final Lock readLock = rwl.readLock();
|
|
|
|
private final Lock writeLock = rwl.writeLock();
|
|
|
|
/**
|
|
* eXist database.
|
|
*/
|
|
private Database database;
|
|
|
|
/**
|
|
* eXist collection.
|
|
*/
|
|
private Collection root;
|
|
/**
|
|
* eXist database manager.
|
|
*/
|
|
private DatabaseInstanceManager manager;
|
|
/**
|
|
* eXist xpath service.
|
|
*/
|
|
private XPathQueryService queryService;
|
|
/**
|
|
* eXist collection manager.
|
|
*/
|
|
private CollectionManagementService colman;
|
|
/**
|
|
* eXist configuration file.
|
|
*/
|
|
private String configFile;
|
|
|
|
/**
|
|
* Directory in which backups are saved.
|
|
*/
|
|
private String backupDir;
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see org.springframework.context.Lifecycle#start()
|
|
*/
|
|
@Override
|
|
public void start() {
|
|
log.info("starting database");
|
|
try {
|
|
if (getDatabase() == null) {
|
|
setDatabase(new DatabaseImpl());
|
|
getDatabase().setProperty("configuration", getConfigFile());
|
|
getDatabase().setProperty("create-database", "true");
|
|
}
|
|
|
|
DatabaseManager.registerDatabase(getDatabase());
|
|
|
|
setRoot(DatabaseManager.getCollection("xmldb:exist://" + getRootCollection(), "admin", ""));
|
|
setManager((DatabaseInstanceManager) getRoot().getService("DatabaseInstanceManager", "1.0"));
|
|
setQueryService((XPathQueryService) getRoot().getService("XPathQueryService", "1.0"));
|
|
setColman((CollectionManagementService) getRoot().getService("CollectionManagementService", "1.0"));
|
|
|
|
for (final Entry<String, Trigger> entry : getTriggerConf().entrySet())
|
|
registerTrigger(entry.getValue(), entry.getKey());
|
|
|
|
} catch (final XMLDBException e) {
|
|
throw new IllegalStateException("cannot open eXist database", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* helper method.
|
|
*
|
|
* @param collection
|
|
* collection name
|
|
* @return an eXist collection
|
|
* @throws XMLDBException
|
|
* happens
|
|
*/
|
|
protected Collection getCollection(final String collection) throws XMLDBException {
|
|
readLock.lock();
|
|
try {
|
|
if (!collection.startsWith("/db"))
|
|
throw new XMLDBException(0, "collection path should begin with /db");
|
|
return database.getCollection("exist://" + collection, "admin", "");
|
|
}finally {
|
|
readLock.unlock();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see eu.dnetlib.xml.database.XMLDatabase#create(java.lang.String, java.lang.String, java.lang.String)
|
|
*/
|
|
@Override
|
|
public void create(final String name, final String collection, final String content) throws XMLDBException {
|
|
writeLock.lock();
|
|
try {
|
|
if ("".equals(name))
|
|
throw new XMLDBException(0, "cannot create a xml file with an empty file name");
|
|
|
|
Collection col = getCollection(collection);
|
|
|
|
if (col == null) {
|
|
// create parent collections
|
|
createCollection(collection, true);
|
|
col = getCollection(collection);
|
|
}
|
|
|
|
final Resource res = col.createResource(name, XMLRESOURCE);
|
|
res.setContent(content);
|
|
col.storeResource(res);
|
|
|
|
((EXistResource) res).freeResources();
|
|
col.close();
|
|
}finally {
|
|
writeLock.unlock();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see eu.dnetlib.xml.database.XMLDatabase#remove(java.lang.String, java.lang.String)
|
|
*/
|
|
@Override
|
|
public boolean remove(final String name, final String collection) throws XMLDBException {
|
|
writeLock.lock();
|
|
try {
|
|
final Collection col = getCollection(collection);
|
|
|
|
final Resource res = col.getResource(name);
|
|
if (res == null)
|
|
return false;
|
|
|
|
col.removeResource(res);
|
|
col.close();
|
|
return true;
|
|
} finally {
|
|
writeLock.unlock();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see eu.dnetlib.xml.database.XMLDatabase#update(java.lang.String, java.lang.String, java.lang.String)
|
|
*/
|
|
@Override
|
|
public void update(final String name, final String collection, final String content) throws XMLDBException {
|
|
writeLock.lock();
|
|
try{
|
|
final Collection col = getCollection(collection);
|
|
|
|
final Resource res = col.getResource(name);
|
|
if (res == null) {
|
|
throw new XMLDBException(0, "resource doesn't exist");
|
|
}
|
|
res.setContent(content);
|
|
col.storeResource(res);
|
|
((EXistResource) res).freeResources();
|
|
col.close();
|
|
} finally {
|
|
writeLock.unlock();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see eu.dnetlib.xml.database.XMLDatabase#read(java.lang.String, java.lang.String)
|
|
*/
|
|
@Override
|
|
public String read(final String name, final String collection) throws XMLDBException {
|
|
readLock.lock();
|
|
try {
|
|
Resource res = null;
|
|
final Collection coll = getCollection(collection);
|
|
try {
|
|
if (coll == null)
|
|
return null;
|
|
res = coll.getResource(name);
|
|
if (res != null)
|
|
return (String) res.getContent();
|
|
return null;
|
|
} finally {
|
|
if (res != null)
|
|
((EXistResource) res).freeResources();
|
|
coll.close();
|
|
}
|
|
}finally {
|
|
readLock.unlock();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see eu.dnetlib.xml.database.XMLDatabase#xquery(java.lang.String)
|
|
*/
|
|
@Override
|
|
public Iterator<String> xquery(final String query) throws XMLDBException {
|
|
readLock.lock();
|
|
try {
|
|
final ResourceSet result = getQueryService().query(query);
|
|
if (result == null)
|
|
return null;
|
|
final ResourceIterator iterator = result.getIterator();
|
|
return new Iterator<String>() {
|
|
@Override
|
|
public boolean hasNext() {
|
|
try {
|
|
return iterator.hasMoreResources();
|
|
} catch (XMLDBException e) {
|
|
throw new RuntimeException("Error while getting next element", e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String next() {
|
|
Resource res = null;
|
|
try {
|
|
res = iterator.nextResource();
|
|
return (String) res.getContent();
|
|
} catch (XMLDBException e) {
|
|
throw new RuntimeException("Error while getting next element", e);
|
|
} finally {
|
|
if (res != null)
|
|
try {
|
|
((EXistResource) res).freeResources();
|
|
} catch (XMLDBException e) {
|
|
log.error("error on free resource");
|
|
}
|
|
}
|
|
}
|
|
};
|
|
} finally {
|
|
readLock.unlock();
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
public void xupdate(final String query) throws XMLDBException {
|
|
writeLock.lock();
|
|
try{
|
|
getQueryService().query(query);
|
|
} finally {
|
|
writeLock.unlock();
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see org.springframework.context.Lifecycle#stop()
|
|
*/
|
|
@Override
|
|
public void stop() {
|
|
// no operation
|
|
try {
|
|
getManager().shutdown();
|
|
DatabaseManager.deregisterDatabase(database);
|
|
} catch (final XMLDBException e) {
|
|
log.fatal("cannot close database", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see eu.dnetlib.xml.database.XMLDatabase#collectionExists(java.lang.String)
|
|
*/
|
|
@Override
|
|
public boolean collectionExists(final String collection) throws XMLDBException {
|
|
Collection col = null;
|
|
try{
|
|
col = getCollection(collection);
|
|
return col != null;
|
|
} finally {
|
|
if (col!=null)
|
|
col.close();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see eu.dnetlib.xml.database.XMLDatabase#createCollection(java.lang.String)
|
|
*/
|
|
@Override
|
|
public void createCollection(final String collection) throws XMLDBException {
|
|
writeLock.lock();
|
|
try {
|
|
createCollection(collection, false);
|
|
}finally {
|
|
writeLock.unlock();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void createCollection(final String collection, final boolean recursive) throws XMLDBException {
|
|
if (recursive) {
|
|
final XmldbURI uri = XmldbURI.create(collection).removeLastSegment();
|
|
if (!collectionExists(uri.toString()))
|
|
createCollection(uri.toString(), true);
|
|
}
|
|
getColman().createCollection(collection);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see eu.dnetlib.xml.database.XMLDatabase#removeCollection(java.lang.String)
|
|
*/
|
|
@Override
|
|
public void removeCollection(final String collection) throws XMLDBException {
|
|
writeLock.lock();
|
|
try {
|
|
getColman().removeCollection(collection);
|
|
}finally {
|
|
writeLock.unlock();
|
|
}
|
|
}
|
|
|
|
public String getConfigFile() {
|
|
return configFile;
|
|
}
|
|
|
|
public void setConfigFile(final String configFile) {
|
|
this.configFile = configFile;
|
|
}
|
|
|
|
|
|
@Override
|
|
public String getBackupDir() {
|
|
return backupDir;
|
|
}
|
|
|
|
@Required
|
|
public void setBackupDir(final String backupDir) {
|
|
this.backupDir = backupDir;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see org.springframework.context.Lifecycle#isRunning() useless contract with spring.
|
|
*
|
|
*/
|
|
@Override
|
|
public boolean isRunning() {
|
|
return false;
|
|
}
|
|
|
|
protected Database getDatabase() {
|
|
return database;
|
|
}
|
|
|
|
protected void setDatabase(final Database database) {
|
|
this.database = database;
|
|
}
|
|
|
|
protected Collection getRoot() {
|
|
return root;
|
|
}
|
|
|
|
protected void setRoot(final Collection root) {
|
|
this.root = root;
|
|
}
|
|
|
|
protected DatabaseInstanceManager getManager() {
|
|
return manager;
|
|
}
|
|
|
|
protected void setManager(final DatabaseInstanceManager manager) {
|
|
this.manager = manager;
|
|
}
|
|
|
|
protected XPathQueryService getQueryService() {
|
|
return queryService;
|
|
}
|
|
|
|
protected void setQueryService(final XPathQueryService queryService) {
|
|
this.queryService = queryService;
|
|
}
|
|
|
|
protected CollectionManagementService getColman() {
|
|
return colman;
|
|
}
|
|
|
|
protected void setColman(final CollectionManagementService colman) {
|
|
this.colman = colman;
|
|
}
|
|
|
|
@Override
|
|
public String getRootCollection() {
|
|
return "/db";
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see eu.dnetlib.xml.database.XMLDatabase#listChildCollections(java.lang.String)
|
|
*/
|
|
@Override
|
|
public List<String> listChildCollections(final String collection) throws XMLDBException {
|
|
readLock.lock();
|
|
try {
|
|
final Collection col = getCollection(collection);
|
|
if (col == null)
|
|
return new ArrayList<>();
|
|
return Arrays.asList(col.listChildCollections());
|
|
} finally {
|
|
readLock.unlock();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see eu.dnetlib.xml.database.XMLDatabase#list(java.lang.String)
|
|
*/
|
|
@Override
|
|
public List<String> list(final String collection) throws XMLDBException {
|
|
readLock.lock();
|
|
try {
|
|
final Collection col = getCollection(collection);
|
|
if(col == null)
|
|
return new ArrayList();
|
|
return Arrays.asList(col.listResources());
|
|
} finally {
|
|
readLock.unlock();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* sets an underlying eXist trigger class for a given collection.
|
|
*
|
|
* @param triggerClass
|
|
* exist trigger class
|
|
* @param collection
|
|
* collection name
|
|
* @param events
|
|
* list of event names
|
|
* @param parameters
|
|
* parameter map
|
|
* @throws XMLDBException
|
|
* happens
|
|
*/
|
|
void setExistTrigger(final Class<?> triggerClass, final String collection, final List<String> events, final Map<String, String> parameters)
|
|
throws XMLDBException {
|
|
|
|
// Arrays.asList(new String[] { "store", "update", "delete" }
|
|
|
|
final StringBuilder conf = new StringBuilder();
|
|
conf.append("<exist:collection xmlns:exist=\"http://exist-db.org/collection-config/1.0\"><exist:triggers>");
|
|
|
|
final String className = triggerClass.getCanonicalName(); // PMD
|
|
|
|
conf.append("<exist:trigger event=\"store,update,remove\" class=\"" + className + "\">");
|
|
if (parameters != null)
|
|
for (final Entry<String, String> entry : parameters.entrySet())
|
|
conf.append("<exist:parameter name=\"" + entry.getKey() + "\" value=\"" + entry.getValue() + "\"/>");
|
|
conf.append("</exist:trigger>");
|
|
|
|
conf.append("</exist:triggers></exist:collection>");
|
|
|
|
log.info(conf.toString());
|
|
createCollection("/db/system/config" + collection, true);
|
|
create(CollectionConfiguration.DEFAULT_COLLECTION_CONFIG_FILE_URI.toString(), "/db/system/config" + collection, conf.toString());
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see eu.dnetlib.xml.database.XMLDatabase#registerTrigger(eu.dnetlib.xml.database.Trigger, java.lang.String)
|
|
*/
|
|
@Override
|
|
public void registerTrigger(final Trigger trigger, final String collection) throws XMLDBException {
|
|
final Map<String, String> params = new HashMap<String, String>();
|
|
params.put("triggerName", trigger.getName());
|
|
|
|
ExistTriggerRegistry.defaultInstance().registerTrigger(trigger.getName(), trigger);
|
|
|
|
setExistTrigger(DelegatingDiffTrigger.class, collection, Arrays.asList("store", "update", "delete" ), params);
|
|
}
|
|
|
|
public Map<String, Trigger> getTriggerConf() {
|
|
return triggerConf;
|
|
}
|
|
|
|
public void setTriggerConf(final Map<String, Trigger> triggerConf) {
|
|
this.triggerConf = triggerConf;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @see eu.dnetlib.xml.database.XMLDatabase#backup()
|
|
*/
|
|
@Override
|
|
public String backup() throws XMLDBException, DatabaseConfigurationException {
|
|
log.info("Starting backup...");
|
|
readLock.lock();
|
|
try {
|
|
verifyBackupDir();
|
|
|
|
String seq = (new SimpleDateFormat("yyyyMMdd-HHmm")).format(new Date());
|
|
|
|
ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(backupDir + "/data-" + seq + ".zip"));
|
|
|
|
FileWriter logFile = new FileWriter(backupDir + "/report-" + seq + ".log");
|
|
logFile.write("Backup started at: " + DateUtils.now_ISO8601() + "\n\n");
|
|
|
|
backup(getRoot().getName(), zip, logFile);
|
|
|
|
logFile.write("\nBackup finished at: " + DateUtils.now_ISO8601() + "\n");
|
|
|
|
logFile.flush();
|
|
logFile.close();
|
|
|
|
zip.flush();
|
|
zip.close();
|
|
|
|
log.info("Backup finished");
|
|
return backupDir;
|
|
} catch (final Exception e) {
|
|
log.error("Backup failed", e);
|
|
throw new XMLDBException(0, "cannot backup", e);
|
|
}
|
|
finally {
|
|
readLock.unlock();
|
|
}
|
|
}
|
|
|
|
|
|
private void verifyBackupDir() {
|
|
File d = new File(backupDir);
|
|
if (!d.exists()) d.mkdirs();
|
|
}
|
|
|
|
private void backup(String coll, ZipOutputStream zip, FileWriter logFile) throws XMLDBException, IOException {
|
|
readLock.lock();
|
|
logFile.write("COLLECTION: " + coll + "\n");
|
|
log.info("Backup of collection " + coll);
|
|
try {
|
|
for (String file : list(coll)) {
|
|
zip.putNextEntry(new ZipEntry(coll + "/" + file + ".xml"));
|
|
Resource resource = getCollection(coll).getResource(file);
|
|
zip.write(resource.getContent().toString().getBytes());
|
|
zip.closeEntry();
|
|
}
|
|
|
|
for (String c : listChildCollections(coll)) {
|
|
backup(coll + "/" + c, zip, logFile);
|
|
}
|
|
}finally {
|
|
readLock.unlock();
|
|
}
|
|
|
|
}
|
|
}
|