001 /*
002 * JBoss DNA (http://www.jboss.org/dna)
003 * See the COPYRIGHT.txt file distributed with this work for information
004 * regarding copyright ownership. Some portions may be licensed
005 * to Red Hat, Inc. under one or more contributor license agreements.
006 * See the AUTHORS.txt file in the distribution for a full listing of
007 * individual contributors.
008 *
009 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010 * is licensed to you under the terms of the GNU Lesser General Public License as
011 * published by the Free Software Foundation; either version 2.1 of
012 * the License, or (at your option) any later version.
013 *
014 * JBoss DNA is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017 * Lesser General Public License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this software; if not, write to the Free
021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023 */
024 package org.jboss.dna.graph.connector.federation;
025
026 import java.lang.reflect.Method;
027 import java.util.Collections;
028 import java.util.LinkedList;
029 import java.util.List;
030 import java.util.concurrent.CopyOnWriteArrayList;
031 import net.jcip.annotations.ThreadSafe;
032 import org.jboss.dna.common.text.TextEncoder;
033 import org.jboss.dna.common.util.CheckArg;
034 import org.jboss.dna.common.util.Logger;
035 import org.jboss.dna.common.util.StringUtil;
036 import org.jboss.dna.graph.ExecutionContext;
037 import org.jboss.dna.graph.GraphI18n;
038 import org.jboss.dna.graph.connector.federation.Projection.Rule;
039 import org.jboss.dna.graph.property.NamespaceRegistry;
040
041 /**
042 * A parser library for {@link Projection projections} and {@link Projection.Rule projection rules}.
043 */
044 @ThreadSafe
045 public class ProjectionParser {
046 private static final ProjectionParser INSTANCE;
047
048 static {
049 INSTANCE = new ProjectionParser();
050 try {
051 INSTANCE.addRuleParser(Projection.class, "parsePathRule");
052 assert INSTANCE.parserMethods.size() == 1;
053 } catch (Throwable err) {
054 Logger.getLogger(Projection.class).error(err, GraphI18n.errorAddingProjectionRuleParseMethod);
055 }
056 }
057
058 /**
059 * Get the shared projection parser, which is by default populated with the standard parser rules.
060 *
061 * @return the parser; never null
062 */
063 public static ProjectionParser getInstance() {
064 return INSTANCE;
065 }
066
067 private final List<Method> parserMethods = new CopyOnWriteArrayList<Method>();
068
069 public ProjectionParser() {
070 }
071
072 /**
073 * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition
074 * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an
075 * {@link ExecutionContext} environment reference as the second parameter, and should return the resulting {@link Rule} (or
076 * null if the definition format could not be understood by the method. Any exceptions during
077 * {@link Method#invoke(Object, Object...) invocation} will be logged at the
078 * {@link Logger#trace(Throwable, String, Object...) trace} level.
079 *
080 * @param method the method to be added
081 * @see #addRuleParser(ClassLoader, String, String)
082 */
083 public void addRuleParser( Method method ) {
084 if (method != null) parserMethods.add(method);
085 }
086
087 /**
088 * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition
089 * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an
090 * {@link ExecutionContext} environment reference as the second parameter, and should return the resulting {@link Rule} (or
091 * null if the definition format could not be understood by the method. Any exceptions during
092 * {@link Method#invoke(Object, Object...) invocation} will be logged at the
093 * {@link Logger#trace(Throwable, String, Object...) trace} level.
094 *
095 * @param clazz the class on which the static method is defined; may not be null
096 * @param methodName the name of the method
097 * @throws SecurityException if there is a security exception while loading the class or getting the method
098 * @throws NoSuchMethodException if the method does not exist on the class
099 * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or
100 * empty
101 * @see #addRuleParser(Method)
102 */
103 public void addRuleParser( Class<?> clazz,
104 String methodName ) throws SecurityException, NoSuchMethodException {
105 CheckArg.isNotNull(clazz, "clazz");
106 CheckArg.isNotEmpty(methodName, "methodName");
107 parserMethods.add(clazz.getMethod(methodName, String.class, ExecutionContext.class));
108 }
109
110 /**
111 * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition
112 * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an
113 * {@link ExecutionContext} environment reference as the second parameter, and should return the resulting {@link Rule} (or
114 * null if the definition format could not be understood by the method. Any exceptions during
115 * {@link Method#invoke(Object, Object...) invocation} will be logged at the
116 * {@link Logger#trace(Throwable, String, Object...) trace} level.
117 *
118 * @param classLoader the class loader that should be used to load the class on which the method is defined; may not be null
119 * @param className the name of the class on which the static method is defined; may not be null
120 * @param methodName the name of the method
121 * @throws SecurityException if there is a security exception while loading the class or getting the method
122 * @throws NoSuchMethodException if the method does not exist on the class
123 * @throws ClassNotFoundException if the class could not be found given the supplied class loader
124 * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or
125 * empty
126 * @see #addRuleParser(Method)
127 */
128 public void addRuleParser( ClassLoader classLoader,
129 String className,
130 String methodName ) throws SecurityException, NoSuchMethodException, ClassNotFoundException {
131 CheckArg.isNotNull(classLoader, "classLoader");
132 CheckArg.isNotEmpty(className, "className");
133 CheckArg.isNotEmpty(methodName, "methodName");
134 Class<?> clazz = Class.forName(className, true, classLoader);
135 parserMethods.add(clazz.getMethod(methodName, String.class, ExecutionContext.class));
136 }
137
138 /**
139 * Remove the rule parser method.
140 *
141 * @param method the method to remove
142 * @return true if the method was removed, or false if the method was not a registered rule parser method
143 */
144 public boolean removeRuleParser( Method method ) {
145 return parserMethods.remove(method);
146 }
147
148 /**
149 * Remove the rule parser method.
150 *
151 * @param declaringClassName the name of the class on which the static method is defined; may not be null
152 * @param methodName the name of the method
153 * @return true if the method was removed, or false if the method was not a registered rule parser method
154 * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or
155 * empty
156 */
157 public boolean removeRuleParser( String declaringClassName,
158 String methodName ) {
159 CheckArg.isNotEmpty(declaringClassName, "declaringClassName");
160 CheckArg.isNotEmpty(methodName, "methodName");
161 for (Method method : parserMethods) {
162 if (method.getName().equals(methodName) && method.getDeclaringClass().getName().equals(declaringClassName)) {
163 return parserMethods.remove(method);
164 }
165 }
166 return false;
167 }
168
169 /**
170 * @return parserMethods
171 */
172 /*package*/List<Method> getParserMethods() {
173 return Collections.unmodifiableList(parserMethods);
174 }
175
176 /**
177 * Parse the string form of a rule definition and return the rule
178 *
179 * @param definition the definition of the rule that is to be parsed
180 * @param context the environment in which this method is being executed; may not be null
181 * @return the rule, or null if the definition could not be parsed
182 */
183 public Rule ruleFromString( String definition,
184 ExecutionContext context ) {
185 CheckArg.isNotNull(context, "env");
186 definition = definition != null ? definition.trim() : "";
187 if (definition.length() == 0) return null;
188 Logger logger = context.getLogger(getClass());
189 for (Method method : parserMethods) {
190 try {
191 Rule rule = (Rule)method.invoke(null, definition, context);
192 if (rule != null) {
193 if (logger.isTraceEnabled()) {
194 String msg = "Success parsing project rule definition \"{0}\" using {1}";
195 logger.trace(msg, definition, method);
196 }
197 return rule;
198 } else if (logger.isTraceEnabled()) {
199 String msg = "Unable to parse project rule definition \"{0}\" using {1}";
200 logger.trace(msg, definition, method);
201 }
202 } catch (Throwable err) {
203 String msg = "Error while parsing project rule definition \"{0}\" using {1}";
204 logger.trace(err, msg, definition, method);
205 }
206 }
207 return null;
208 }
209
210 /**
211 * Parse string forms of an arry of rule definitions and return the rules
212 *
213 * @param context the environment in which this method is being executed; may not be null
214 * @param definitions the definition of the rules that are to be parsed
215 * @return the rule, or null if the definition could not be parsed
216 */
217 public Rule[] rulesFromStrings( ExecutionContext context,
218 String... definitions ) {
219 List<Rule> rules = new LinkedList<Rule>();
220 for (String definition : definitions) {
221 Rule rule = ruleFromString(definition, context);
222 if (rule != null) rules.add(rule);
223 }
224 return rules.toArray(new Rule[rules.size()]);
225 }
226
227 /**
228 * Parse a single string containing one or more string forms of rule definitions, and return the rules. The string contains
229 * each rule on a separate line.
230 *
231 * @param context the environment in which this method is being executed; may not be null
232 * @param definitions the definitions of the rules that are to be parsed, each definition separated by a newline character.
233 * @return the rule, or null if the definition could not be parsed
234 */
235 public Rule[] rulesFromString( ExecutionContext context,
236 String definitions ) {
237 List<String> lines = StringUtil.splitLines(definitions);
238 List<Rule> rules = new LinkedList<Rule>();
239 for (String definition : lines) {
240 Rule rule = ruleFromString(definition, context);
241 if (rule != null) rules.add(rule);
242 }
243 return rules.toArray(new Rule[rules.size()]);
244 }
245 }