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.graph.connector.federation;
25  
26  import net.jcip.annotations.ThreadSafe;
27  import org.modeshape.common.text.TextEncoder;
28  import org.modeshape.common.util.CheckArg;
29  import org.modeshape.common.util.Logger;
30  import org.modeshape.common.util.StringUtil;
31  import org.modeshape.graph.ExecutionContext;
32  import org.modeshape.graph.GraphI18n;
33  import org.modeshape.graph.connector.federation.Projection.Rule;
34  import org.modeshape.graph.property.NamespaceRegistry;
35  
36  import java.lang.reflect.Method;
37  import java.util.Collections;
38  import java.util.LinkedList;
39  import java.util.List;
40  import java.util.concurrent.CopyOnWriteArrayList;
41  
42  /**
43   * A parser library for {@link Projection projections} and {@link Projection.Rule projection rules}.
44   */
45  @ThreadSafe
46  public class ProjectionParser {
47      private static final ProjectionParser INSTANCE;
48  
49      private static final Logger LOGGER = Logger.getLogger(Projection.class);
50  
51      static {
52          INSTANCE = new ProjectionParser();
53          try {
54              INSTANCE.addRuleParser(Projection.class, "parsePathRule");
55              assert INSTANCE.parserMethods.size() == 1;
56          } catch (Throwable err) {
57              LOGGER.error(err, GraphI18n.errorAddingProjectionRuleParseMethod);
58          }
59      }
60  
61      /**
62       * Get the shared projection parser, which is by default populated with the standard parser rules.
63       * 
64       * @return the parser; never null
65       */
66      public static ProjectionParser getInstance() {
67          return INSTANCE;
68      }
69  
70      private final List<Method> parserMethods = new CopyOnWriteArrayList<Method>();
71  
72      public ProjectionParser() {
73      }
74  
75      /**
76       * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition
77       * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an
78       * {@link ExecutionContext} environment reference as the second parameter, and should return the resulting {@link Rule} (or
79       * null if the definition format could not be understood by the method. Any exceptions during
80       * {@link Method#invoke(Object, Object...) invocation} will be logged at the
81       * {@link Logger#trace(Throwable, String, Object...) trace} level.
82       * 
83       * @param method the method to be added
84       * @see #addRuleParser(ClassLoader, String, String)
85       */
86      public void addRuleParser( Method method ) {
87          if (method != null) parserMethods.add(method);
88      }
89  
90      /**
91       * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition
92       * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an
93       * {@link ExecutionContext} environment reference as the second parameter, and should return the resulting {@link Rule} (or
94       * null if the definition format could not be understood by the method. Any exceptions during
95       * {@link Method#invoke(Object, Object...) invocation} will be logged at the
96       * {@link Logger#trace(Throwable, String, Object...) trace} level.
97       * 
98       * @param clazz the class on which the static method is defined; may not be null
99       * @param methodName the name of the method
100      * @throws SecurityException if there is a security exception while loading the class or getting the method
101      * @throws NoSuchMethodException if the method does not exist on the class
102      * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or
103      *         empty
104      * @see #addRuleParser(Method)
105      */
106     public void addRuleParser( Class<?> clazz,
107                                String methodName ) throws SecurityException, NoSuchMethodException {
108         CheckArg.isNotNull(clazz, "clazz");
109         CheckArg.isNotEmpty(methodName, "methodName");
110         parserMethods.add(clazz.getMethod(methodName, String.class, ExecutionContext.class));
111     }
112 
113     /**
114      * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition
115      * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an
116      * {@link ExecutionContext} environment reference as the second parameter, and should return the resulting {@link Rule} (or
117      * null if the definition format could not be understood by the method. Any exceptions during
118      * {@link Method#invoke(Object, Object...) invocation} will be logged at the
119      * {@link Logger#trace(Throwable, String, Object...) trace} level.
120      * 
121      * @param classLoader the class loader that should be used to load the class on which the method is defined; may not be null
122      * @param className the name of the class on which the static method is defined; may not be null
123      * @param methodName the name of the method
124      * @throws SecurityException if there is a security exception while loading the class or getting the method
125      * @throws NoSuchMethodException if the method does not exist on the class
126      * @throws ClassNotFoundException if the class could not be found given the supplied class loader
127      * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or
128      *         empty
129      * @see #addRuleParser(Method)
130      */
131     public void addRuleParser( ClassLoader classLoader,
132                                String className,
133                                String methodName ) throws SecurityException, NoSuchMethodException, ClassNotFoundException {
134         CheckArg.isNotNull(classLoader, "classLoader");
135         CheckArg.isNotEmpty(className, "className");
136         CheckArg.isNotEmpty(methodName, "methodName");
137         Class<?> clazz = Class.forName(className, true, classLoader);
138         parserMethods.add(clazz.getMethod(methodName, String.class, ExecutionContext.class));
139     }
140 
141     /**
142      * Remove the rule parser method.
143      * 
144      * @param method the method to remove
145      * @return true if the method was removed, or false if the method was not a registered rule parser method
146      */
147     public boolean removeRuleParser( Method method ) {
148         return parserMethods.remove(method);
149     }
150 
151     /**
152      * Remove the rule parser method.
153      * 
154      * @param declaringClassName the name of the class on which the static method is defined; may not be null
155      * @param methodName the name of the method
156      * @return true if the method was removed, or false if the method was not a registered rule parser method
157      * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or
158      *         empty
159      */
160     public boolean removeRuleParser( String declaringClassName,
161                                      String methodName ) {
162         CheckArg.isNotEmpty(declaringClassName, "declaringClassName");
163         CheckArg.isNotEmpty(methodName, "methodName");
164         for (Method method : parserMethods) {
165             if (method.getName().equals(methodName) && method.getDeclaringClass().getName().equals(declaringClassName)) {
166                 return parserMethods.remove(method);
167             }
168         }
169         return false;
170     }
171 
172     /**
173      * @return parserMethods
174      */
175     /*package*/List<Method> getParserMethods() {
176         return Collections.unmodifiableList(parserMethods);
177     }
178 
179     /**
180      * Parse the string form of a rule definition and return the rule
181      * 
182      * @param definition the definition of the rule that is to be parsed
183      * @param context the environment in which this method is being executed; may not be null
184      * @return the rule, or null if the definition could not be parsed
185      */
186     public Rule ruleFromString( String definition,
187                                 ExecutionContext context ) {
188         CheckArg.isNotNull(context, "env");
189         definition = definition != null ? definition.trim() : "";
190         if (definition.length() == 0) return null;
191         Logger logger = context.getLogger(getClass());
192         for (Method method : parserMethods) {
193             try {
194                 Rule rule = (Rule)method.invoke(null, definition, context);
195                 if (rule != null) {
196                     if (logger.isTraceEnabled()) {
197                         String msg = "Success parsing project rule definition \"{0}\" using {1}";
198                         logger.trace(msg, definition, method);
199                     }
200                     return rule;
201                 } else if (logger.isTraceEnabled()) {
202                     String msg = "Unable to parse project rule definition \"{0}\" using {1}";
203                     logger.trace(msg, definition, method);
204                 }
205             } catch (Throwable err) {
206                 String msg = "Error while parsing project rule definition \"{0}\" using {1}";
207                 logger.trace(err, msg, definition, method);
208             }
209         }
210         return null;
211     }
212 
213     /**
214      * Parse string forms of an arry of rule definitions and return the rules
215      * 
216      * @param context the environment in which this method is being executed; may not be null
217      * @param definitions the definition of the rules that are to be parsed
218      * @return the rule, or null if the definition could not be parsed
219      */
220     public Rule[] rulesFromStrings( ExecutionContext context,
221                                     String... definitions ) {
222         List<Rule> rules = new LinkedList<Rule>();
223         for (String definition : definitions) {
224             Rule rule = ruleFromString(definition, context);
225             if (rule != null) rules.add(rule);
226         }
227         return rules.toArray(new Rule[rules.size()]);
228     }
229 
230     /**
231      * Parse a single string containing one or more string forms of rule definitions, and return the rules. The string contains
232      * each rule on a separate line.
233      * 
234      * @param context the environment in which this method is being executed; may not be null
235      * @param definitions the definitions of the rules that are to be parsed, each definition separated by a newline character.
236      * @return the rule, or null if the definition could not be parsed
237      */
238     public Rule[] rulesFromString( ExecutionContext context,
239                                    String definitions ) {
240         List<String> lines = StringUtil.splitLines(definitions);
241         List<Rule> rules = new LinkedList<Rule>();
242         for (String definition : lines) {
243             Rule rule = ruleFromString(definition, context);
244             if (rule != null) rules.add(rule);
245         }
246         return rules.toArray(new Rule[rules.size()]);
247     }
248 }