org.apache.jackrabbit.webdav.simple.ResourceConfig Maven / Gradle / Ivy
Show all versions of jackrabbit-jcr-server Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.webdav.simple;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.jackrabbit.server.io.CopyMoveHandler;
import org.apache.jackrabbit.server.io.CopyMoveManager;
import org.apache.jackrabbit.server.io.CopyMoveManagerImpl;
import org.apache.jackrabbit.server.io.DefaultIOManager;
import org.apache.jackrabbit.server.io.DeleteManager;
import org.apache.jackrabbit.server.io.DeleteManagerImpl;
import org.apache.jackrabbit.server.io.IOHandler;
import org.apache.jackrabbit.server.io.IOManager;
import org.apache.jackrabbit.server.io.PropertyHandler;
import org.apache.jackrabbit.server.io.PropertyManager;
import org.apache.jackrabbit.server.io.PropertyManagerImpl;
import org.apache.jackrabbit.webdav.xml.DomUtil;
import org.apache.jackrabbit.webdav.xml.ElementIterator;
import org.apache.jackrabbit.webdav.xml.Namespace;
import org.apache.tika.detect.Detector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
/**
* ResourceConfig
...
*/
public class ResourceConfig {
private static Logger log = LoggerFactory.getLogger(ResourceConfig.class);
private static final String ELEMENT_IOMANAGER = "iomanager";
private static final String ELEMENT_IOHANDLER = "iohandler";
private static final String ELEMENT_PROPERTYMANAGER = "propertymanager";
private static final String ELEMENT_PROPERTYHANDLER = "propertyhandler";
private static final String ELEMENT_COPYMOVEMANAGER = "copymovemanager";
private static final String ELEMENT_COPYMOVEHANDLER = "copymovehandler";
private static final String ELEMENT_CLASS = "class";
private static final String ELEMENT_PARAM = "param";
private static final String ATTR_NAME = "name";
private static final String ATTR_VALUE = "value";
/**
* Content type detector.
*/
private final Detector detector;
private ItemFilter itemFilter;
private IOManager ioManager;
private CopyMoveManager cmManager;
private PropertyManager propManager;
private DeleteManager deleteManager;
private String[] nodetypeNames = new String[0];
private boolean collectionNames = false;
public ResourceConfig(Detector detector) {
this.detector = detector;
}
/**
* Tries to parse the given xml configuration file.
* The xml must match the following structure:
*
* <!ELEMENT config (iomanager, propertymanager, (collection | noncollection)?, filter?, mimetypeproperties?) >
* <!ELEMENT iomanager (class, iohandler*) >
* <!ELEMENT iohandler (class) >
* <!ELEMENT propertymanager (class, propertyhandler*) >
* <!ELEMENT propertyhandler (class) >
* <!ELEMENT collection (nodetypes) >
* <!ELEMENT noncollection (nodetypes) >
* <!ELEMENT filter (class, namespaces?, nodetypes?) >
* <!ELEMENT class >
* <!ATTLIST class
* name CDATA #REQUIRED
* >
* <!ELEMENT namespaces (prefix|uri)* >
* <!ELEMENT prefix (CDATA) >
* <!ELEMENT uri (CDATA) >
* <!ELEMENT nodetypes (nodetype)* >
* <!ELEMENT nodetype (CDATA) >
* <!ELEMENT mimetypeproperties (mimemapping*, defaultmimetype) >
* <!ELEMENT mimemapping >
* <!ATTLIST mimemapping
* extension CDATA #REQUIRED
* mimetype CDATA #REQUIRED
* >
* <!ELEMENT defaultmimetype (CDATA) >
*
*
* The <mimetypeproperties/> settings have been deprecated and will
* be ignored with a warning. Instead you can use the
* {@link SimpleWebdavServlet#INIT_PARAM_MIME_INFO mime-info}
* servlet initialization parameter to customize the media type settings.
*
* @param configURL
*/
public void parse(URL configURL) {
try {
parse(configURL.openStream());
} catch (IOException e) {
log.debug("Invalid resource configuration: " + e.getMessage());
}
}
/**
* Parses the given input stream into the xml configuration file.
* The xml must match the following structure:
*
* <!ELEMENT config (iomanager, propertymanager, (collection | noncollection)?, filter?, mimetypeproperties?) >
* <!ELEMENT iomanager (class, iohandler*) >
* <!ELEMENT iohandler (class) >
* <!ELEMENT propertymanager (class, propertyhandler*) >
* <!ELEMENT propertyhandler (class) >
* <!ELEMENT collection (nodetypes) >
* <!ELEMENT noncollection (nodetypes) >
* <!ELEMENT filter (class, namespaces?, nodetypes?) >
* <!ELEMENT class >
* <!ATTLIST class
* name CDATA #REQUIRED
* >
* <!ELEMENT namespaces (prefix|uri)* >
* <!ELEMENT prefix (CDATA) >
* <!ELEMENT uri (CDATA) >
* <!ELEMENT nodetypes (nodetype)* >
* <!ELEMENT nodetype (CDATA) >
* <!ELEMENT mimetypeproperties (mimemapping*, defaultmimetype) >
* <!ELEMENT mimemapping >
* <!ATTLIST mimemapping
* extension CDATA #REQUIRED
* mimetype CDATA #REQUIRED
* >
* <!ELEMENT defaultmimetype (CDATA) >
*
*
* The <mimetypeproperties/> settings have been deprecated and will
* be ignored with a warning. Instead you can use the
* {@link SimpleWebdavServlet#INIT_PARAM_MIME_INFO mime-info}
* servlet initialization parameter to customize the media type settings.
*
* @param stream
*/
public void parse(InputStream stream) {
try {
Element config = DomUtil.parseDocument(stream).getDocumentElement();
if (config == null) {
log.warn("Mandatory 'config' element is missing.");
return;
}
// iomanager config entry
Element el = DomUtil.getChildElement(config, ELEMENT_IOMANAGER, null);
if (el != null) {
Object inst = buildClassFromConfig(el);
if (inst != null && inst instanceof IOManager) {
ioManager = (IOManager)inst;
ioManager.setDetector(detector);
// get optional 'iohandler' child elements and populate the
// ioManager with the instances
ElementIterator iohElements = DomUtil.getChildren(el, ELEMENT_IOHANDLER, null);
while (iohElements.hasNext()) {
Element iohEl = iohElements.nextElement();
inst = buildClassFromConfig(iohEl);
if (inst != null && inst instanceof IOHandler) {
IOHandler handler = (IOHandler) inst;
setParameters(handler, iohEl);
ioManager.addIOHandler(handler);
} else {
log.warn("Not a valid IOHandler : " + getClassName(iohEl));
}
}
} else {
log.warn("'iomanager' element does not define a valid IOManager.");
}
} else {
log.warn("'iomanager' element is missing.");
}
// propertymanager config entry
el = DomUtil.getChildElement(config, ELEMENT_PROPERTYMANAGER, null);
if (el != null) {
Object inst = buildClassFromConfig(el);
if (inst != null && inst instanceof PropertyManager) {
propManager = (PropertyManager)inst;
// get optional 'iohandler' child elements and populate the
// ioManager with the instances
ElementIterator iohElements = DomUtil.getChildren(el, ELEMENT_PROPERTYHANDLER, null);
while (iohElements.hasNext()) {
Element iohEl = iohElements.nextElement();
inst = buildClassFromConfig(iohEl);
if (inst != null && inst instanceof PropertyHandler) {
PropertyHandler handler = (PropertyHandler) inst;
setParameters(handler, iohEl);
propManager.addPropertyHandler(handler);
} else {
log.warn("Not a valid PropertyHandler : " + getClassName(iohEl));
}
}
} else {
log.warn("'propertymanager' element does not define a valid PropertyManager.");
}
} else {
log.debug("'propertymanager' element is missing.");
}
// copymovemanager config entry
el = DomUtil.getChildElement(config, ELEMENT_COPYMOVEMANAGER, null);
if (el != null) {
Object inst = buildClassFromConfig(el);
if (inst != null && inst instanceof CopyMoveManager) {
cmManager = (CopyMoveManager) inst;
// get optional 'copymovehandler' child elements and populate
// the copy move manager with the instances
ElementIterator iohElements = DomUtil.getChildren(el, ELEMENT_COPYMOVEHANDLER, null);
while (iohElements.hasNext()) {
Element iohEl = iohElements.nextElement();
inst = buildClassFromConfig(iohEl);
if (inst != null && inst instanceof CopyMoveHandler) {
CopyMoveHandler handler = (CopyMoveHandler) inst;
setParameters(handler, iohEl);
cmManager.addCopyMoveHandler(handler);
} else {
log.warn("Not a valid CopyMoveHandler : " + getClassName(iohEl));
}
}
} else {
log.warn("'copymovemanager' element does not define a valid CopyMoveManager.");
}
} else {
log.debug("'copymovemanager' element is missing.");
}
// collection/non-collection config entry
el = DomUtil.getChildElement(config, "collection", null);
if (el != null) {
nodetypeNames = parseNodeTypesEntry(el);
collectionNames = true;
} else if ((el = DomUtil.getChildElement(config, "noncollection", null)) != null) {
nodetypeNames = parseNodeTypesEntry(el);
collectionNames = false;
}
// todo: should check if both 'noncollection' and 'collection' are present and write a warning
// filter config entry
el = DomUtil.getChildElement(config, "filter", null);
if (el != null) {
Object inst = buildClassFromConfig(el);
if (inst != null && inst instanceof ItemFilter) {
itemFilter = (ItemFilter)inst;
}
if (itemFilter != null) {
itemFilter.setFilteredNodetypes(parseNodeTypesEntry(el));
parseNamespacesEntry(el);
}
} else {
log.debug("No 'filter' element specified.");
}
el = DomUtil.getChildElement(config, "mimetypeproperties", null);
if (el != null) {
log.warn("Ignoring deprecated mimetypeproperties settings");
}
} catch (IOException e) {
log.debug("Invalid resource configuration: " + e.getMessage());
} catch (ParserConfigurationException e) {
log.warn("Failed to parse resource configuration: " + e.getMessage());
} catch (SAXException e) {
log.warn("Failed to parse resource configuration: " + e.getMessage());
}
}
private void parseNamespacesEntry(Element parent) {
Element namespaces = DomUtil.getChildElement(parent, "namespaces", null);
if (namespaces != null) {
List l = new ArrayList();
// retrieve prefix child elements
ElementIterator it = DomUtil.getChildren(namespaces, "prefix", null);
while (it.hasNext()) {
Element e = it.nextElement();
l.add(DomUtil.getText(e));
}
String[] prefixes = l.toArray(new String[l.size()]);
l.clear();
// retrieve uri child elements
it = DomUtil.getChildren(namespaces, "uri", null);
while (it.hasNext()) {
Element e = it.nextElement();
l.add(DomUtil.getText(e));
}
String[] uris = l.toArray(new String[l.size()]);
itemFilter.setFilteredPrefixes(prefixes);
itemFilter.setFilteredURIs(uris);
}
}
private static String[] parseNodeTypesEntry(Element parent) {
String[] ntNames;
Element nodetypes = DomUtil.getChildElement(parent, "nodetypes", null);
if (nodetypes != null) {
List l = new ArrayList();
ElementIterator it = DomUtil.getChildren(nodetypes, "nodetype", null);
while (it.hasNext()) {
Element e = it.nextElement();
l.add(DomUtil.getText(e));
}
ntNames = l.toArray(new String[l.size()]);
} else {
ntNames = new String[0];
}
return ntNames;
}
private static Object buildClassFromConfig(Element parent) {
Object instance = null;
Element classElem = DomUtil.getChildElement(parent, "class", null);
if (classElem != null) {
// contains a 'class' child node
try {
String className = DomUtil.getAttribute(classElem, "name", null);
if (className != null) {
Class> c = Class.forName(className);
instance = c.newInstance();
} else {
log.error("Invalid configuration: missing 'class' element");
}
} catch (Exception e) {
log.error("Error while create class instance: " + e.getMessage());
}
}
return instance;
}
private static String getClassName(Element parent) {
String className = null;
Element classElem = DomUtil.getChildElement(parent, "class", null);
if (classElem != null) {
className = DomUtil.getAttribute(classElem, "name", null);
}
return (className == null) ? "" : className;
}
/**
* Retrieve 'param' elements for the specified xmlElement
and
* use the public setter methods of the given instance
to set
* the corresponding instance fields.
*
* @param instance
* @param xmlElement
*/
private static void setParameters(Object instance, Element xmlElement) {
ElementIterator paramElems = DomUtil.getChildren(xmlElement, ELEMENT_PARAM, Namespace.EMPTY_NAMESPACE);
if (paramElems.hasNext()) {
Map setters = getSetters(instance.getClass());
if (!setters.isEmpty()) {
while (paramElems.hasNext()) {
Element parameter = paramElems.next();
String name = DomUtil.getAttribute(parameter, ATTR_NAME, null);
String value = DomUtil.getAttribute(parameter, ATTR_VALUE, null);
if (name == null || value == null) {
log.error("Parameter name or value missing -> ignore.");
continue;
}
Method setter = setters.get(name);
if (setter != null) {
Class> type = setter.getParameterTypes()[0];
try {
if (type.isAssignableFrom(String.class)
|| type.isAssignableFrom(Object.class)) {
setter.invoke(instance, value);
} else if (type.isAssignableFrom(Boolean.TYPE)
|| type.isAssignableFrom(Boolean.class)) {
setter.invoke(instance, Boolean.valueOf(value));
} else if (type.isAssignableFrom(Integer.TYPE)
|| type.isAssignableFrom(Integer.class)) {
setter.invoke(instance, Integer.valueOf(value));
} else if (type.isAssignableFrom(Long.TYPE)
|| type.isAssignableFrom(Long.class)) {
setter.invoke(instance, Long.valueOf(value));
} else if (type.isAssignableFrom(Double.TYPE)
|| type.isAssignableFrom(Double.class)) {
setter.invoke(instance, Double.valueOf(value));
} else {
log.error("Cannot set configuration property " + name);
}
} catch (Exception e) {
log.error("Invalid format (" + value + ") for property " + name + " of class " + instance.getClass().getName(), e);
}
}
}
}
}
}
private static Map getSetters(Class> cl) {
Map methods = new HashMap();
for (Method method : cl.getMethods()) {
String name = method.getName();
if (name.startsWith("set") && name.length() > 3
&& Modifier.isPublic(method.getModifiers())
&& !Modifier.isStatic(method.getModifiers())
&& Void.TYPE.equals(method.getReturnType())
&& method.getParameterTypes().length == 1) {
methods.put(name.substring(3, 4).toLowerCase() + name.substring(4), method);
}
}
return methods;
}
/**
*
* @return
*/
public IOManager getIOManager() {
if (ioManager == null) {
log.debug("Missing io-manager > building DefaultIOManager ");
ioManager = new DefaultIOManager();
ioManager.setDetector(detector);
}
return ioManager;
}
/**
*
* @return
*/
public PropertyManager getPropertyManager() {
if (propManager == null) {
log.debug("Missing property-manager > building default.");
propManager = PropertyManagerImpl.getDefaultManager();
}
return propManager;
}
/**
*
* @return
*/
public CopyMoveManager getCopyMoveManager() {
if (cmManager == null) {
log.debug("Missing copymove-manager > building default.");
cmManager = CopyMoveManagerImpl.getDefaultManager();
}
return cmManager;
}
/**
* Returns the delete manager.
* @return the delete manager
*/
public DeleteManager getDeleteManager() {
if (deleteManager == null) {
log.debug("Missing delete-manager > building default.");
deleteManager = DeleteManagerImpl.getDefaultManager();
}
return deleteManager;
}
/**
* Returns true, if the given item represents a {@link Node node} that is
* either any of the nodetypes specified to represent a collection or
* none of the nodetypes specified to represent a non-collection, respectively.
* If no valid configuration entry is present, this method returns true
* for node items. For items which are not a node, this method always
* returns false.
*
* @param item
* @return true if the given item is a node that represents a webdav
* collection, false otherwise.
*/
public boolean isCollectionResource(Item item) {
if (item.isNode()) {
boolean isCollection = true;
Node n = (Node)item;
try {
for (int i = 0; i < nodetypeNames.length && isCollection; i++) {
isCollection = collectionNames ? n.isNodeType(nodetypeNames[i]) : !n.isNodeType(nodetypeNames[i]);
}
} catch (RepositoryException e) {
log.warn(e.getMessage());
}
return isCollection;
} else {
return false;
}
}
/**
* Returns the item filter specified with the configuration or {@link DefaultItemFilter}
* if the configuration was missing the corresponding entry or the parser failed
* to build a ItemFilter
instance from the configuration.
*
* @return item filter as defined by the config or {@link DefaultItemFilter}
*/
public ItemFilter getItemFilter() {
if (itemFilter == null) {
log.debug("Missing resource filter > building DefaultItemFilter ");
itemFilter = new DefaultItemFilter();
}
return itemFilter;
}
/**
* Returns the configured content type detector.
*
* @return content type detector
*/
public Detector getDetector() {
return detector;
}
}