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.sitemap;
018:
019: import java.util.Arrays;
020: import java.util.Collection;
021: import java.util.Collections;
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import java.util.Map;
026: import java.util.Set;
027:
028: import org.apache.avalon.excalibur.logger.LoggerManager;
029: import org.apache.avalon.framework.component.ComponentManager;
030: import org.apache.avalon.framework.configuration.Configuration;
031: import org.apache.avalon.framework.configuration.ConfigurationException;
032: import org.apache.avalon.framework.configuration.DefaultConfiguration;
033: import org.apache.cocoon.components.CocoonComponentManager;
034: import org.apache.cocoon.components.treeprocessor.CategoryNode;
035: import org.apache.cocoon.components.treeprocessor.CategoryNodeBuilder;
036: import org.apache.cocoon.components.treeprocessor.DefaultTreeBuilder;
037: import org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory;
038: import org.apache.cocoon.generation.Generator;
039: import org.apache.cocoon.serialization.Serializer;
040: import org.apache.cocoon.sitemap.PatternException;
041: import org.apache.cocoon.sitemap.SitemapComponentSelector;
042: import org.apache.cocoon.util.StringUtils;
043: import org.apache.regexp.RE;
044:
045: /**
046: * The tree builder for the sitemap language.
047: *
048: * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
049: * @version CVS $Id: SitemapLanguage.java 433543 2006-08-22 06:22:54Z crossley $
050: */
051:
052: public class SitemapLanguage extends DefaultTreeBuilder {
053:
054: // Regexp's for splitting expressions
055: private static final String COMMA_SPLIT_REGEXP = "[\\s]*,[\\s]*";
056: private static final String EQUALS_SPLIT_REGEXP = "[\\s]*=[\\s]*";
057:
058: /**
059: * Build a component manager with the contents of the <map:components> element of
060: * the tree.
061: */
062: protected ComponentManager createComponentManager(Configuration tree)
063: throws Exception {
064:
065: // Get the map:component node
066: // Don't check namespace here : this will be done by node builders
067: Configuration config = tree.getChild("components", false);
068:
069: if (config == null) {
070: if (getLogger().isDebugEnabled()) {
071: getLogger().debug(
072: "Sitemap has no components definition at "
073: + tree.getLocation());
074: }
075: config = new DefaultConfiguration("", "");
076: }
077:
078: final CocoonComponentManager manager = new CocoonComponentManager(
079: this .parentManager);
080:
081: manager.enableLogging(getLogger());
082:
083: final LoggerManager loggerManager = (LoggerManager) this .parentManager
084: .lookup(LoggerManager.ROLE);
085: manager.setLoggerManager(loggerManager);
086:
087: if (null != this .context) {
088: manager.contextualize(this .context);
089: }
090:
091: if (null != this .roleManager) {
092: manager.setRoleManager(this .roleManager);
093: }
094:
095: manager.configure(config);
096: manager.initialize();
097:
098: return manager;
099: }
100:
101: //---- Views management
102:
103: /** Collection of view names for each label */
104: private Map labelViews = new HashMap();
105:
106: /** The views CategoryNode */
107: private CategoryNode viewsNode;
108:
109: /** Are we currently building a view ? */
110: private boolean isBuildingView = false;
111:
112: /** Are we currently building a view ? */
113: private boolean isBuildingErrorHandler = false;
114:
115: /**
116: * Pseudo-label for views <code>from-position="first"</code> (i.e. generator).
117: */
118: public static final String FIRST_POS_LABEL = "!first!";
119:
120: /**
121: * Pseudo-label for views <code>from-position="last"</code> (i.e. serializer).
122: */
123: public static final String LAST_POS_LABEL = "!last!";
124:
125: public void recycle() {
126: super .recycle();
127:
128: // Go back to initial state
129: this .labelViews.clear();
130: this .viewsNode = null;
131: this .isBuildingView = false;
132: this .isBuildingErrorHandler = false;
133: }
134:
135: /**
136: * Set to <code>true</code> while building the internals of a <map:view>
137: */
138: public void setBuildingView(boolean building) {
139: this .isBuildingView = building;
140: }
141:
142: /**
143: * Are we currently building a view ?
144: */
145: public boolean isBuildingView() {
146: return this .isBuildingView;
147: }
148:
149: /**
150: * Set to <code>true</code> while building the internals of a <map:handle-errors>
151: */
152: public void setBuildingErrorHandler(boolean building) {
153: this .isBuildingErrorHandler = building;
154: }
155:
156: /**
157: * Are we currently building an error handler ?
158: */
159: public boolean isBuildingErrorHandler() {
160: return this .isBuildingErrorHandler;
161: }
162:
163: /**
164: * Add a view for a label. This is used to register all views that start from
165: * a given label.
166: *
167: * @param label the label (or pseudo-label) for the view
168: * @param view the view name
169: */
170: public void addViewForLabel(String label, String view) {
171: if (getLogger().isDebugEnabled()) {
172: getLogger().debug(
173: "views:addViewForLabel(" + label + ", " + view
174: + ")");
175: }
176: Set views = (Set) this .labelViews.get(label);
177: if (views == null) {
178: views = new HashSet();
179: this .labelViews.put(label, views);
180: }
181:
182: views.add(view);
183: }
184:
185: /**
186: * Get the names of views for a given statement. If the cocoon view exists in the returned
187: * collection, the statement can directly branch to the view-handling node.
188: *
189: * @param role the component role (e.g. <code>Generator.ROLE</code>)
190: * @param hint the component hint, i.e. the 'type' attribute
191: * @param statement the sitemap statement
192: * @return the view names for this statement
193: */
194: public Collection getViewsForStatement(String role, String hint,
195: Configuration statement) throws Exception {
196:
197: String statementLabels = statement.getAttribute("label", null);
198:
199: if (this .isBuildingView) {
200: // Labels are forbidden inside view definition
201: if (statementLabels != null) {
202: String msg = "Cannot put a 'label' attribute inside view definition at "
203: + statement.getLocation();
204: throw new ConfigurationException(msg);
205: }
206:
207: // We are currently building a view. Don't recurse !
208: return null;
209: }
210:
211: // Compute the views attached to this component
212: Set views = null;
213:
214: // Build the set for all labels for this statement
215: Set labels = new HashSet();
216:
217: // 1 - labels defined on the component
218: if (role != null && role.length() > 0) {
219: SitemapComponentSelector selector = null;
220: try {
221: selector = (SitemapComponentSelector) this .manager
222: .lookup(role + "Selector");
223: String[] compLabels = selector.getLabels(hint);
224: if (compLabels != null) {
225: for (int i = 0; i < compLabels.length; i++) {
226: labels.add(compLabels[i]);
227: }
228: }
229: } catch (Exception e) {
230: // Ignore (no selector for this role)
231: getLogger().warn("No selector for role " + role);
232: } finally {
233: this .manager.release(selector);
234: }
235: }
236:
237: // 2 - labels defined on this statement
238: if (statementLabels != null) {
239: labels.addAll(splitLabels(statementLabels));
240: }
241:
242: // 3 - pseudo-label depending on the role
243: if (Generator.ROLE.equals(role)) {
244: labels.add("!first!");
245: } else if (Serializer.ROLE.equals(role)) {
246: labels.add("!last!");
247: }
248:
249: // Build the set of views attached to these labels
250: views = new HashSet();
251:
252: // Iterate on all labels for this statement
253: Iterator labelIter = labels.iterator();
254: while (labelIter.hasNext()) {
255:
256: // Iterate on all views for this labek
257: Collection coll = (Collection) this .labelViews
258: .get(labelIter.next());
259: if (coll != null) {
260: Iterator viewIter = coll.iterator();
261: while (viewIter.hasNext()) {
262: String viewName = (String) viewIter.next();
263:
264: views.add(viewName);
265: }
266: }
267: }
268:
269: // Don't keep empty result
270: if (views.size() == 0) {
271: views = null;
272:
273: if (getLogger().isDebugEnabled()) {
274: getLogger().debug(
275: statement.getName() + " has no views at "
276: + statement.getLocation());
277: }
278: } else {
279: if (getLogger().isDebugEnabled()) {
280: // Dump matching views
281: StringBuffer buf = new StringBuffer(statement.getName()
282: + " will match views [");
283: Iterator iter = views.iterator();
284: while (iter.hasNext()) {
285: buf.append(iter.next()).append(" ");
286: }
287: buf.append("] at ").append(statement.getLocation());
288:
289: getLogger().debug(buf.toString());
290: }
291: }
292:
293: return views;
294: }
295:
296: /**
297: * Before linking nodes, lookup the view category node used in {@link #getViewNodes(Collection)}.
298: */
299: protected void linkNodes() throws Exception {
300: // Get the views category node
301: this .viewsNode = CategoryNodeBuilder.getCategoryNode(this ,
302: "views");
303:
304: super .linkNodes();
305: }
306:
307: /**
308: * Get the {view name, view node} map for a collection of view names.
309: * This allows to resolve view nodes at build time, thus avoiding runtime lookup.
310: *
311: * @param viewNames the view names
312: * @return association of names to views
313: */
314: public Map getViewNodes(Collection viewNames) throws Exception {
315: if (viewNames == null || viewNames.size() == 0) {
316: return null;
317: }
318:
319: if (this .viewsNode == null) {
320: return null;
321: }
322:
323: Map result = new HashMap();
324:
325: Iterator iter = viewNames.iterator();
326: while (iter.hasNext()) {
327: String viewName = (String) iter.next();
328: result.put(viewName, viewsNode.getNodeByName(viewName));
329: }
330:
331: return result;
332: }
333:
334: /**
335: * Extract pipeline-hints from the given statement (if any exist)
336: *
337: * @param role the component role (e.g. <code>Generator.ROLE</code>)
338: * @param hint the component hint, i.e. the 'type' attribute
339: * @param statement the sitemap statement
340: * @return the hint params <code>Map</code> for this statement, or null
341: * if none exist
342: */
343: public Map getHintsForStatement(String role, String hint,
344: Configuration statement) throws Exception {
345: // This method implemets the hintParam Syntax as follows:
346: // A hints attribute has one or more comma separated hints
347: // hints-attr :: hint [ ',' hint ]*
348: // A hint is a name and an optional (string) value
349: // If there is no value, it is considered as boolean string "true"
350: // hint :: literal [ '=' litteral ]
351: // literal :: <a character string where the chars ',' and '=' are not permitted>
352: //
353: // A ConfigurationException is thrown if there is a problem "parsing"
354: // the hint.
355:
356: String statementHintParams = statement.getAttribute(
357: "pipeline-hints", null);
358: String componentHintParams = null;
359: String hintParams = null;
360:
361: // firstly, determine if any pipeline-hints are defined at the component level
362: // if so, inherit these pipeline-hints (these hints can be overriden by local pipeline-hints)
363: SitemapComponentSelector selector = null;
364: try {
365: selector = (SitemapComponentSelector) this .manager
366: .lookup(role + "Selector");
367: componentHintParams = selector.getPipelineHint(hint);
368: } catch (Exception ex) {
369: if (getLogger().isWarnEnabled()) {
370: getLogger().warn(
371: "pipeline-hints: Component Exception: could not "
372: + "check for component level hints "
373: + ex);
374: }
375: } finally {
376: this .manager.release(selector);
377: }
378:
379: if (componentHintParams != null) {
380: hintParams = componentHintParams;
381:
382: if (statementHintParams != null) {
383: hintParams = hintParams + "," + statementHintParams;
384: }
385: } else {
386: hintParams = statementHintParams;
387: }
388:
389: // if there are no pipeline-hints defined then
390: // it makes no sense to continue so, return null
391: if (hintParams == null) {
392: return null;
393: }
394:
395: Map params = new HashMap();
396:
397: RE commaSplit = new RE(COMMA_SPLIT_REGEXP);
398: RE equalsSplit = new RE(EQUALS_SPLIT_REGEXP);
399:
400: String[] expressions = commaSplit.split(hintParams.trim());
401:
402: if (getLogger().isDebugEnabled()) {
403: getLogger().debug(
404: "pipeline-hints: (aggregate-hint) " + hintParams);
405: }
406:
407: for (int i = 0; i < expressions.length; i++) {
408: String[] nameValuePair = equalsSplit.split(expressions[i]);
409:
410: try {
411: if (nameValuePair.length < 2) {
412: if (getLogger().isDebugEnabled()) {
413: getLogger()
414: .debug(
415: "pipeline-hints: (name) "
416: + nameValuePair[0]
417: + "\npipeline-hints: (value) [implicit] true");
418: }
419:
420: params.put(VariableResolverFactory.getResolver(
421: nameValuePair[0], this .manager),
422: VariableResolverFactory.getResolver("true",
423: this .manager));
424: } else {
425: if (getLogger().isDebugEnabled()) {
426: getLogger().debug(
427: "pipeline-hints: (name) "
428: + nameValuePair[0]
429: + "\npipeline-hints: (value) "
430: + nameValuePair[1]);
431: }
432:
433: params.put(VariableResolverFactory.getResolver(
434: nameValuePair[0], this .manager),
435: VariableResolverFactory.getResolver(
436: nameValuePair[1], this .manager));
437: }
438: } catch (PatternException pe) {
439: String msg = "Invalid pattern '" + hintParams + "' at "
440: + statement.getLocation();
441: getLogger().error(msg, pe);
442: throw new ConfigurationException(msg, pe);
443: }
444: }
445:
446: return params;
447: }
448:
449: /**
450: * Split a list of space/comma separated labels into a Collection
451: *
452: * @return the collection of labels (may be empty, nut never null)
453: */
454: private static final Collection splitLabels(String labels) {
455: if (labels == null) {
456: return Collections.EMPTY_SET;
457: } else {
458: return Arrays.asList(StringUtils.split(labels, ", \t\n\r"));
459: }
460: }
461: }
|