/* * 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.sequencer.ddl; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Queue; import javax.jcr.Binary; import javax.jcr.NamespaceRegistry; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFactory; import org.modeshape.common.annotation.NotThreadSafe; import org.modeshape.common.logging.Logger; import org.modeshape.common.text.ParsingException; import org.modeshape.common.util.CheckArg; import org.modeshape.common.util.IoUtil; import org.modeshape.jcr.api.JcrConstants; import org.modeshape.jcr.api.Session; import org.modeshape.jcr.api.nodetype.NodeTypeManager; import org.modeshape.jcr.api.sequencer.Sequencer; import org.modeshape.sequencer.ddl.node.AstNode; /** * A sequencer of DDL files. */ @NotThreadSafe public class DdlSequencer extends Sequencer { private static final Logger LOGGER = Logger.getLogger(DdlSequencer.class); protected static final URL[] DEFAULT_CLASSPATH = new URL[] {}; protected static final List<String> DEFAULT_GRAMMARS; protected static final Map<String, DdlParser> STANDARD_PARSERS_BY_NAME; static { List<String> grammarNames = new ArrayList<String>(); Map<String, DdlParser> parsersByName = new HashMap<String, DdlParser>(); for (DdlParser parser : DdlParsers.BUILTIN_PARSERS) { String grammarName = parser.getId().toLowerCase(); grammarNames.add(grammarName); parsersByName.put(grammarName, parser); } DEFAULT_GRAMMARS = Collections.unmodifiableList(grammarNames); STANDARD_PARSERS_BY_NAME = Collections.unmodifiableMap(parsersByName); } private String[] parserGrammars = DEFAULT_GRAMMARS.toArray(new String[DEFAULT_GRAMMARS.size()]); private URL[] classpath = DEFAULT_CLASSPATH; private final Map<AstNode, Node> nodeMap = new HashMap<AstNode, Node>(); /** * Get the names of the grammars that should be considered during processing. The grammar names may be the case-insensitive * {@link DdlParser#getId() identifier} of a built-in grammar, or the name of a {@link DdlParser} implementation class. * * @return the array of grammar names or classes; never null but possibly empty */ public String[] getGrammars() { return parserGrammars; } /** * Set the names of the grammars that should be considered during processing. The grammar names may be the case-insensitive * {@link DdlParser#getId() identifier} of a built-in grammar, or the name of a {@link DdlParser} implementation class. * * @param grammarNamesOrClasses the names; may be null if the default grammar list should be used */ public void setGrammars( String[] grammarNamesOrClasses ) { this.parserGrammars = grammarNamesOrClasses != null && grammarNamesOrClasses.length != 0 ? grammarNamesOrClasses : DEFAULT_GRAMMARS.toArray(new String[DEFAULT_GRAMMARS.size()]); } /** * Get the names of the classloaders that should be used to load any non-standard DdlParser implementations specified in the * list of grammars. * * @return the classloader names that make up the classpath; never null but possibly empty if the default classpath should be * used */ public URL[] getClasspath() { return classpath; } /** * Set the names of the classloaders that should be used to load any non-standard DdlParser implementations specified in the * list of grammars. * * @param classpath the classloader names that make up the classpath; may be null or empty if the default classpath should be * used */ public void setClasspath( URL[] classpath ) { this.classpath = classpath != null ? classpath : DEFAULT_CLASSPATH; } /** * Method that creates the DdlParsers instance. This may be overridden in subclasses to creates specific implementations. * * @param parsers the list of DdlParser instances to use; may be empty or null * @return the DdlParsers implementation; may not be null */ protected DdlParsers createParsers( List<DdlParser> parsers ) { return new DdlParsers(parsers); } @SuppressWarnings( "unchecked" ) protected List<DdlParser> getParserList() { List<DdlParser> parserList = new LinkedList<DdlParser>(); for (String grammar : getGrammars()) { if (grammar == null) { continue; } // Look for a standard parser using a case-insensitive name ... String lowercaseGrammar = grammar.toLowerCase(); DdlParser parser = STANDARD_PARSERS_BY_NAME.get(lowercaseGrammar); if (parser == null) { // Attempt to instantiate the parser if its a classname ... try { ClassLoader classloader = new URLClassLoader(getClasspath(), Thread.currentThread().getContextClassLoader()); Class<DdlParser> componentClass = (Class<DdlParser>)Class.forName(grammar, true, classloader); parser = componentClass.newInstance(); } catch (Throwable e) { if (classpath == null || classpath.length == 0) { LOGGER.error(e, DdlSequencerI18n.errorInstantiatingParserForGrammarUsingDefaultClasspath, grammar, e.getLocalizedMessage()); } else { LOGGER.error(e, DdlSequencerI18n.errorInstantiatingParserForGrammarClasspath, grammar, classpath, e.getLocalizedMessage()); } } } if (parser != null) { parserList.add(parser); } } return parserList; // okay if empty } @Override public void initialize( NamespaceRegistry registry, NodeTypeManager nodeTypeManager ) throws RepositoryException, IOException { registerNodeTypes("StandardDdl.cnd", nodeTypeManager, true); registerNodeTypes("dialect/derby/DerbyDdl.cnd", nodeTypeManager, true); registerNodeTypes("dialect/oracle/OracleDdl.cnd", nodeTypeManager, true); registerNodeTypes("dialect/postgres/PostgresDdl.cnd", nodeTypeManager, true); } @Override public boolean execute( Property inputProperty, Node outputNode, Context context ) throws Exception { Binary ddlContent = inputProperty.getBinary(); CheckArg.isNotNull(ddlContent, "ddl content binary value"); // make sure node map is empty this.nodeMap.clear(); // Look at the input path to get the name of the input node (or it's parent if it's "jcr:content") ... String fileName = getNameOfDdlContent(inputProperty); // Perform the parsing final AstNode rootNode; DdlParsers parsers = createParsers(getParserList()); try (InputStream stream = ddlContent.getStream()) { rootNode = parsers.parse(IoUtil.read(stream), fileName); } catch (ParsingException e) { LOGGER.error(e, DdlSequencerI18n.errorParsingDdlContent, e.getLocalizedMessage()); return false; } catch (IOException e) { LOGGER.error(e, DdlSequencerI18n.errorSequencingDdlContent, e.getLocalizedMessage()); return false; } Queue<AstNode> queue = new LinkedList<AstNode>(); queue.add(rootNode); while (queue.peek() != null) { AstNode astNode = queue.poll(); createFromAstNode(outputNode, astNode); // Add the children to the queue ... for (AstNode child : astNode.getChildren()) { queue.add(child); } } // second pass to lookup references (this allows for DDL to have forward references) for (final Entry<AstNode, Node> entry : this.nodeMap.entrySet()) { appendNodeProperties(entry.getKey(), entry.getValue()); } return true; } private void appendNodeProperties( AstNode astNode, Node sequenceNode ) throws RepositoryException { ValueFactory valueFactory = sequenceNode.getSession().getValueFactory(); for (String propertyName : astNode.getPropertyNames()) { Object astNodePropertyValue = astNode.getProperty(propertyName); List<Value> valuesList = convertToPropertyValues(astNodePropertyValue, valueFactory); if (valuesList.size() == 1) { sequenceNode.setProperty(propertyName, valuesList.get(0)); } else { sequenceNode.setProperty(propertyName, valuesList.toArray(new Value[0])); } } } private Node createFromAstNode( Node parent, AstNode astNode ) throws RepositoryException { String relativePath = astNode.getAbsolutePath().substring(1); Node sequenceNode = null; // for SNS the absolute path will use first node it finds as the parent so find real parent if possible Node parentNode = getNode(astNode.getParent()); if (parentNode == null) { sequenceNode = parent.addNode(relativePath, astNode.getPrimaryType()); } else { final Session session = (Session)parentNode.getSession(); String jcrName = astNode.getName(); // if first character is a '{' then the name is prefixed by the namespace URL if ((jcrName.charAt(0) == '{') && (jcrName.indexOf('}') != -1)) { final int index = jcrName.indexOf('}'); String localName = jcrName.substring(index + 1); localName = session.encode(localName); jcrName = jcrName.substring(0, (index + 1)) + localName; } else { jcrName = session.encode(jcrName); } sequenceNode = parentNode.addNode(jcrName, astNode.getPrimaryType()); } this.nodeMap.put(astNode, sequenceNode); for (String mixin : astNode.getMixins()) { sequenceNode.addMixin(mixin); } astNode.removeProperty(JcrConstants.JCR_MIXIN_TYPES); astNode.removeProperty(JcrConstants.JCR_PRIMARY_TYPE); return sequenceNode; } private List<Value> convertToPropertyValues( Object objectValue, ValueFactory valueFactory ) throws RepositoryException { List<Value> result = new ArrayList<Value>(); if (objectValue instanceof Collection) { Collection<?> objects = (Collection<?>)objectValue; for (Object childObjectValue : objects) { List<Value> childValues = convertToPropertyValues(childObjectValue, valueFactory); result.addAll(childValues); } } else if (objectValue instanceof Boolean) { result.add(valueFactory.createValue((Boolean)objectValue)); } else if (objectValue instanceof Integer) { result.add(valueFactory.createValue((Integer)objectValue)); } else if (objectValue instanceof Long) { result.add(valueFactory.createValue((Long)objectValue)); } else if (objectValue instanceof Double) { result.add(valueFactory.createValue((Double)objectValue)); } else if (objectValue instanceof Float) { result.add(valueFactory.createValue((Float)objectValue)); } else if (objectValue instanceof AstNode) { result.add(valueFactory.createValue(getNode((AstNode)objectValue))); } else { result.add(valueFactory.createValue(objectValue.toString())); } return result; } private Node getNode( final AstNode node ) { return this.nodeMap.get(node); } private String getNameOfDdlContent( Property inputProperty ) throws RepositoryException { Node parentNode = inputProperty.getParent(); if (JcrConstants.JCR_CONTENT.equalsIgnoreCase(parentNode.getName())) { parentNode = parentNode.getParent(); } return parentNode.getName(); } }