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    }