package org.wyona.yarep.impl.repo.fs; import java.io.File; 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 java.util.ArrayList; import java.util.List; import javax.swing.plaf.ListUI; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; import org.apache.log4j.Category; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.analysis.WhitespaceAnalyzer; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.search.Searcher; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.Hits; import org.apache.lucene.queryParser.QueryParser; import org.wyona.commons.io.FileUtil; 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.Revision; import org.wyona.yarep.core.Storage; import org.wyona.yarep.core.UID; import org.wyona.yarep.core.search.Indexer; /** * Node based file system repository. * A node of type resource is stored as a file. * A node of type collection is stored as a directory. * Each resource has a myresource.yarep directory which contains: * <ul> * <li>A meta file containing the properties</li> * <li>A revisions directory containing the revisions</li> * </ul> * This directory and the meta file will be created automatically when a node is * accessed which does not have such a .yarep directory yet. */ public class FileSystemRepository implements Repository { private static Category log = Category.getInstance(FileSystemRepository.class); protected String id; protected File configFile; protected String name; protected Map map; protected Storage storage; private boolean fallback = false; // Search and index private File searchIndexSrcFile = null; private File propertiesSearchIndexFile = null; private File fulltextSearchIndexFile = null; private Analyzer analyzer = null; private Analyzer whitespaceAnalyzer = null; private String PROPERTIES_INDEX_DIR = "properties"; private String FULLTEXT_INDEX_DIR = "fulltext"; /** * */ public FileSystemRepository() { } /** * */ public FileSystemRepository(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(); } map.readConfig(pathConfig, configFile); this.contentDir = new File(config.getChild("content", false).getAttribute("src")); if (!this.contentDir.isAbsolute()) { this.contentDir = FileUtil.file(configFile.getParent(), this.contentDir.toString()); } log.info("Content dir: " + this.contentDir); Configuration metaDirConfig = config.getChild("meta", false); if (metaDirConfig != null) { this.metaDir = new File(metaDirConfig.getAttribute("src")); if (!this.metaDir.isAbsolute()) { this.metaDir = FileUtil.file(configFile.getParent(), this.metaDir.toString()); } log.info("Meta dir: " + this.metaDir); } Configuration searchIndexConfig = config.getChild("search-index", false); if (searchIndexConfig != null) { searchIndexSrcFile = new File(searchIndexConfig.getAttribute("src", "index")); } else { log.warn("Search index directory has not been configured! Please check " + configFile); searchIndexSrcFile = new File("search-index-default"); } if (!searchIndexSrcFile.isAbsolute()) { searchIndexSrcFile = FileUtil.file(configFile.getParent(), searchIndexSrcFile.toString()); } analyzer = new StandardAnalyzer(); // TODO: For search within properties the WhitespaceAnalyzer is used because the StandardAnalyzer doesn't accept resp. misinterprets escaped query strings, e.g. 03\:07\- ... whitespaceAnalyzer = new WhitespaceAnalyzer(); // Create a lucene search index (for fulltext) if it doesn't exist yet if (!searchIndexSrcFile.isDirectory()) { fulltextSearchIndexFile = new File(searchIndexSrcFile, FULLTEXT_INDEX_DIR); IndexWriter indexWriter = new IndexWriter(fulltextSearchIndexFile.getAbsolutePath(), getAnalyzer(), true); indexWriter.close(); } else { if (new File(searchIndexSrcFile, FULLTEXT_INDEX_DIR).isDirectory()) { fulltextSearchIndexFile = new File(searchIndexSrcFile, FULLTEXT_INDEX_DIR); } else { fulltextSearchIndexFile = searchIndexSrcFile; } } // Create properties index dir subdirectory in order to save the lucene index for searching on properties propertiesSearchIndexFile = new File(searchIndexSrcFile, PROPERTIES_INDEX_DIR); if (!propertiesSearchIndexFile.isDirectory()) { IndexWriter indexWriter = new IndexWriter(propertiesSearchIndexFile.getAbsolutePath(), getWhitespaceAnalyzer(), true); indexWriter.close(); } } catch (Exception e) { log.error(e.toString()); throw new RepositoryException("Could not read repository configuration: " + e.getMessage(), e); } } /** * */ public String toString() { return "Repository: 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 void addSymbolicLink(Path target, Path link) throws RepositoryException { log.warn("Not implemented."); } public boolean delete(Path path) throws RepositoryException { getNode(path.toString()).delete(); return true; } /** * @return true if node has been deleted, otherwise false */ public boolean delete(Path path, boolean recursive) throws RepositoryException { log.warn("Not implemented yet!"); if (recursive) throw new RepositoryException("Not implemented yet"); return delete(path); } public boolean exists(Path path) throws RepositoryException { return existsNode(path.toString()); } /** * Get paths of children */ public Path[] getChildren(Path path) throws RepositoryException { Node node = getNode(path.toString()); Node[] childNodes = node.getNodes(); Path[] childPaths = new Path[childNodes.length]; for (int i=0; i<childNodes.length; i++) { childPaths[i] = new Path(childNodes[i].getPath()); } return childPaths; } public void getContentLength(Path path) throws RepositoryException { log.warn("Not implemented."); } public InputStream getInputStream(Path path) throws RepositoryException { return getNode(path.toString()).getInputStream(); } public long getLastModified(Path path) throws RepositoryException { return getNode(path.toString()).getLastModified(); } public OutputStream getOutputStream(Path path) throws RepositoryException { return getNode(path.toString()).getOutputStream(); } public Reader getReader(Path path) throws RepositoryException { try { return new InputStreamReader(getNode(path.toString()).getInputStream(), "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RepositoryException(e.getMessage(), e); } } public String[] getRevisions(Path path) throws RepositoryException { Node node = getNode(path.toString()); Revision[] revisions = node.getRevisions(); String[] revisionNames = new String[revisions.length]; for (int i=0; i<revisions.length; i++) { revisionNames[i] = revisions[i].getName(); } return revisionNames; } public long getSize(Path path) throws RepositoryException { return getNode(path.toString()).getSize(); } public UID getUID(Path path) throws RepositoryException { log.warn("Not implemented."); return null; } public void getURI(Path path) throws RepositoryException { log.warn("Not implemented."); } public void getValidity(Path path) throws RepositoryException { log.warn("Not implemented."); } public Writer getWriter(Path path) throws RepositoryException { try { return new OutputStreamWriter(getNode(path.toString()).getOutputStream(), "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RepositoryException(e.getMessage(), e); } } public boolean isCollection(Path path) throws RepositoryException { return getNode(path.toString()).isCollection(); } public boolean isResource(Path path) throws RepositoryException { return getNode(path.toString()).isResource(); } /////////////////////////////////////////////////////////////////////////// // New methods for node based repository /////////////////////////////////////////////////////////////////////////// protected File contentDir; protected File metaDir; /** * @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 { // strip trailing slash: if (path.length() > 1 && path.endsWith("/")) { path = path.substring(0, path.length() - 1); } if (map.exists(new Path(path))) { return true; } else if (fallback) { log.info("No UID! Fallback to : " + path); File file = new File(contentDir + path); return file.exists(); } else { return false; } //return map.exists(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); if (!(new File(contentDir + path)).exists()) { throw new NoSuchNodeException(path, this); } 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(); } //String uuid = map.getUID(new Path(path)).toString(); return new FileSystemNode(this, path, uuid); } /** * @see org.wyona.yarep.core.Repository#getNodeByUUID(java.lang.String) */ public Node getNodeByUUID(String uuid) throws NoSuchNodeException, RepositoryException { //String path = map.getPath(uuid); //return new FileSystemNode(this, path, uuid); // 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 { //map.move(srcPath, destPath); // TODO: not implemented yet log.warn("Not implemented yet."); } // implementation specific methods: public File getContentDir() { return this.contentDir; } public Map getMap() { return this.map; } /** * */ public void close() throws RepositoryException { log.warn("Not implemented!"); } /** * Search content */ public Node[] search(String query) throws RepositoryException { try { Searcher searcher = new IndexSearcher(getFulltextSearchIndexFile().getAbsolutePath()); if (searcher != null) { try { Query luceneQuery = new QueryParser("_FULLTEXT", analyzer).parse(query); Hits hits = searcher.search(luceneQuery); log.info("Number of matching documents: " + hits.length()); java.util.Vector results = new java.util.Vector(); for (int i = 0; i < hits.length(); i++) { try { results.addElement(getNode(hits.doc(i).getField("_PATH").stringValue())); } catch (NoSuchNodeException nsne) { log.warn("Found within search index, but no such node within repository: " + hits.doc(i).getField("_PATH").stringValue()); } } Node[] res = new Node[results.size()]; for (int i = 0; i < res.length; i++) { res[i] = (Node) results.elementAt(i); } return res; } catch (Exception e) { log.error(e, e); throw new RepositoryException(e.getMessage()); } } else { log.warn("No search index seems to be configured!"); } } catch (Exception e) { log.error(e, e); throw new RepositoryException(e.getMessage()); } return null; } /** * Search properties within subtree */ public Node[] searchProperty(String pName, String pValue, String path) throws RepositoryException { try { Searcher searcher = new IndexSearcher(getPropertiesSearchIndexFile().getAbsolutePath()); if (searcher != null) { try { Query luceneQuery = new QueryParser(pName, whitespaceAnalyzer).parse(QueryParser.escape(pValue)); Hits hits = searcher.search(luceneQuery); log.info("Number of matching documents: " + hits.length()); List results = new ArrayList(); for (int i = 0; i < hits.length(); i++) { try { String resultPath = hits.doc(i).getField("_PATH").stringValue(); // subtree filter if (resultPath.startsWith(path)) { results.add(getNode(resultPath)); } } catch (NoSuchNodeException nsne) { log.warn("Found within search index, but no such node within repository: " + hits.doc(i).getField("_PATH").stringValue()); } } return (Node[])results.toArray(new Node[results.size()]); } catch (Exception e) { log.error(e, e); throw new RepositoryException(e.getMessage()); } } } catch (Exception e) { log.error(e, e); throw new RepositoryException(e.getMessage()); } return null; } /** * Get yarep meta directory */ public File getYarepMetaDir() { return this.metaDir; } /** * */ public File getSearchIndexFile() { return searchIndexSrcFile; } public File getPropertiesSearchIndexFile() { return propertiesSearchIndexFile; } public File getFulltextSearchIndexFile() { return fulltextSearchIndexFile; } /** * */ public Analyzer getAnalyzer() { return analyzer; } public Analyzer getWhitespaceAnalyzer() { return whitespaceAnalyzer; } /** * */ public boolean isFallbackEnabled() { return fallback; } /** * 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 { // TODO: Implement importNode log.warn("Not implemented yet!"); return false; } }