/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2015 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.exist.storage.md;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.exist.Database;
import org.exist.EXistException;
import org.exist.collections.Collection;
import org.exist.dom.persistent.DocumentImpl;
import org.exist.dom.QName;
import org.exist.security.PermissionDeniedException;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.MetaStreamListener;
import org.exist.xmldb.XmldbURI;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.persist.*;
import org.w3c.dom.Document;
/**
* @author <a href="mailto:shabanovd@gmail.com">Dmitriy Shabanov</a>
*
*/
public class MetaDataImpl extends MetaData {
protected static MetaDataImpl inst = null;
private Environment env;
private EntityStore store;
private PrimaryIndex<String, MetasImpl> docByUUID;
private SecondaryIndex<String, String, MetasImpl> uriToDoc;
private PrimaryIndex<String, MetaImpl> metadataByUUID;
private SecondaryIndex<String, String, MetaImpl> metadata;
private SecondaryIndex<String, String, MetaImpl> keyToMeta;
private SecondaryIndex<String, String, MetaImpl> valueToMeta;
public MetaDataImpl(Database db) throws IOException {
LOG.debug("initializing metadata storage");
Path folder = db.getStoragePlace();
Path dataDirectory = folder.resolve("metadata");
Files.createDirectories(dataDirectory);
LOG.debug("folder created ... ");
EnvironmentConfig envConfig = new EnvironmentConfig();
envConfig.setAllowCreate(true);
envConfig.setTransactional(false);
env = new Environment(dataDirectory.toFile(), envConfig);
LOG.debug("environment ... ");
StoreConfig storeConfig = new StoreConfig();
storeConfig.setAllowCreate(true);
storeConfig.setTransactional(false);
store = new EntityStore(env, "md", storeConfig);
LOG.debug("entity store ... ");
docByUUID = store.getPrimaryIndex(String.class, MetasImpl.class);
uriToDoc = store.getSecondaryIndex(docByUUID, String.class, "uri");
metadataByUUID = store.getPrimaryIndex(String.class, MetaImpl.class);
metadata = store.getSecondaryIndex(metadataByUUID, String.class, "object");
keyToMeta = store.getSecondaryIndex(metadataByUUID, String.class, "key");
valueToMeta = store.getSecondaryIndex(metadataByUUID, String.class, "value");
LOG.debug("ready ... ");
MetaDataImpl.inst = this;
MetaData.inst = this;
LOG.debug("done.");
}
public String getId() {
return MDStorageManager.PREFIX;
}
public DocumentImpl getDocument(String uuid) throws EXistException, PermissionDeniedException {
MetasImpl ms = docByUUID.get(uuid);
if (ms == null) return null;
final BrokerPool db = BrokerPool.getInstance();
try(final DBBroker broker = db.getBroker()) {
XmldbURI uri = XmldbURI.create(ms.uri);
Collection col = broker.getCollection(uri.removeLastSegment());
if (col != null) {
return col.getDocument(broker, uri.lastSegment());
}
return null;
}
}
private Metas getMetas(String uuid) {
return docByUUID.get(uuid);
}
public Collection getCollection(String uuid) throws EXistException, PermissionDeniedException {
MetasImpl ms = docByUUID.get(uuid);
if (ms == null) return null;
final BrokerPool db = BrokerPool.getInstance();
try(final DBBroker broker = db.getBroker()) {
XmldbURI uri = XmldbURI.create(ms.uri);
return broker.getCollection(uri);
}
}
private Metas _addMetas(Document doc) {
MetasImpl d = new MetasImpl(doc);
docByUUID.put(d);
if (LOG.isDebugEnabled()) {
LOG.debug("addMetas " + d.getUUID() + " " + (doc instanceof DocumentImpl ? ((DocumentImpl)doc).getURI() : ""));
}
return d;
}
private Metas _addMetas(Collection col) {
MetasImpl d = new MetasImpl(col.getURI());
docByUUID.put(d);
if (LOG.isDebugEnabled())
LOG.debug("addMetas "+d.getUUID()+" "+col.getURI());
return d;
}
protected Metas _addMetas(String uri, String uuid) {
MetasImpl d = new MetasImpl(uri, uuid);
docByUUID.put(d);
if (LOG.isDebugEnabled())
LOG.debug("addMetas "+uuid+" "+uri);
return d;
}
public Metas replaceMetas(XmldbURI uri, String uuid) {
Metas metas = getMetas(uri, false);
if (metas != null)
delMetas(metas);
MetasImpl d = new MetasImpl(uri.toString(), uuid);
docByUUID.put(d);
if (LOG.isDebugEnabled())
LOG.debug("addMetas "+uuid+" "+uri);
return d;
}
public Metas addMetas(Document doc) {
Metas _d = getMetas(doc instanceof DocumentImpl ? ((DocumentImpl)doc).getURI() : null, false);
if (_d != null)
return _d;
return _addMetas(doc);
}
public Metas addMetas(Collection col) {
Metas _c = getMetas(col.getURI(), false);
if (_c != null)
return _c;
return _addMetas(col);
}
public Metas getMetas(Document doc) {
return getMetas(doc instanceof DocumentImpl ? ((DocumentImpl) doc).getURI() : null, true);
}
public Metas getMetas(XmldbURI uri) {
return getMetas(uri, true);
}
public Metas getMetas(XmldbURI uri, boolean addIfmissing) {
if (LOG.isDebugEnabled())
LOG.debug("getMetas "+uri+" ");
EntityJoin<String, MetasImpl> join = new EntityJoin<String, MetasImpl>(docByUUID);
join.addCondition(uriToDoc, uri.toString());
ForwardCursor<MetasImpl> entities = join.entities();
try {
MetasImpl v = entities.next();
if (LOG.isDebugEnabled()) {
MetasImpl n;
while ((n = entities.next()) != null) {
LOG.error("ERROR "+n.getUUID()+" "+uri);
}
}
// System.out.println(v);
//
//
// //this possible a bug, but NPE should be avoided (TODO: write lock required)
// if (addIfmissing && v == null) {
//
// //check that document exist
// BrokerPool pool = null;
// DBBroker broker = null;
// try {
// pool = BrokerPool.getInstance();
// broker = pool.get(null);
//
// Collection col = broker.getCollection(uri.removeLastSegment());
// if (col != null) {
// DocumentImpl _doc = col.getDocument(broker, uri.lastSegment());
//
// if (_doc != null) {
// LOG.error("metas for document "+uri+" get lost!");
// return _addMetas(_doc);
// }
//
// }
// } catch (Exception e) {
// LOG.error(e);
// return null;
//
// } finally {
// if (pool != null)
// pool.release(broker);
// }
//
// return null;
// }
return v;
} finally {
entities.close();
}
}
public void delMetas(XmldbURI uri) {
Metas d = getMetas(uri, false);
if (d != null) {
if (LOG.isDebugEnabled())
LOG.debug("delete metas "+d.getUUID()+" "+uri);
delMetas(d);
} else {
if (LOG.isDebugEnabled())
LOG.debug("delete metas NULL "+uri);
}
}
protected void delMetas(Metas d) {
EntityCursor<MetaImpl> sub = metadata.subIndex(d.getUUID()).entities();
try {
for (MetaImpl m : sub)
metadataByUUID.delete(m.getUUID());
} finally {
sub.close();
}
docByUUID.delete(d.getUUID());
indexRemoveMetas(d);
}
protected MetaImpl addMeta(Metas doc, String key, Object value) {
MetaImpl m = new MetaImpl(doc.getUUID(), key, value);
metadataByUUID.put(m);
indexMetas(doc);
return m;
}
protected Meta _addMeta(Metas doc, String uuid, String key, String value) {
MetaImpl m = new MetaImpl(doc.getUUID(), uuid, key, value);
metadataByUUID.put(m);
indexMetas(doc);
return m;
}
protected MetaImpl setMeta(MetaImpl meta) {
metadataByUUID.put(meta);
return meta;
}
protected Meta addMeta(Meta meta) {
if (meta instanceof MetaImpl) {
return setMeta((MetaImpl) meta);
}
throw new RuntimeException("unsupported operation ["+meta+"]");
}
protected Meta getMeta(Metas doc, String key) {
//System.out.println("key = "+key);
EntityCursor<MetaImpl> sub = metadata.subIndex(doc.getUUID()).entities();
try {
for (MetaImpl m : sub)
if (m.getKey().equals(key))
return m;
} finally {
sub.close();
}
return null;
}
public void streamMetas(DocumentImpl doc, MetaStreamListener listener) {
Metas metas = getMetas(doc);
if (metas == null)
return;
EntityCursor<MetaImpl> sub = metadata.subIndex(metas.getUUID()).entities();
try {
for (MetaImpl m : sub)
listener.metadata(new QName(m.getKey(), MDStorageManager.NAMESPACE_URI, MDStorageManager.PREFIX) , m.getValue());
} finally {
sub.close();
}
}
public Meta getMeta(String uuid) {
return metadataByUUID.get(uuid);
}
protected void delMeta(String docUUID, String key) {
//System.out.println("key = "+key);
EntityCursor<MetaImpl> sub = metadata.subIndex(docUUID).entities();
try {
for (MetaImpl m : sub)
if (m.getKey().equals(key)) {
sub.delete();
}
} finally {
sub.close();
}
indexMetas(getMetas(docUUID));
}
public List<DocumentImpl> matchDocuments(String key, String value) throws EXistException {
EntityJoin<String, MetaImpl> join = new EntityJoin<String, MetaImpl>(metadataByUUID);
join.addCondition(keyToMeta, key);
join.addCondition(valueToMeta, value);
ForwardCursor<MetaImpl> entities = join.entities();
try {
List<DocumentImpl> list = new ArrayList<DocumentImpl>();
for (MetaImpl entity : entities) {
try {
list.add(getDocument(entity.getObject()));
} catch (PermissionDeniedException ex) {
//ignore
}
}
return list;
} finally {
entities.close();
}
}
public List<DocumentImpl> matchDocumentsByKey(String key) throws EXistException {
EntityJoin<String, MetaImpl> join = new EntityJoin<String, MetaImpl>(metadataByUUID);
join.addCondition(keyToMeta, key);
ForwardCursor<MetaImpl> entities = join.entities();
try {
List<DocumentImpl> list = new ArrayList<DocumentImpl>();
for (MetaImpl entity : entities) {
try {
list.add(getDocument(entity.getObject()));
} catch (PermissionDeniedException ex) {
//ignore
}
}
return list;
} finally {
entities.close();
}
}
public List<DocumentImpl> matchDocumentsByValue(String value) throws EXistException {
EntityJoin<String, MetaImpl> join = new EntityJoin<String, MetaImpl>(metadataByUUID);
join.addCondition(valueToMeta, value);
ForwardCursor<MetaImpl> entities = join.entities();
try {
List<DocumentImpl> list = new ArrayList<DocumentImpl>();
for (MetaImpl entity : entities) {
try {
list.add(getDocument(entity.getObject()));
} catch (PermissionDeniedException ex) {
//ignore
}
}
return list;
} finally {
entities.close();
}
}
public void close() {
store.close();
env.close();
}
public void sync() {
store.sync();
env.sync();
}
// public void moveMetas(Metas metas, DocumentImpl doc) {
// if (metas instanceof MetasImpl) {
// MetasImpl ms = (MetasImpl) metas;
// ms.update(doc);
//
// //docByUUID.put(ms);
//
// return;
// }
// throw new RuntimeException("unsupported operation ["+metas+"]");
// }
public void moveMetas(XmldbURI oldUri, XmldbURI newUri) {
MetasImpl ms = (MetasImpl)getMetas(oldUri);
if (ms != null) {
ms.uri = newUri.toString();
docByUUID.put(ms);
ms = (MetasImpl)getMetas(oldUri);
} else {
throw new RuntimeException("Metas NULL: " + oldUri + " in moveMetas");
//LOG.warn("Metas NULL for document: " + doc.getURI() + " in moveMetas");
}
// Map<String, String> map = new HashMap<String, String>();
//
// if(ms != null)
// {
// EntityCursor<MetaImpl> sub = metadata.subIndex(ms.getUUID()).entities();
// try {
// for (MetaImpl m : sub)
// map.put(m.getKey(), m.getValue());
//
// } finally {
// sub.close();
// }
// }
//
// delMetas(ms);
//
// MetasImpl newMs = new MetasImpl((MetasImpl)ms, uri);
return;
}
// public void updateMetas(XmldbURI oldD, DocumentImpl newD) {
// MetasImpl ms = (MetasImpl)getMetas(oldD);
// ms.update(newD);
// }
public void copyMetas(XmldbURI oldDoc, DocumentImpl newDoc) {
MetasImpl ms = (MetasImpl)getMetas(oldDoc);
MetasImpl newMs = (MetasImpl) addMetas(newDoc);
if (ms != null) {
EntityCursor<MetaImpl> sub = metadata.subIndex(ms.getUUID()).entities();
try {
for (MetaImpl m : sub)
newMs.put(m.getKey(), m.getValue());
} finally {
sub.close();
}
}
}
public void copyMetas(XmldbURI oldDoc, Collection newCol) {
MetasImpl ms = (MetasImpl)getMetas(oldDoc);
MetasImpl newMs = (MetasImpl) addMetas(newCol);
if (ms != null) {
EntityCursor<MetaImpl> sub = metadata.subIndex(ms.getUUID()).entities();
try {
for (MetaImpl m : sub) {
newMs.put(m.getKey(), m.getValue());
}
} finally {
sub.close();
}
}
}
public EntityCursor<MetaImpl> getMetaKeys(Metas doc) {
return metadata.subIndex(doc.getUUID()).entities();
}
@Override
public XmldbURI UUIDtoURI(String uuid) {
MetasImpl ms = docByUUID.get(uuid);
if (ms == null) return null;
return XmldbURI.create(ms.uri);
}
@Override
public String URItoUUID(XmldbURI uri) {
Metas d = getMetas(uri, false);
if (d == null) return null;
return d.getUUID();
}
//lucene index methods
public void indexMetas(Metas metas) {
//XXX: update lucene!!!
// PlugToLucene plug = new PlugToLucene(this);
// plug.addMetas(metas);
}
private void indexRemoveMetas(Metas metas) {
//XXX: update lucene!!!
// PlugToLucene plug = new PlugToLucene(this);
// plug.removeMetas(metas);
}
// public NodeImpl search(String queryText, List<String> toBeMatchedURIs) throws XPathException {
// return (new PlugToLucene(this)).search(queryText, toBeMatchedURIs);
// }
//
// public List<String> searchDocuments(String queryText, List<String> toBeMatchedURIs) throws XPathException {
// return (new PlugToLucene(this)).searchDocuments(queryText, toBeMatchedURIs);
// }
}