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 }