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