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.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 org.jboss.dna.common.text.TextEncoder;
032 import org.jboss.dna.common.util.CheckArg;
033 import org.jboss.dna.common.util.Logger;
034 import org.jboss.dna.common.util.StringUtil;
035 import org.jboss.dna.connector.federation.Projection.Rule;
036 import org.jboss.dna.graph.ExecutionContext;
037 import org.jboss.dna.graph.property.NamespaceRegistry;
038
039 /**
040 * A parser library for {@link Projection projections} and {@link Projection.Rule projection rules}.
041 *
042 * @author Randall Hauch
043 */
044 public class ProjectionParser {
045 private static final ProjectionParser INSTANCE;
046
047 static {
048 INSTANCE = new ProjectionParser();
049 try {
050 INSTANCE.addRuleParser(Projection.class, "parsePathRule");
051 assert INSTANCE.parserMethods.size() == 1;
052 } catch (Throwable err) {
053 Logger.getLogger(Projection.class).error(err, FederationI18n.errorAddingProjectionRuleParseMethod);
054 }
055 }
056
057 /**
058 * Get the shared projection parser, which is by default populated with the standard parser rules.
059 *
060 * @return the parser; never null
061 */
062 public static ProjectionParser getInstance() {
063 return INSTANCE;
064 }
065
066 private final List<Method> parserMethods = new CopyOnWriteArrayList<Method>();
067
068 public ProjectionParser() {
069 }
070
071 /**
072 * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition
073 * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an
074 * {@link ExecutionContext} environment reference as the second parameter, and should return the resulting {@link Rule} (or
075 * null if the definition format could not be understood by the method. Any exceptions during
076 * {@link Method#invoke(Object, Object...) invocation} will be logged at the
077 * {@link Logger#trace(Throwable, String, Object...) trace} level.
078 *
079 * @param method the method to be added
080 * @see #addRuleParser(ClassLoader, String, String)
081 */
082 public void addRuleParser( Method method ) {
083 if (method != null) parserMethods.add(method);
084 }
085
086 /**
087 * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition
088 * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an
089 * {@link ExecutionContext} environment reference as the second parameter, and should return the resulting {@link Rule} (or
090 * null if the definition format could not be understood by the method. Any exceptions during
091 * {@link Method#invoke(Object, Object...) invocation} will be logged at the
092 * {@link Logger#trace(Throwable, String, Object...) trace} level.
093 *
094 * @param clazz the class on which the static method is defined; may not be null
095 * @param methodName the name of the method
096 * @throws SecurityException if there is a security exception while loading the class or getting the method
097 * @throws NoSuchMethodException if the method does not exist on the class
098 * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or
099 * empty
100 * @see #addRuleParser(Method)
101 */
102 public void addRuleParser( Class<?> clazz,
103 String methodName ) throws SecurityException, NoSuchMethodException {
104 CheckArg.isNotNull(clazz, "clazz");
105 CheckArg.isNotEmpty(methodName, "methodName");
106 parserMethods.add(clazz.getMethod(methodName, String.class, ExecutionContext.class));
107 }
108
109 /**
110 * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition
111 * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an
112 * {@link ExecutionContext} environment reference as the second parameter, and should return the resulting {@link Rule} (or
113 * null if the definition format could not be understood by the method. Any exceptions during
114 * {@link Method#invoke(Object, Object...) invocation} will be logged at the
115 * {@link Logger#trace(Throwable, String, Object...) trace} level.
116 *
117 * @param classLoader the class loader that should be used to load the class on which the method is defined; may not be null
118 * @param className the name of the class on which the static method is defined; may not be null
119 * @param methodName the name of the method
120 * @throws SecurityException if there is a security exception while loading the class or getting the method
121 * @throws NoSuchMethodException if the method does not exist on the class
122 * @throws ClassNotFoundException if the class could not be found given the supplied class loader
123 * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or
124 * empty
125 * @see #addRuleParser(Method)
126 */
127 public void addRuleParser( ClassLoader classLoader,
128 String className,
129 String methodName ) throws SecurityException, NoSuchMethodException, ClassNotFoundException {
130 CheckArg.isNotNull(classLoader, "classLoader");
131 CheckArg.isNotEmpty(className, "className");
132 CheckArg.isNotEmpty(methodName, "methodName");
133 Class<?> clazz = Class.forName(className, true, classLoader);
134 parserMethods.add(clazz.getMethod(methodName, String.class, ExecutionContext.class));
135 }
136
137 /**
138 * Remove the rule parser method.
139 *
140 * @param method the method to remove
141 * @return true if the method was removed, or false if the method was not a registered rule parser method
142 */
143 public boolean removeRuleParser( Method method ) {
144 return parserMethods.remove(method);
145 }
146
147 /**
148 * Remove the rule parser method.
149 *
150 * @param declaringClassName the name of the class on which the static method is defined; may not be null
151 * @param methodName the name of the method
152 * @return true if the method was removed, or false if the method was not a registered rule parser method
153 * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or
154 * empty
155 */
156 public boolean removeRuleParser( String declaringClassName,
157 String methodName ) {
158 CheckArg.isNotEmpty(declaringClassName, "declaringClassName");
159 CheckArg.isNotEmpty(methodName, "methodName");
160 for (Method method : parserMethods) {
161 if (method.getName().equals(methodName) && method.getDeclaringClass().getName().equals(declaringClassName)) {
162 return parserMethods.remove(method);
163 }
164 }
165 return false;
166 }
167
168 /**
169 * @return parserMethods
170 */
171 /*package*/List<Method> getParserMethods() {
172 return Collections.unmodifiableList(parserMethods);
173 }
174
175 /**
176 * Parse the string form of a rule definition and return the rule
177 *
178 * @param definition the definition of the rule that is to be parsed
179 * @param context the environment in which this method is being executed; may not be null
180 * @return the rule, or null if the definition could not be parsed
181 */
182 public Rule ruleFromString( String definition,
183 ExecutionContext context ) {
184 CheckArg.isNotNull(context, "env");
185 definition = definition != null ? definition.trim() : "";
186 if (definition.length() == 0) return null;
187 Logger logger = context.getLogger(getClass());
188 for (Method method : parserMethods) {
189 try {
190 Rule rule = (Rule)method.invoke(null, definition, context);
191 if (rule != null) {
192 if (logger.isTraceEnabled()) {
193 String msg = "Success parsing project rule definition \"{0}\" using {1}";
194 logger.trace(msg, definition, method);
195 }
196 return rule;
197 } else if (logger.isTraceEnabled()) {
198 String msg = "Unable to parse project rule definition \"{0}\" using {1}";
199 logger.trace(msg, definition, method);
200 }
201 } catch (Throwable err) {
202 String msg = "Error while parsing project rule definition \"{0}\" using {1}";
203 logger.trace(err, msg, definition, method);
204 }
205 }
206 return null;
207 }
208
209 /**
210 * Parse string forms of an arry of rule definitions and return the rules
211 *
212 * @param context the environment in which this method is being executed; may not be null
213 * @param definitions the definition of the rules that are to be parsed
214 * @return the rule, or null if the definition could not be parsed
215 */
216 public Rule[] rulesFromStrings( ExecutionContext context,
217 String... definitions ) {
218 List<Rule> rules = new LinkedList<Rule>();
219 for (String definition : definitions) {
220 Rule rule = ruleFromString(definition, context);
221 if (rule != null) rules.add(rule);
222 }
223 return rules.toArray(new Rule[rules.size()]);
224 }
225
226 /**
227 * Parse a single string containing one or more string forms of rule definitions, and return the rules. The string contains
228 * each rule on a separate line.
229 *
230 * @param context the environment in which this method is being executed; may not be null
231 * @param definitions the definitions of the rules that are to be parsed, each definition separated by a newline character.
232 * @return the rule, or null if the definition could not be parsed
233 */
234 public Rule[] rulesFromString( ExecutionContext context,
235 String definitions ) {
236 List<String> lines = StringUtil.splitLines(definitions);
237 List<Rule> rules = new LinkedList<Rule>();
238 for (String definition : lines) {
239 Rule rule = ruleFromString(definition, context);
240 if (rule != null) rules.add(rule);
241 }
242 return rules.toArray(new Rule[rules.size()]);
243 }
244 }