/*
* ModeShape (http://www.modeshape.org)
*
* Licensed 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.modeshape.jcr;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jcr.NamespaceException;
import javax.jcr.NamespaceRegistry;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.nodetype.NodeDefinitionTemplate;
import javax.jcr.nodetype.NodeTypeDefinition;
import javax.jcr.nodetype.NodeTypeTemplate;
import javax.jcr.nodetype.PropertyDefinitionTemplate;
import javax.jcr.version.OnParentVersionAction;
import org.modeshape.common.annotation.NotThreadSafe;
import org.modeshape.jcr.api.nodetype.NodeTypeManager;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* A class that reads node types from Jackrabbit XML files. This class is used automatically when the ModeShape
* {@link NodeTypeManager}'s {@link NodeTypeManager#registerNodeTypes registerNodeTypes(...)} methods are used:
*
* <pre>
* Session session = ...
* org.modeshape.jcr.api.nodetype.NodeTypeManager mgr =
* (org.modeshape.jcr.api.nodetype.NodeTypeManager)session.getWorkspace().getNodeTypeManager();
* mgr.registerNodeTypes(file); // or stream or URL
* </pre>
*
* </p>
* <p>
* The format of the Jackrabbit XML is defined by this DTD:
*
* <pre>
* <!ELEMENT nodeTypes (nodeType)*>
* <!ELEMENT nodeType (supertypes?|propertyDefinition*|childNodeDefinition*)>
*
* <!ATTLIST nodeType
* name CDATA #REQUIRED
* isMixin (true|false) #REQUIRED
* hasOrderableChildNodes (true|false) #REQUIRED
* primaryItemName CDATA #REQUIRED
* >
* <!ELEMENT supertypes (supertype+)>
* <!ELEMENT supertype (CDATA)>
*
* <!ELEMENT propertyDefinition (valueConstraints?|defaultValues?)>
* <!ATTLIST propertyDefinition
* name CDATA #REQUIRED
* requiredType (String|Date|Path|Name|Reference|Binary|Double|Long|Boolean|undefined) #REQUIRED
* autoCreated (true|false) #REQUIRED
* mandatory (true|false) #REQUIRED
* onParentVersion (COPY|VERSION|INITIALIZE|COMPUTE|IGNORE|ABORT) #REQUIRED
* protected (true|false) #REQUIRED
* multiple (true|false) #REQUIRED
* >
* <!ELEMENT valueConstraints (valueConstraint+)>
* <!ELEMENT valueConstraint (CDATA)>
* <!ELEMENT defaultValues (defaultValue+)>
* <!ELEMENT defaultValue (CDATA)>
*
* <!ELEMENT childNodeDefinition (requiredPrimaryTypes)>
* <!ATTLIST childNodeDefinition
* name CDATA #REQUIRED
* defaultPrimaryType CDATA #REQUIRED
* autoCreated (true|false) #REQUIRED
* mandatory (true|false) #REQUIRED
* onParentVersion (COPY|VERSION|INITIALIZE|COMPUTE|IGNORE|ABORT) #REQUIRED
* protected (true|false) #REQUIRED
* sameNameSiblings (true|false) #REQUIRED
* >
* <!ELEMENT requiredPrimaryTypes (requiredPrimaryType+)>
* <!ELEMENT requiredPrimaryType (CDATA)>
*
* </pre>
*/
@NotThreadSafe
class XmlNodeTypeReader extends DefaultHandler {
private static final String NODE_TYPE = "nodeType";
private static final String PROPERTY_DEFINITION = "propertyDefinition";
private static final String CHILD_NODE_DEFINITION = "childNodeDefinition";
private static final String SUPERTYPES = "supertypes";
private static final String REQUIRED_PRIMARY_TYPES = "requiredPrimaryTypes";
private static final String DEFAULT_VALUES = "defaultValues";
private static final String VALUE_CONSTRAINTS = "valueConstraints";
private static final String SUPERTYPE = "supertype";
private static final String REQUIRED_PRIMARY_TYPE = "requiredPrimaryType";
private static final String DEFAULT_VALUE = "defaultValue";
private static final String VALUE_CONSTRAINT = "valueConstraint";
protected abstract class CharHandler {
private Set<String> values = new LinkedHashSet<String>();
private int stack = 0;
public void characters( String chars ) {
if (chars == null) return;
chars = chars.trim();
if (chars.length() == 0) return;
values.add(chars);
}
public void incrementStack() {
++stack;
}
public boolean finish() throws RepositoryException {
if (--stack < 1) {
doFinish();
stack = 0;
return true;
}
return false;
}
protected abstract void doFinish() throws RepositoryException;
protected final String[] getStringValues() {
Set<String> values = this.values;
this.values = new LinkedHashSet<String>();
return values.toArray(new String[values.size()]);
}
protected final Value[] getJcrValues() throws RepositoryException {
Value[] values = new Value[this.values.size()];
ValueFactory factory = session.getValueFactory();
int i = 0;
for (String value : this.values) {
values[i++] = factory.createValue(value);
}
this.values = new LinkedHashSet<String>();
return values;
}
}
protected class SupertypeNameHandler extends CharHandler {
@Override
protected void doFinish() throws RepositoryException {
currentNodeType.setDeclaredSuperTypeNames(getStringValues());
}
}
protected class RequiredPrimaryTypeHandler extends CharHandler {
@Override
protected void doFinish() throws RepositoryException {
currentChildDefn.setRequiredPrimaryTypeNames(getStringValues());
}
}
protected class DefaultValueHandler extends CharHandler {
@Override
protected void doFinish() throws RepositoryException {
currentPropDefn.setDefaultValues(getJcrValues());
}
}
protected class ConstraintHandler extends CharHandler {
@Override
protected void doFinish() {
currentPropDefn.setValueConstraints(getStringValues());
}
}
protected JcrSession session;
private final javax.jcr.nodetype.NodeTypeManager nodeTypeManager;
private List<NodeTypeTemplate> nodeTypes = new ArrayList<NodeTypeTemplate>();
private final Map<String, CharHandler> charHandlers = new HashMap<String, CharHandler>();
private CharHandler charHandler;
protected NodeTypeTemplate currentNodeType;
protected PropertyDefinitionTemplate currentPropDefn;
protected NodeDefinitionTemplate currentChildDefn;
private NamespaceRegistry namespaces;
/**
* Create a new node type factory that reads the node types from Jackrabbit XML files.
*
* @param session the session that will be used to register the node types; may not be null
* @throws RepositoryException if there is a problem
*/
XmlNodeTypeReader( JcrSession session ) throws RepositoryException {
this.session = session;
this.nodeTypeManager = session.getWorkspace().getNodeTypeManager();
this.namespaces = this.session.getWorkspace().getNamespaceRegistry();
CharHandler supertypeHandler = new SupertypeNameHandler();
CharHandler requiredPrimaryTypeHandler = new RequiredPrimaryTypeHandler();
CharHandler defaultHandler = new DefaultValueHandler();
CharHandler constraintHandler = new ConstraintHandler();
this.charHandlers.put(SUPERTYPES, supertypeHandler);
this.charHandlers.put(REQUIRED_PRIMARY_TYPES, requiredPrimaryTypeHandler);
this.charHandlers.put(DEFAULT_VALUES, defaultHandler);
this.charHandlers.put(VALUE_CONSTRAINTS, constraintHandler);
this.charHandlers.put(SUPERTYPE, supertypeHandler);
this.charHandlers.put(REQUIRED_PRIMARY_TYPE, requiredPrimaryTypeHandler);
this.charHandlers.put(DEFAULT_VALUE, defaultHandler);
this.charHandlers.put(VALUE_CONSTRAINT, constraintHandler);
}
@Override
public void startPrefixMapping( String prefix,
String uri ) throws SAXException {
try {
try {
namespaces.getPrefix(uri);
} catch (NamespaceException e) {
namespaces.registerNamespace(prefix, uri);
}
} catch (RepositoryException e) {
throw new SAXException(e);
}
}
@Override
public void startElement( String uri,
String localName,
String name,
Attributes atts ) throws SAXException {
String value = null;
try {
if (NODE_TYPE.equals(localName)) {
currentNodeType = nodeTypeManager.createNodeTypeTemplate();
if ((value = atts.getValue("name")) != null) currentNodeType.setName(value);
if ((value = atts.getValue("isMixin")) != null) currentNodeType.setMixin(bool(value));
if ((value = atts.getValue("hasOrderableChildNodes")) != null) currentNodeType.setOrderableChildNodes(bool(value));
if ((value = atts.getValue("primaryItemName")) != null) currentNodeType.setPrimaryItemName(value);
} else if (PROPERTY_DEFINITION.equals(localName)) {
currentPropDefn = nodeTypeManager.createPropertyDefinitionTemplate();
currentChildDefn = null;
if ((value = atts.getValue("name")) != null) currentPropDefn.setName(value);
if ((value = atts.getValue("requiredType")) != null) currentPropDefn.setRequiredType(type(value));
if ((value = atts.getValue("autoCreated")) != null) currentPropDefn.setAutoCreated(bool(value));
if ((value = atts.getValue("mandatory")) != null) currentPropDefn.setMandatory(bool(value));
if ((value = atts.getValue("onParentVersion")) != null) currentPropDefn.setOnParentVersion(opv(value));
if ((value = atts.getValue("protected")) != null) currentPropDefn.setProtected(bool(value));
if ((value = atts.getValue("multiple")) != null) currentPropDefn.setMultiple(bool(value));
} else if (CHILD_NODE_DEFINITION.equals(localName)) {
currentChildDefn = nodeTypeManager.createNodeDefinitionTemplate();
currentPropDefn = null;
if ((value = atts.getValue("name")) != null) currentChildDefn.setName(value);
if ((value = atts.getValue("defaultPrimaryType")) != null) currentChildDefn.setDefaultPrimaryTypeName(value);
if ((value = atts.getValue("autoCreated")) != null) currentChildDefn.setAutoCreated(bool(value));
if ((value = atts.getValue("mandatory")) != null) currentChildDefn.setMandatory(bool(value));
if ((value = atts.getValue("onParentVersion")) != null) currentChildDefn.setOnParentVersion(opv(value));
if ((value = atts.getValue("protected")) != null) currentChildDefn.setProtected(bool(value));
if ((value = atts.getValue("sameNameSiblings")) != null) currentChildDefn.setSameNameSiblings(bool(value));
}
charHandler = charHandlers.get(localName);
if (charHandler != null) charHandler.incrementStack();
} catch (RepositoryException e) {
throw new SAXException(e);
}
}
@SuppressWarnings( "unchecked" )
@Override
public void endElement( String uri,
String localName,
String name ) throws SAXException {
if (charHandler != null) {
try {
if (charHandler.finish()) charHandler = null;
} catch (RepositoryException e) {
throw new SAXException(e);
}
}
if (NODE_TYPE.equals(localName)) {
nodeTypes.add(currentNodeType);
currentNodeType = null;
} else if (PROPERTY_DEFINITION.equals(localName)) {
currentNodeType.getPropertyDefinitionTemplates().add(currentPropDefn);
currentPropDefn = null;
} else if (CHILD_NODE_DEFINITION.equals(localName)) {
currentNodeType.getNodeDefinitionTemplates().add(currentChildDefn);
currentChildDefn = null;
}
}
@Override
public void characters( char[] ch,
int start,
int length ) {
if (charHandler != null) {
String value = new String(ch, start, length);
charHandler.characters(value);
}
}
protected boolean bool( String value ) {
return Boolean.parseBoolean(value);
}
protected int type( String value ) {
return org.modeshape.jcr.api.PropertyType.valueFromName(value);
}
protected int opv( String value ) {
return OnParentVersionAction.valueFromName(value);
}
/**
* @return nodeTypes
*/
public List<NodeTypeDefinition> getNodeTypeDefinitions() {
return Collections.unmodifiableList(new ArrayList<NodeTypeDefinition>(nodeTypes));
}
}