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