package org.wyona.yarep.impl;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.wyona.yarep.core.Map;
import org.wyona.yarep.core.NoSuchNodeException;
import org.wyona.yarep.core.Node;
import org.wyona.yarep.core.Path;
import org.wyona.yarep.core.Repository;
import org.wyona.yarep.core.RepositoryException;
import org.wyona.yarep.core.Storage;
import org.wyona.yarep.core.UID;
/**
* Historically the interfaces {@link org.wyona.yarep.core.Map Map} and {@link org.wyona.yarep.core.Storage Storage} existed before the Repository interface.
* The Repository interface does not require a Map and Storage interface specifically, but in
* order to guarantee backwards compatibility the DefaultRepository has been introduced.
*/
public class DefaultRepository implements Repository {
private static Logger log = LogManager.getLogger(DefaultRepository.class);
protected String id;
protected File configFile;
protected String name;
protected Map map;
protected Storage storage;
private boolean fallback = false;
/**
*
*/
public DefaultRepository() {
}
/**
*
*/
public DefaultRepository(String id, File configFile) throws RepositoryException {
setID(id);
readConfiguration(configFile);
}
/**
* Read respectively load repository configuration
*/
public void readConfiguration(File configFile) throws RepositoryException {
this.configFile = configFile;
DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
Configuration config;
try {
config = builder.buildFromFile(configFile);
name = config.getChild("name", false).getValue();
Configuration pathConfig = config.getChild("paths", false);
fallback = pathConfig.getAttributeAsBoolean("fallback", false);
String pathsClassname = pathConfig.getAttribute("class", null);
if (pathsClassname != null) {
log.debug(pathsClassname);
Class<?> pathsClass = Class.forName(pathsClassname);
map = (Map) pathsClass.newInstance();
} else {
map = (Map) Class.forName("org.wyona.yarep.impl.DefaultMapImpl").newInstance();
//map = new org.wyona.yarep.impl.DefaultMapImpl();
log.info("No paths class specified. Use 'org.wyona.yarep.impl.DefaultMapImpl' as fallback!");
}
map.readConfig(pathConfig, configFile);
Configuration storageConfig = config.getChild("storage", false);
String storageClassname = storageConfig.getAttribute("class", null);
log.debug(storageClassname);
Class<?> storageClass = Class.forName(storageClassname);
storage = (Storage) storageClass.newInstance();
storage.readConfig(storageConfig, configFile);
log.debug(storage.getClass().getName());
} catch (Exception e) {
log.error(e.toString());
throw new RepositoryException("Could not read repository configuration: "
+ e.getMessage(), e);
}
}
/**
*
*/
public String toString() {
return "Default Repository Impl: ID = " + id + ", Configuration-File = " + configFile + ", Name = " + name;
}
/**
* Get repository ID
*/
public String getID() {
return id;
}
/**
* Set repository ID
*/
public void setID(String id) {
this.id = id;
}
/**
* Get repository name
*/
public String getName() {
return name;
}
/**
* Get repository configuration file
*/
public File getConfigFile() {
return configFile;
}
/**
*
*/
public Writer getWriter(Path path) throws RepositoryException {
OutputStream out = getOutputStream(path);
try {
if (out != null) {
return new OutputStreamWriter(getOutputStream(path), "UTF-8");
} else {
return null;
}
} catch (UnsupportedEncodingException e) {
throw new RepositoryException("Could not read path: " + path + ": " + e.getMessage(), e);
}
}
/**
*
*/
public OutputStream getOutputStream(Path path) throws RepositoryException {
UID uid = getUID(path);
if (uid == null) {
if (fallback) {
log.warn("No path to get UID from! Fallback to : " + path);
uid = new UID(path.toString());
map.addSymbolicLink(path, uid);
} else {
uid = map.create(path, org.wyona.yarep.core.NodeType.RESOURCE);
}
}
log.debug(uid.toString());
return storage.getOutputStream(uid, path);
}
/**
*
*/
public Reader getReader(Path path) throws RepositoryException {
try {
return new InputStreamReader(getInputStream(path), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RepositoryException("Could not read path: " + path + ": " + e.getMessage(), e);
}
}
/**
*
*/
public InputStream getInputStream(Path path) throws RepositoryException {
UID uid = null;
if (!existsWithinMap(path)) {
if (fallback) {
log.info("No UID! Fallback to : " + path);
uid = new UID(path.toString());
} else {
throw new NoSuchNodeException(path, this);
}
} else {
uid = getUID(path);
}
if (uid == null) {
log.error("No UID: " + path);
return null;
}
log.debug(uid.toString());
return storage.getInputStream(uid, path);
}
/**
*
*/
public long getLastModified(Path path) throws RepositoryException {
UID uid = getUID(path);
if (uid == null) {
log.error("No UID: " + path);
return -1;
}
return storage.getLastModified(uid, path);
}
/**
*
*/
public long getSize(Path path) throws RepositoryException {
UID uid = getUID(path);
if (uid == null) {
log.error("No UID: " + path);
return -1;
}
return storage.getSize(uid, path);
}
/**
* @return true if node has been deleted, otherwise false
*/
public boolean delete(Path path) throws RepositoryException {
if (log.isDebugEnabled()) log.info("Try to delete: " + path);
if(map.isCollection(path)) {
if (map.getChildren(path).length > 0) {
log.error("Node is a non-empty collection: " + path);
return false;
} else {
log.warn("Node is an empty collection: " + path);
}
}
boolean deletedStorage = false;
UID uid = getUID(path);
if (uid == null) {
if (fallback) {
log.warn("Fallback: " + path);
deletedStorage = storage.delete(new UID(path.toString()), path);
if (!deletedStorage) {
log.error("Could not delete from storage: " + path);
}
} else {
log.error("Neither UID nor Fallback: " + path);
return false;
}
} else {
deletedStorage = storage.delete(uid, path);
if (!deletedStorage) {
log.error("Could not delete from storage: " + path);
}
}
boolean deletedMap = map.delete(path);
if (!deletedMap) {
log.error("Could not delete from map: " + path);
}
return deletedMap && deletedStorage;
}
/**
* @return true if node has been deleted, otherwise false
*/
public boolean delete(Path path, boolean recursive) throws RepositoryException {
Node node = getNode(path.toString());
Node[] children = node.getNodes();
if (recursive && children.length > 0) {;
for (int i = 0; i < children.length; i++) {
if (!delete(new Path(children[i].getPath()), true)) {
throw new RepositoryException("Could not delete node: " + children[i]);
}
}
}
return delete(path);
}
/**
* Not implemented yet
* http://excalibur.apache.org/apidocs/org/apache/excalibur/source/impl/FileSource.html#getValidity()
* http://excalibur.apache.org/apidocs/org/apache/excalibur/source/SourceValidity.html
*/
public void getValidity(Path path) throws RepositoryException {
log.error("TODO: No implemented yet!");
}
/**
* Not implemented yet
* http://excalibur.apache.org/apidocs/org/apache/excalibur/source/impl/FileSource.html#getContentLength()
*/
public void getContentLength(Path path) throws RepositoryException {
log.error("TODO: No implemented yet!");
}
/**
* Not implemented yet
* http://excalibur.apache.org/apidocs/org/apache/excalibur/source/impl/FileSource.html#getURI()
*/
public void getURI(Path path) throws RepositoryException {
log.error("TODO: No implemented yet!");
}
/**
*
*/
public boolean isResource(Path path) throws RepositoryException {
return map.isResource(path);
}
/**
* One might want to discuss what is a collection. A resource for instance could
* also be a collection, but a collection with some default content.
* In the case of JCR there are only nodes and properties!
*/
public boolean isCollection(Path path) throws RepositoryException {
return map.isCollection(path);
}
/**
*
*/
public boolean exists(Path path) throws RepositoryException {
return existsWithinMapAndStorage(path);
}
/**
*
*/
public boolean existsWithinMap(Path path) throws RepositoryException {
return map.exists(path);
}
/**
*
*/
public boolean existsWithinMapAndStorage(Path path) throws RepositoryException {
if (map.exists(path) && storage.exists(getUID(path), path)) {
return true;
} else {
if (fallback && storage.exists(null, path)) {
return true;
} else {
return false;
}
}
}
/**
*
*/
public Path[] getChildren(Path path) throws RepositoryException {
if (fallback) {
log.warn("Repository " + getName() + " has fallback enabled and hence some children might be missed because these only exist within the storage (Path: " + path + ")");
}
// TODO: Order by last modified resp. alphabetical resp. ...
return map.getChildren(path);
}
/**
* Get UID
*
* http://www.ietf.org/rfc/rfc4122.txt
* http://incubator.apache.org/jackrabbit/apidocs/org/apache/jackrabbit/uuid/UUID.html
* http://www.webdav.org/specs/draft-leach-uuids-guids-01.txt
*/
public synchronized UID getUID(Path path) throws RepositoryException {
return map.getUID(path);
}
/**
* Get all revision numbers of the given path.
* @return Array of revision number strings. Newest revision first.
*/
public String[] getRevisions(Path path) throws RepositoryException {
UID uid = getUID(path);
//if (uid == null) throw new NoSuchNodeException("Path not found: " + path);
// fallback?
return storage.getRevisions(uid, path);
}
/**
* Add symbolic link
*/
public void addSymbolicLink(Path target, Path link) throws NoSuchNodeException, RepositoryException {
log.debug("Target: " + target);
UID uid = null;
if (!existsWithinMap(target)) {
if (fallback) {
log.warn("No UID! Fallback to : " + target);
uid = new UID(target.toString());
} else {
throw new NoSuchNodeException(target, this);
}
} else {
uid = getUID(target);
}
log.debug("UID of Target: " + uid);
log.debug("Link: " + link);
map.addSymbolicLink(link, uid);
}
///////////////////////////////////////////////////////////////////////////
// New methods for node based repository
///////////////////////////////////////////////////////////////////////////
/**
* @see org.wyona.yarep.core.Repository#copy(java.lang.String, java.lang.String)
*/
public void copy(String srcPath, String destPath) throws RepositoryException {
// TODO: not implemented yet
log.warn("Not implemented yet.");
}
/**
* @see org.wyona.yarep.core.Repository#existsNode(java.lang.String)
*/
public boolean existsNode(String path) throws RepositoryException {
return existsWithinMapAndStorage(new Path(path));
}
/**
* @see org.wyona.yarep.core.Repository#getNode(java.lang.String)
*/
public Node getNode(String path) throws NoSuchNodeException, RepositoryException {
// strip trailing slash:
if (path.length() > 1 && path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
String uuid;
if (!map.exists(new Path(path))) {
if (fallback) {
log.info("No UID! Fallback to : " + path);
uuid = new UID(path).toString();
} else {
throw new NoSuchNodeException(path, this);
}
} else {
UID uid = map.getUID(new Path(path));
uuid = (uid == null) ? path : uid.toString();
}
return new DummyNode(this, path, uuid);
}
/**
* @see org.wyona.yarep.core.Repository#getNodeByUUID(java.lang.String)
*/
public Node getNodeByUUID(String uuid) throws NoSuchNodeException, RepositoryException {
// TODO: not implemented yet
log.warn("Not implemented yet.");
return null;
}
/**
* @see org.wyona.yarep.core.Repository#getRootNode()
*/
public Node getRootNode() throws RepositoryException {
return getNode("/");
}
/**
* @see org.wyona.yarep.core.Repository#move(java.lang.String, java.lang.String)
*/
public void move(String srcPath, String destPath) throws RepositoryException {
// TODO: not implemented yet
log.warn("Not implemented yet.");
}
// implementation specific methods:
public Map getMap() {
return this.map;
}
public Storage getStorage() {
return this.storage;
}
/**
*
*/
public void close() throws RepositoryException {
log.warn("Not implemented!");
}
/**
* Search content
*/
public Node[] search(String query) throws RepositoryException {
log.error("Not implemented yet!");
return null;
}
public Node[] searchProperty(String pName, String pValue, String path) throws RepositoryException {
throw new RepositoryException("Not implemented yet!");
}
/**
* TODO: not implemented yet.
*/
public org.wyona.yarep.core.search.Indexer getIndexer() {
log.warn("TODO: not implemented yet.");
return null;
}
/**
* TODO: not implemented yet.
*/
public org.wyona.yarep.core.search.Searcher getSearcher() {
log.warn("TODO: not implemented yet.");
return null;
}
/**
* @see org.wyona.yarep.core.Repository#importNode(String, String, Repository)
*/
public boolean importNode(String destPath, String srcPath, Repository srcRepository) throws RepositoryException {
try {
// Copy content of node
Node srcNode = srcRepository.getNode(srcPath);
if (existsNode(destPath)) {
log.warn("Node '" + destPath + "' already exists and will be overwritten!");
}
if (srcNode.isCollection()) {
log.warn("This seems to be a collection: " + srcNode.getPath());
Node destNode = org.wyona.yarep.util.YarepUtil.addNodes(this, destPath, org.wyona.yarep.core.NodeType.COLLECTION);
copyCollectionIntrinsicData(srcNode, destNode);
// TODO: What about properties?!
return true;
}
Node destNode = org.wyona.yarep.util.YarepUtil.addNodes(this, destPath, org.wyona.yarep.core.NodeType.RESOURCE);
OutputStream os = destNode.getOutputStream();
IOUtils.copy(srcNode.getInputStream(), os);
os.close();
log.info("Import of revisions and meta/properties ... (src: " + srcPath + ", dest: " + destPath + ")");
/*
// Copy revisions of node
Revision[] revisions = srcNode.getRevisions();
for (int i = 0; i < revisions.length; i++) {
log.info("Copy revision: " + revisions[i].getRevisionName());
File revisionContentFile = ((VirtualFileSystemNode) destNode).getRevisionContentFile(revisions[i].getRevisionName());
if (!new File(revisionContentFile.getParent()).exists())
new File(revisionContentFile.getParent()).mkdirs();
FileOutputStream out = new FileOutputStream(revisionContentFile);
IOUtils.copy(revisions[i].getInputStream(), out);
out.close();
// Copy meta/properties of revision
File destRevisionMetaFile = ((VirtualFileSystemNode) destNode).getRevisionMetaFile(revisions[i].getRevisionName());
copyProperties(revisions[i], destRevisionMetaFile);
}
// Copy meta/properties of node
File metaFile = ((VirtualFileSystemNode) destNode).getMetaFile();
copyProperties(srcNode, metaFile);
*/
} catch (Exception e) {
throw new RepositoryException(e);
}
return true;
}
/**
* Copy properties of source node to destination meta file (also see VirtualFileSystemNode#saveProperties())
* @param srcNode Source node containing properties
* @param destMetaFile Destination meta file where properties of source node shall be copied to
private boolean copyProperties(Node srcNode, File destMetaFile) throws Exception {
if (!new File(destMetaFile.getParent()).exists())
new File(destMetaFile.getParent()).mkdirs();
log.info("Copy properties: " + destMetaFile);
java.io.PrintWriter writer = new java.io.PrintWriter(new FileOutputStream(destMetaFile));
org.wyona.yarep.core.Property[] properties = srcNode.getProperties();
for (int i = 0; i < properties.length; i++) {
writer.println(properties[i].getName() + "<" + org.wyona.yarep.core.PropertyType.getTypeName(properties[i].getType()) + ">:" + properties[i].getValueAsString());
}
writer.flush();
writer.close();
return true;
}
*/
private boolean copyCollectionIntrinsicData(Node srcNode, Node destNode) throws Exception {
//XXX: ugly but we have to cope with implementations that do not allow to get the OutputStream from a collection
OutputStream os = null;
try {
os = destNode.getOutputStream();
} catch (RepositoryException e) {
if (e.getCause() instanceof FileNotFoundException) {
if (! destNode.isCollection()) {
throw e;
}
log.warn(e.getMessage()); // we log the message as there may as well be a real problem with the directory there
}
}
if (os != null) {
// no need to copy anything from collections' data streams themselves as of now, but
// we need to at least close the stream for save-related side-effects to happen:
os.close();
}
return true;
}
}