1 /*
2 * ModeShape (http://www.modeshape.org)
3 * See the COPYRIGHT.txt file distributed with this work for information
4 * regarding copyright ownership. Some portions may be licensed
5 * to Red Hat, Inc. under one or more contributor license agreements.
6 * See the AUTHORS.txt file in the distribution for a full listing of
7 * individual contributors.
8 *
9 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10 * is licensed to you under the terms of the GNU Lesser General Public License as
11 * published by the Free Software Foundation; either version 2.1 of
12 * the License, or (at your option) any later version.
13 *
14 * ModeShape is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this software; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23 */
24 package org.modeshape.sequencer.ddl;
25
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Queue;
35 import net.jcip.annotations.NotThreadSafe;
36 import org.modeshape.common.text.ParsingException;
37 import org.modeshape.common.util.IoUtil;
38 import org.modeshape.graph.JcrLexicon;
39 import org.modeshape.graph.property.Path;
40 import org.modeshape.graph.property.Property;
41 import org.modeshape.graph.sequencer.SequencerOutput;
42 import org.modeshape.graph.sequencer.StreamSequencer;
43 import org.modeshape.graph.sequencer.StreamSequencerContext;
44 import org.modeshape.sequencer.ddl.node.AstNode;
45
46 /**
47 * A sequencer of DDL files.
48 */
49 @NotThreadSafe
50 public class DdlSequencer implements StreamSequencer {
51
52 protected static final String[] DEFAULT_CLASSPATH = new String[] {};
53 protected static final List<String> DEFAULT_GRAMMARS;
54 protected static final Map<String, DdlParser> STANDARD_PARSERS_BY_NAME;
55
56 static {
57 List<String> grammarNames = new ArrayList<String>();
58 Map<String, DdlParser> parsersByName = new HashMap<String, DdlParser>();
59 for (DdlParser parser : DdlParsers.BUILTIN_PARSERS) {
60 String grammarName = parser.getId().toLowerCase();
61 grammarNames.add(grammarName);
62 parsersByName.put(grammarName, parser);
63 }
64 DEFAULT_GRAMMARS = Collections.unmodifiableList(grammarNames);
65 STANDARD_PARSERS_BY_NAME = Collections.unmodifiableMap(parsersByName);
66 }
67
68 private String[] parserGrammars = DEFAULT_GRAMMARS.toArray(new String[DEFAULT_GRAMMARS.size()]);
69 private String[] classpath = DEFAULT_CLASSPATH;
70
71 /**
72 * Get the names of the grammars that should be considered during processing. The grammar names may be the case-insensitive
73 * {@link DdlParser#getId() identifier} of a built-in grammar, or the name of a {@link DdlParser} implementation class.
74 *
75 * @return the array of grammar names or classes; never null but possibly empty
76 */
77 public String[] getGrammars() {
78 return parserGrammars;
79 }
80
81 /**
82 * Set the names of the grammars that should be considered during processing. The grammar names may be the case-insensitive
83 * {@link DdlParser#getId() identifier} of a built-in grammar, or the name of a {@link DdlParser} implementation class.
84 *
85 * @param grammarNamesOrClasses the names; may be null if the default grammar list should be used
86 */
87 public void setGrammars( String[] grammarNamesOrClasses ) {
88 this.parserGrammars = grammarNamesOrClasses != null && grammarNamesOrClasses.length != 0 ? grammarNamesOrClasses : DEFAULT_GRAMMARS.toArray(new String[DEFAULT_GRAMMARS.size()]);
89 }
90
91 /**
92 * Get the names of the classloaders that should be used to load any non-standard DdlParser implementations specified in the
93 * list of grammars.
94 *
95 * @return the classloader names that make up the classpath; never null but possibly empty if the default classpath should be
96 * used
97 */
98 public String[] getClasspath() {
99 return classpath;
100 }
101
102 /**
103 * Set the names of the classloaders that should be used to load any non-standard DdlParser implementations specified in the
104 * list of grammars.
105 *
106 * @param classpath the classloader names that make up the classpath; may be null or empty if the default classpath should be
107 * used
108 */
109 public void setClasspath( String[] classpath ) {
110 this.classpath = classpath != null ? classpath : DEFAULT_CLASSPATH;
111 }
112
113 /**
114 * {@inheritDoc}
115 *
116 * @see org.modeshape.graph.sequencer.StreamSequencer#sequence(java.io.InputStream,
117 * org.modeshape.graph.sequencer.SequencerOutput, org.modeshape.graph.sequencer.StreamSequencerContext)
118 */
119 public void sequence( InputStream stream,
120 SequencerOutput output,
121 StreamSequencerContext context ) {
122 try {
123 // Look at the input path to get the name of the input node (or it's parent if it's "jcr:content") ...
124 String fileName = getNameOfDdlContent(context);
125
126 // Perform the parsing
127 DdlParsers parsers = createParsers(getParserList(context));
128 final AstNode rootNode = parsers.parse(IoUtil.read(stream), fileName);
129
130 // Convert the AST graph into graph nodes in the output ...
131 Queue<AstNode> queue = new LinkedList<AstNode>();
132 queue.add(rootNode);
133 while (queue.peek() != null) {
134 AstNode astNode = queue.poll();
135 Path path = astNode.getPath(context);
136 // Write the AST node properties to the output ...
137 for (Property property : astNode.getProperties()) {
138 output.setProperty(path, property.getName(), property.getValuesAsArray());
139 }
140 // Add the children to the queue ...
141 for (AstNode child : astNode.getChildren()) {
142 queue.add(child);
143 }
144 }
145 } catch (ParsingException e) {
146 context.getProblems().addError(e, DdlSequencerI18n.errorParsingDdlContent, e.getLocalizedMessage());
147 } catch (IOException e) {
148 context.getProblems().addError(e, DdlSequencerI18n.errorSequencingDdlContent, e.getLocalizedMessage());
149 }
150 }
151
152 /**
153 * Method that creates the DdlParsers instance. This may be overridden in subclasses to creates specific implementations.
154 *
155 * @param parsers the list of DdlParser instances to use; may be empty or null
156 * @return the DdlParsers implementation; may not be null
157 */
158 protected DdlParsers createParsers( List<DdlParser> parsers ) {
159 return new DdlParsers(parsers);
160 }
161
162 /**
163 * Utility method that attempts to discover the "name" of the DDL content being sequenced, which may help identify the
164 * dialect.
165 *
166 * @param context the sequencing context; never null
167 * @return the name, or null if no name could be identified
168 */
169 protected String getNameOfDdlContent( StreamSequencerContext context ) {
170 Path inputPath = context.getInputPath();
171 if (inputPath.isRoot()) return null;
172 Path.Segment segment = inputPath.getLastSegment();
173 if (JcrLexicon.CONTENT.equals(segment.getName()) && inputPath.size() > 1) {
174 // Get the name of the parent ...
175 segment = inputPath.getParent().getLastSegment();
176 }
177 return segment.getName().getLocalName();
178 }
179
180 @SuppressWarnings( "unchecked" )
181 protected List<DdlParser> getParserList( StreamSequencerContext context ) {
182 List<DdlParser> parserList = new LinkedList<DdlParser>();
183 for (String grammar : getGrammars()) {
184 if (grammar == null) continue;
185 // Look for a standard parser using a case-insensitive name ...
186 String lowercaseGrammar = grammar.toLowerCase();
187 DdlParser parser = STANDARD_PARSERS_BY_NAME.get(lowercaseGrammar);
188 if (parser == null) {
189 // Attempt to instantiate the parser if its a classname ...
190 String[] classpath = getClasspath();
191 try {
192 ClassLoader classloader = context.getClassLoader(classpath);
193 Class<DdlParser> componentClass = (Class<DdlParser>)Class.forName(grammar, true, classloader);
194 parser = componentClass.newInstance();
195 } catch (Throwable e) {
196 if (classpath == null || classpath.length == 0) {
197 context.getProblems().addError(e,
198 DdlSequencerI18n.errorInstantiatingParserForGrammarUsingDefaultClasspath,
199 grammar,
200 e.getLocalizedMessage());
201 } else {
202 context.getProblems().addError(e,
203 DdlSequencerI18n.errorInstantiatingParserForGrammarClasspath,
204 grammar,
205 classpath,
206 e.getLocalizedMessage());
207 }
208 }
209 }
210 if (parser != null) parserList.add(parser);
211 }
212 return parserList; // okay if empty
213 }
214 }