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: */
017: package org.apache.cocoon.components.treeprocessor.variables;
018:
019: import org.apache.avalon.framework.activity.Disposable;
020: import org.apache.avalon.framework.component.ComponentManager;
021: import org.apache.avalon.framework.configuration.ConfigurationException;
022: import org.apache.avalon.framework.service.ServiceException;
023: import org.apache.avalon.framework.service.ServiceManager;
024: import org.apache.avalon.framework.service.ServiceSelector;
025: import org.apache.avalon.framework.service.WrapperServiceManager;
026: import org.apache.avalon.framework.thread.ThreadSafe;
027:
028: import org.apache.cocoon.components.modules.input.InputModule;
029: import org.apache.cocoon.components.treeprocessor.InvokeContext;
030: import org.apache.cocoon.sitemap.PatternException;
031: import org.apache.commons.lang.builder.HashCodeBuilder;
032:
033: import java.util.ArrayList;
034: import java.util.Iterator;
035: import java.util.List;
036: import java.util.Map;
037: import java.util.Stack;
038:
039: /**
040: * Prepared implementation of {@link VariableResolver} for fast evaluation.
041: *
042: * @author <a href="mailto:uv@upaya.co.uk">Upayavira</a>
043: * @version CVS $Id: PreparedVariableResolver.java 433543 2006-08-22 06:22:54Z crossley $
044: */
045: final public class PreparedVariableResolver extends VariableResolver
046: implements Disposable {
047:
048: private ServiceManager manager;
049: private ServiceSelector selector;
050: protected List tokens;
051: protected boolean needsMapStack;
052:
053: private static final int OPEN = -2;
054: private static final int CLOSE = -3;
055: private static final int COLON = -4;
056: private static final int TEXT = -5;
057: private static final int EXPR = -7;
058: private static final int SITEMAP_VAR = -9;
059: private static final int THREADSAFE_MODULE = -10;
060: private static final int STATEFUL_MODULE = -11;
061: private static final int ROOT_SITEMAP_VARIABLE = 0;
062: private static final int ANCHOR_VAR = -1;
063:
064: private static Token COLON_TOKEN = new Token(COLON);
065: private static Token OPEN_TOKEN = new Token(OPEN);
066: private static Token CLOSE_TOKEN = new Token(CLOSE);
067: private static Token EMPTY_TOKEN = new Token(EXPR);
068:
069: /**
070: * @deprecated use the version with <code>ServiceManager</service>
071: */
072: public PreparedVariableResolver(String expr,
073: ComponentManager manager) throws PatternException {
074: this (expr, new WrapperServiceManager(manager));
075: }
076:
077: public PreparedVariableResolver(String expr, ServiceManager manager)
078: throws PatternException {
079: super (expr);
080: this .manager = manager;
081: this .tokens = new ArrayList();
082:
083: VariableExpressionTokenizer.tokenize(expr,
084: new VariableExpressionTokenizer.TokenReciever() {
085: public void addToken(int type, String value)
086: throws PatternException {
087: switch (type) {
088: case VariableExpressionTokenizer.TokenReciever.COLON:
089: tokens.add(COLON_TOKEN);
090: break;
091: case VariableExpressionTokenizer.TokenReciever.OPEN:
092: tokens.add(OPEN_TOKEN);
093: break;
094: case VariableExpressionTokenizer.TokenReciever.CLOSE:
095: tokens.add(CLOSE_TOKEN);
096: break;
097: case VariableExpressionTokenizer.TokenReciever.TEXT:
098: tokens.add(new Token(value));
099: break;
100: case VariableExpressionTokenizer.TokenReciever.MODULE:
101: Token token;
102: if (value.equals("sitemap")) {
103: // Explicit prefix for sitemap variable
104: needsMapStack = true;
105: token = new Token(SITEMAP_VAR);
106: } else if (value.startsWith("#")) {
107: // anchor syntax refering to a name result level
108: needsMapStack = true;
109: token = new Token(ANCHOR_VAR, value
110: .substring(1));
111: } else {
112: // Module used
113: token = getNewModuleToken(value);
114: }
115: tokens.add(token);
116: break;
117: case VariableExpressionTokenizer.TokenReciever.VARIABLE:
118: needsMapStack = true;
119: tokens.add(getNewVariableToken(value));
120: break;
121: default:
122: throw new IllegalArgumentException(
123: "Unknown token type: " + type);
124: }
125: }
126: });
127: }
128:
129: private Token getNewVariableToken(String variable) {
130: if (variable.startsWith("/")) {
131: return new Token(ROOT_SITEMAP_VARIABLE, variable
132: .substring(1));
133: } else {
134: // Find level
135: int level = 1; // Start at 1 since it will be substracted from list.size()
136: int pos = 0;
137: while (variable.startsWith("../", pos)) {
138: level++;
139: pos += "../".length();
140: }
141: return new Token(level, variable.substring(pos));
142: }
143: }
144:
145: private Token getNewModuleToken(String moduleName)
146: throws PatternException {
147: if (this .selector == null) {
148: try {
149: // First access to a module : lookup selector
150: this .selector = (ServiceSelector) this .manager
151: .lookup(InputModule.ROLE + "Selector");
152: } catch (ServiceException ce) {
153: throw new PatternException(
154: "Cannot access input modules selector", ce);
155: }
156: }
157:
158: // Get the module
159: InputModule module;
160: try {
161: module = (InputModule) this .selector.select(moduleName);
162: } catch (ServiceException e) {
163: throw new PatternException("Cannot get module named '"
164: + moduleName + "' in expression '"
165: + this .originalExpr + "'", e);
166: }
167:
168: Token token;
169: // Is this module threadsafe ?
170: if (module instanceof ThreadSafe) {
171: token = new Token(THREADSAFE_MODULE, module);
172: } else {
173: // Stateful module : release it and get a new one each time
174: this .selector.release(module);
175: token = new Token(STATEFUL_MODULE, moduleName);
176: }
177: return token;
178: }
179:
180: public final String resolve(InvokeContext context, Map objectModel)
181: throws PatternException {
182: List mapStack = null; // get the stack only when necessary - lazy inside the loop
183: int stackSize = 0;
184:
185: if (needsMapStack) {
186: if (context == null) {
187: throw new PatternException(
188: "Need an invoke context to resolve " + this );
189: }
190: mapStack = context.getMapStack();
191: stackSize = mapStack.size();
192: }
193:
194: Stack stack = new Stack();
195:
196: for (Iterator i = tokens.iterator(); i.hasNext();) {
197: Token token = (Token) i.next();
198: Token last;
199: switch (token.getType()) {
200: case TEXT:
201: if (stack.empty()) {
202: stack.push(new Token(EXPR, token.getStringValue()));
203: } else {
204: last = (Token) stack.peek();
205: if (last.hasType(EXPR)) {
206: last.merge(token);
207: } else {
208: stack.push(new Token(EXPR, token
209: .getStringValue()));
210: }
211: }
212: break;
213: case CLOSE:
214: Token expr = (Token) stack.pop();
215: Token lastButOne = (Token) stack.pop();
216: Token result;
217: if (expr.hasType(COLON)) { // i.e. nothing was specified after the colon
218: stack.pop(); // Pop the OPEN
219: result = processModule(lastButOne, EMPTY_TOKEN,
220: objectModel, context, mapStack, stackSize);
221: } else if (lastButOne.hasType(COLON)) {
222: Token module = (Token) stack.pop();
223: stack.pop(); // Pop the OPEN
224: result = processModule(module, expr, objectModel,
225: context, mapStack, stackSize);
226: } else {
227: result = processVariable(expr, mapStack, stackSize);
228: }
229: if (stack.empty()) {
230: stack.push(result);
231: } else {
232: last = (Token) stack.peek();
233: if (last.hasType(EXPR)) {
234: last.merge(result);
235: } else {
236: stack.push(result);
237: }
238: }
239: break;
240: case OPEN:
241: case COLON:
242: case ANCHOR_VAR:
243: case THREADSAFE_MODULE:
244: case STATEFUL_MODULE:
245: case ROOT_SITEMAP_VARIABLE:
246: default: {
247: stack.push(token);
248: break;
249: }
250: }
251: }
252: if (stack.size() != 1) {
253: throw new PatternException(
254: "Evaluation error in expression: " + originalExpr);
255: }
256: return ((Token) stack.pop()).getStringValue();
257: }
258:
259: private Token processModule(Token module, Token expr,
260: Map objectModel, InvokeContext context, List mapStack,
261: int stackSize) throws PatternException {
262: int type = module.getType();
263:
264: if (type == ANCHOR_VAR) {
265: Map levelResult = context.getMapByAnchor(module
266: .getStringValue());
267:
268: if (levelResult == null) {
269: throw new PatternException("Error while evaluating '"
270: + this .originalExpr + "' : no anchor '"
271: + String.valueOf(module.getStringValue())
272: + "' found in context");
273: }
274:
275: Object result = levelResult.get(expr.getStringValue());
276: return new Token(EXPR, result == null ? "" : result
277: .toString());
278: } else if (type == THREADSAFE_MODULE) {
279: try {
280: InputModule im = module.getModule();
281: Object result = im.getAttribute(expr.getStringValue(),
282: null, objectModel);
283: return new Token(EXPR, result == null ? "" : result
284: .toString());
285:
286: } catch (ConfigurationException confEx) {
287: throw new PatternException("Cannot get variable '"
288: + expr.getStringValue() + "' in expression '"
289: + this .originalExpr + "'", confEx);
290: }
291:
292: } else if (type == STATEFUL_MODULE) {
293: InputModule im = null;
294: String moduleName = module.getStringValue();
295: try {
296: im = (InputModule) this .selector.select(moduleName);
297:
298: Object result = im.getAttribute(expr.getStringValue(),
299: null, objectModel);
300: return new Token(EXPR, result == null ? "" : result
301: .toString());
302:
303: } catch (ServiceException e) {
304: throw new PatternException("Cannot get module '"
305: + moduleName + "' in expression '"
306: + this .originalExpr + "'", e);
307:
308: } catch (ConfigurationException confEx) {
309: throw new PatternException("Cannot get variable '"
310: + expr.getStringValue() + "' in expression '"
311: + this .originalExpr + "'", confEx);
312:
313: } finally {
314: this .selector.release(im);
315: }
316: } else if (type == SITEMAP_VAR) {
317: // Prefixed sitemap variable must be parsed at runtime
318: String variable = expr.getStringValue();
319: Token token;
320: if (variable.startsWith("/")) {
321: token = new Token(ROOT_SITEMAP_VARIABLE, variable
322: .substring(1));
323: } else {
324: // Find level
325: int level = 1; // Start at 1 since it will be substracted from list.size()
326: int pos = 0;
327: while (variable.startsWith("../", pos)) {
328: level++;
329: pos += "../".length();
330: }
331: token = new Token(level, variable.substring(pos));
332: }
333: return processVariable(token, mapStack, stackSize);
334: } else {
335: throw new PatternException("Unknown token type: "
336: + expr.getType());
337: }
338: }
339:
340: private Token processVariable(Token expr, List mapStack,
341: int stackSize) throws PatternException {
342: int type = expr.getType();
343: String value = expr.getStringValue();
344: if (type == ROOT_SITEMAP_VARIABLE) {
345: Object result = ((Map) mapStack.get(0)).get(value);
346: return new Token(EXPR, result == null ? "" : result
347: .toString());
348: } else {
349: // relative sitemap variable
350: if (type > stackSize) {
351: throw new PatternException("Error while evaluating '"
352: + this .originalExpr + "' : not so many levels");
353: }
354:
355: Object result = ((Map) mapStack.get(stackSize - type))
356: .get(value);
357: return new Token(EXPR, result == null ? "" : result
358: .toString());
359: }
360: }
361:
362: public final void dispose() {
363: if (this .selector != null) {
364: for (Iterator i = tokens.iterator(); i.hasNext();) {
365: Token token = (Token) i.next();
366: if (token.hasType(THREADSAFE_MODULE)) {
367: InputModule im = token.getModule();
368: this .selector.release(im);
369: }
370: }
371: this .manager.release(this .selector);
372: this .selector = null;
373: this .manager = null;
374: }
375: }
376:
377: private static class Token {
378:
379: private Object value;
380: private int type;
381:
382: public Token(int type) {
383: if (type == EXPR) {
384: this .value = "";
385: } else {
386: this .value = null;
387: }
388: this .type = type;
389: }
390:
391: public Token(int type, String value) {
392: this .value = value;
393: this .type = type;
394: }
395:
396: public Token(int type, InputModule module) {
397: this .value = module;
398: this .type = type;
399: }
400:
401: public Token(String value) {
402: this .type = TEXT;
403: this .value = value;
404: }
405:
406: public int getType() {
407: return type;
408: }
409:
410: public String getStringValue() {
411: if (value instanceof String) {
412: return (String) this .value;
413: } else {
414: return null;
415: }
416: }
417:
418: public boolean hasType(int type) {
419: return this .type == type;
420: }
421:
422: public boolean equals(Object o) {
423: if (o instanceof Token) {
424: return ((Token) o).hasType(this .type);
425: } else {
426: return false;
427: }
428: }
429:
430: public int hashCode() {
431: return new HashCodeBuilder().append(value)
432: .append(this .type).toHashCode();
433: }
434:
435: public void merge(Token newToken) {
436: this .value = this .value + newToken.getStringValue();
437: }
438:
439: public InputModule getModule() {
440: if (value instanceof InputModule) {
441: return (InputModule) value;
442: } else {
443: return null;
444: }
445: }
446: }
447: }
|