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 }