001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.filter; 018 019import java.io.IOException; 020import java.lang.reflect.Constructor; 021import java.lang.reflect.InvocationTargetException; 022import java.util.ArrayList; 023import java.util.List; 024import java.util.Map; 025import java.util.Properties; 026 027import javax.jms.JMSException; 028import javax.xml.XMLConstants; 029import javax.xml.parsers.DocumentBuilder; 030import javax.xml.parsers.DocumentBuilderFactory; 031import javax.xml.parsers.ParserConfigurationException; 032 033import org.apache.activemq.command.Message; 034import org.apache.activemq.util.JMSExceptionSupport; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038/** 039 * Used to evaluate an XPath Expression in a JMS selector. 040 */ 041public final class XPathExpression implements BooleanExpression { 042 043 private static final Logger LOG = LoggerFactory.getLogger(XPathExpression.class); 044 private static final String EVALUATOR_SYSTEM_PROPERTY = "org.apache.activemq.XPathEvaluatorClassName"; 045 private static final String DEFAULT_EVALUATOR_CLASS_NAME = "org.apache.activemq.filter.JAXPXPathEvaluator"; 046 public static final String DOCUMENT_BUILDER_FACTORY_FEATURE = "org.apache.activemq.documentBuilderFactory.feature"; 047 048 private static final Constructor EVALUATOR_CONSTRUCTOR; 049 private static DocumentBuilder builder = null; 050 051 static { 052 String cn = System.getProperty(EVALUATOR_SYSTEM_PROPERTY, DEFAULT_EVALUATOR_CLASS_NAME); 053 Constructor m = null; 054 try { 055 try { 056 m = getXPathEvaluatorConstructor(cn); 057 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); 058 builderFactory.setNamespaceAware(true); 059 builderFactory.setIgnoringElementContentWhitespace(true); 060 builderFactory.setIgnoringComments(true); 061 try { 062 // set some reasonable defaults 063 builderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); 064 builderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); 065 builderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); 066 builderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 067 } catch (ParserConfigurationException e) { 068 LOG.warn("Error setting document builder factory feature", e); 069 } 070 // setup the feature from the system property 071 setupFeatures(builderFactory); 072 builder = builderFactory.newDocumentBuilder(); 073 } catch (Throwable e) { 074 LOG.warn("Invalid " + XPathEvaluator.class.getName() + " implementation: " + cn + ", reason: " + e, e); 075 cn = DEFAULT_EVALUATOR_CLASS_NAME; 076 try { 077 m = getXPathEvaluatorConstructor(cn); 078 } catch (Throwable e2) { 079 LOG.error("Default XPath evaluator could not be loaded", e); 080 } 081 } 082 } finally { 083 EVALUATOR_CONSTRUCTOR = m; 084 } 085 } 086 087 private final String xpath; 088 private final XPathEvaluator evaluator; 089 090 public static interface XPathEvaluator { 091 boolean evaluate(Message message) throws JMSException; 092 } 093 094 XPathExpression(String xpath) { 095 this.xpath = xpath; 096 this.evaluator = createEvaluator(xpath); 097 } 098 099 private static Constructor getXPathEvaluatorConstructor(String cn) throws ClassNotFoundException, SecurityException, NoSuchMethodException { 100 Class c = XPathExpression.class.getClassLoader().loadClass(cn); 101 if (!XPathEvaluator.class.isAssignableFrom(c)) { 102 throw new ClassCastException("" + c + " is not an instance of " + XPathEvaluator.class); 103 } 104 return c.getConstructor(new Class[] {String.class, DocumentBuilder.class}); 105 } 106 107 protected static void setupFeatures(DocumentBuilderFactory factory) { 108 Properties properties = System.getProperties(); 109 List<String> features = new ArrayList<String>(); 110 for (Map.Entry<Object, Object> prop : properties.entrySet()) { 111 String key = (String) prop.getKey(); 112 if (key.startsWith(DOCUMENT_BUILDER_FACTORY_FEATURE)) { 113 String uri = key.split(DOCUMENT_BUILDER_FACTORY_FEATURE + ":")[1]; 114 Boolean value = Boolean.valueOf((String)prop.getValue()); 115 try { 116 factory.setFeature(uri, value); 117 features.add("feature " + uri + " value " + value); 118 } catch (ParserConfigurationException e) { 119 LOG.warn("DocumentBuilderFactory doesn't support the feature {} with value {}, due to {}.", new Object[]{uri, value, e}); 120 } 121 } 122 } 123 if (features.size() > 0) { 124 StringBuffer featureString = new StringBuffer(); 125 // just log the configured feature 126 for (String feature : features) { 127 if (featureString.length() != 0) { 128 featureString.append(", "); 129 } 130 featureString.append(feature); 131 } 132 } 133 134 } 135 136 private XPathEvaluator createEvaluator(String xpath2) { 137 try { 138 return (XPathEvaluator)EVALUATOR_CONSTRUCTOR.newInstance(new Object[] {xpath, builder}); 139 } catch (InvocationTargetException e) { 140 Throwable cause = e.getCause(); 141 if (cause instanceof RuntimeException) { 142 throw (RuntimeException)cause; 143 } 144 throw new RuntimeException("Invalid XPath Expression: " + xpath + " reason: " + e.getMessage(), e); 145 } catch (Throwable e) { 146 throw new RuntimeException("Invalid XPath Expression: " + xpath + " reason: " + e.getMessage(), e); 147 } 148 } 149 150 public Object evaluate(MessageEvaluationContext message) throws JMSException { 151 try { 152 if (message.isDropped()) { 153 return null; 154 } 155 return evaluator.evaluate(message.getMessage()) ? Boolean.TRUE : Boolean.FALSE; 156 } catch (IOException e) { 157 throw JMSExceptionSupport.create(e); 158 } 159 160 } 161 162 public String toString() { 163 return "XPATH " + ConstantExpression.encodeString(xpath); 164 } 165 166 /** 167 * @param message 168 * @return true if the expression evaluates to Boolean.TRUE. 169 * @throws JMSException 170 */ 171 public boolean matches(MessageEvaluationContext message) throws JMSException { 172 Object object = evaluate(message); 173 return object != null && object == Boolean.TRUE; 174 } 175 176}