/* * 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)); } }