1 /*
2 Copyright (C) 2007 Grid Systems, S.A.
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18 package com.gridsystems.nextgrid.api.pom.expr;
19
20 import java.io.Serializable;
21 import java.util.Iterator;
22 import java.util.Map;
23
24 import org.apache.commons.jxpath.JXPathContext;
25 import org.apache.commons.jxpath.Variables;
26
27 import com.gridsystems.nextgrid.api.env.AbstractTransientAttribute;
28
29 import nextgrid.api.env.ProcessEnvironment;
30 import nextgrid.api.pom.Expression;
31 import nextgrid.api.pom.ExpressionException;
32
33 /**
34 * XPath expression wrapper to simplify expression handling.
35 * <p>
36 * XPath is focused on retrieving and selecting nodes from an XML document.
37 * In this case we use its ability in the following way:
38 *
39 * <ul>
40 * <li>Jakarta Commons JXPath is used to use arbitrary Java objects as data
41 * sources, instead of plain XML files.</li>
42 * <li>We will consider that an XPath expression is equivalent to <tt>true</tt>
43 * if (and only if) it returns a minimum number of results, which is 1 by
44 * default.</li>
45 * </ul>
46 *
47 * @author Rodrigo Ruiz
48 */
49 public final class XPathExpression extends Expression {
50
51 /**
52 * <code>serialVersionUID</code> attribute.
53 */
54 private static final long serialVersionUID = -3368997983579800617L;
55
56 /**
57 * Minimum accepted number of returned objects.
58 */
59 private int minValues;
60
61 /**
62 * Maximum accepted number of returned objects.
63 */
64 private int maxValues;
65
66 /**
67 * Creates a new instance.
68 *
69 * @param text The XPath expression
70 * @throws ExpressionException If a compilation error occurs
71 */
72 public XPathExpression(String text) throws ExpressionException {
73 this(text, 1, Integer.MAX_VALUE);
74 }
75
76 /**
77 * Creates a new instance.
78 *
79 * @param text The XPath expression
80 * @param min The minimum number of results
81 * @param max The maximum number of results
82 * @throws ExpressionException If a compilation error occurs
83 */
84 public XPathExpression(String text, int min, int max) throws ExpressionException {
85 super("XPath", text);
86 this.minValues = min;
87 this.maxValues = max;
88 }
89
90 /**
91 * {@inheritDoc}
92 */
93 @Override public void validate() throws ExpressionException {
94 // TODO Implement this method
95 }
96
97 /**
98 * {@inheritDoc}
99 */
100 @SuppressWarnings("unchecked")
101 @Override public boolean boolEval(ProcessEnvironment env, Object o)
102 throws ExpressionException {
103 JXPathContext root = getRootContext(env);
104 JXPathContext context = JXPathContext.newContext(root, o);
105
106 context.setLenient(true);
107 Iterator it = context.iterate(this.getText());
108
109 int count = 0;
110 while (it.hasNext()) {
111 count++;
112 it.next();
113 }
114
115 return (count >= minValues) && (count <= maxValues);
116 }
117
118 /**
119 * Gets a shared context instance for the specified Process Environment.
120 * <p>
121 * This context will contain common variable definitions.
122 *
123 * @param env ProcessEnvironment for which to get a context
124 * @return The context
125 */
126 public synchronized JXPathContext getRootContext(ProcessEnvironment env) {
127 Serializable attr = env.getAttribute("XPathExpression.rootContext");
128 ContextHolder holder = null;
129 if (attr instanceof ContextHolder) {
130 holder = (ContextHolder)attr;
131 } else {
132 holder = new ContextHolder();
133 env.setAttribute("XPathExpression.rootContext", holder);
134 }
135
136 return holder.getValue(env);
137 }
138
139 /**
140 * Context attribute type. This class is a workaround for using
141 */
142 static class ContextHolder extends AbstractTransientAttribute<JXPathContext> {
143
144 /**
145 * <code>serialVersionUID</code> attribute.
146 */
147 private static final long serialVersionUID = -4973174396783312256L;
148
149 /**
150 * {@inheritDoc}
151 */
152 @Override public JXPathContext createValue(ProcessEnvironment env) {
153 JXPathContext ctx = JXPathContext.newContext(null);
154
155 // Declare global variables
156 Variables vars = ctx.getVariables();
157 for (Map.Entry<String, Serializable> entry : env.getAttributes().entrySet()) {
158 vars.declareVariable(entry.getKey(), entry.getValue());
159 }
160
161 // Declare utility constants
162 vars.declareVariable("true", Integer.valueOf(1));
163 vars.declareVariable("false", Integer.valueOf(0));
164
165 return ctx;
166 }
167 }
168 }