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    }