/*
* eXist Open Source Native XML Database
* Copyright (C) 2004-2009 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 program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Id$
*/
package org.exist.xquery.functions.xmldb;
import org.apache.log4j.Logger;
import java.io.File;
import org.exist.dom.QName;
import org.exist.util.DirectoryScanner;
import org.exist.util.MimeTable;
import org.exist.util.MimeType;
import org.exist.xmldb.EXistResource;
import org.exist.xquery.Cardinality;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.FunctionReturnSequenceType;
import org.exist.xquery.value.FunctionParameterSequenceType;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceIterator;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.StringValue;
import org.exist.xquery.value.Type;
import org.exist.xquery.value.ValueSequence;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.Resource;
import org.xmldb.api.base.XMLDBException;
/**
* @author wolf
*/
public class XMLDBLoadFromPattern extends XMLDBAbstractCollectionManipulator {
protected static final Logger logger = Logger.getLogger(XMLDBLoadFromPattern.class);
protected final static QName FUNCTION_NAME = new QName("store-files-from-pattern", XMLDBModule.NAMESPACE_URI, XMLDBModule.PREFIX);
protected final static String FUNCTION_DESCRIPTION = "Stores new resources into the database. Resources are read from the server's " +
"file system, using file patterns. " +
"The function returns a sequence of all document paths added " +
"to the db. These can be directly passed to fn:doc() to retrieve the document(s).";
protected final static SequenceType PARAM_COLLECTION = new FunctionParameterSequenceType("collection-uri", Type.STRING, Cardinality.EXACTLY_ONE, "The collection-uri where resources should be stored. " + XMLDBModule.COLLECTION_URI);
protected final static SequenceType PARAM_FS_DIRECTORY = new FunctionParameterSequenceType("directory", Type.STRING, Cardinality.EXACTLY_ONE, "The directory in the file system from where the files are read.");
// fixit! - security - we should say some words about sanity
// DBA role should be required for anything short of chroot/jail
// easily setup per installation/execution host for each function. /ljo
protected final static SequenceType PARAM_FS_PATTERN = new FunctionParameterSequenceType("pattern", Type.STRING, Cardinality.ONE_OR_MORE, "The file matching pattern. Based on code from Apache's Ant, thus following the same conventions. For example: *.xml matches any file ending with .xml in the current directory, **/*.xml matches files in any directory below the current one");
protected final static SequenceType PARAM_MIME_TYPE = new FunctionParameterSequenceType("mime-type", Type.STRING, Cardinality.EXACTLY_ONE, "If the mime-type is something other than 'text/xml' or 'application/xml', the resource will be stored as a binary resource.");
protected static final SequenceType PARAM_PRESERVE_STRUCTURE = new FunctionParameterSequenceType("preserve-structure", Type.BOOLEAN, Cardinality.EXACTLY_ONE, "If preserve-structure is true(), the filesystem directory structure will be mirrored in the collection. Otherwise all the matching resources, including the ones in sub-directories, will be stored in the collection given in the first argument flatly.");
protected static final FunctionReturnSequenceType RETURN_TYPE = new FunctionReturnSequenceType(Type.STRING, Cardinality.ZERO_OR_MORE, "the sequence of document paths");
public final static FunctionSignature signatures[] = {
new FunctionSignature(
FUNCTION_NAME,
FUNCTION_DESCRIPTION,
new SequenceType[] { PARAM_COLLECTION, PARAM_FS_DIRECTORY, PARAM_FS_PATTERN },
RETURN_TYPE
),
new FunctionSignature(
FUNCTION_NAME,
FUNCTION_DESCRIPTION,
new SequenceType[] { PARAM_COLLECTION, PARAM_FS_DIRECTORY, PARAM_FS_PATTERN, PARAM_MIME_TYPE },
RETURN_TYPE
),
new FunctionSignature(
FUNCTION_NAME,
FUNCTION_DESCRIPTION,
new SequenceType[] { PARAM_COLLECTION, PARAM_FS_DIRECTORY, PARAM_FS_PATTERN, PARAM_MIME_TYPE, PARAM_PRESERVE_STRUCTURE },
RETURN_TYPE
)
};
public XMLDBLoadFromPattern(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}
/* (non-Javadoc)
* @see org.exist.xquery.functions.xmldb.XMLDBAbstractCollectionManipulator#evalWithCollection(org.xmldb.api.base.Collection, org.exist.xquery.value.Sequence[], org.exist.xquery.value.Sequence)
*/
protected Sequence evalWithCollection(Collection collection, Sequence[] args, Sequence contextSequence)
throws XPathException {
File baseDir = new File(args[1].getStringValue());
logger.debug("Loading files from directory: " + baseDir);
//determine resource type - xml or binary?
String mimeType = MimeType.XML_TYPE.getName();
String resourceType = "XMLResource";
if(getSignature().getArgumentCount() > 3) {
mimeType = args[3].getStringValue();
MimeType mime = MimeTable.getInstance().getContentType(mimeType);
if(mime != null && !mime.isXMLType())
resourceType = "BinaryResource";
}
//keep the directory structure?
boolean keepDirStructure = false;
if(getSignature().getArgumentCount() == 5)
keepDirStructure = args[4].effectiveBooleanValue();
ValueSequence stored = new ValueSequence();
//store according to each pattern
Sequence patterns = args[2];
for(SequenceIterator i = patterns.iterate(); i.hasNext(); )
{
//get the files to store
String pattern = i.nextItem().getStringValue();
File[] files = DirectoryScanner.scanDir(baseDir, pattern);
logger.debug("Found: " + files.length);
Collection col = collection;
String relDir, prevDir = null;
for(int j = 0; j < files.length; j++) {
try {
logger.debug(files[j].getAbsolutePath());
String relPath = files[j].toString().substring(baseDir.toString().length());
int p = relPath.lastIndexOf(File.separatorChar);
if(p >= 0) {
relDir = relPath.substring(0, p);
relDir = relDir.replace(File.separatorChar, '/');
} else {
relDir = relPath;
}
if(keepDirStructure && (prevDir == null || (!relDir.equals(prevDir)))) {
col = createCollectionPath(collection, relDir);
prevDir = relDir;
}
//TODO : these probably need to be encoded and checked for right mime type
Resource resource = col.createResource(files[j].getName(), resourceType);
resource.setContent(files[j]);
((EXistResource) resource).setMimeType(mimeType);
col.storeResource(resource);
//TODO : use dedicated function in XmldbURI
stored.add(new StringValue(col.getName() + "/" + resource.getId()));
} catch(XMLDBException e) {
logger.error("Could not store file " + files[j].getAbsolutePath() + ": " + e.getMessage());
}
}
}
return stored;
}
}