View Javadoc

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 }