/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.cocoon.components.treeprocessor.sitemap; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.apache.avalon.excalibur.logger.LoggerManager; import org.apache.avalon.framework.component.ComponentManager; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.avalon.framework.configuration.DefaultConfiguration; import org.apache.cocoon.components.CocoonComponentManager; import org.apache.cocoon.components.treeprocessor.CategoryNode; import org.apache.cocoon.components.treeprocessor.CategoryNodeBuilder; import org.apache.cocoon.components.treeprocessor.DefaultTreeBuilder; import org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory; import org.apache.cocoon.generation.Generator; import org.apache.cocoon.serialization.Serializer; import org.apache.cocoon.sitemap.PatternException; import org.apache.cocoon.sitemap.SitemapComponentSelector; import org.apache.cocoon.util.StringUtils; import org.apache.regexp.RE; /** * The tree builder for the sitemap language. * * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a> * @version CVS $Id$ */ public class SitemapLanguage extends DefaultTreeBuilder { // Regexp's for splitting expressions private static final String COMMA_SPLIT_REGEXP = "[\\s]*,[\\s]*"; private static final String EQUALS_SPLIT_REGEXP = "[\\s]*=[\\s]*"; /** * Build a component manager with the contents of the <map:components> element of * the tree. */ protected ComponentManager createComponentManager(Configuration tree) throws Exception { // Get the map:component node // Don't check namespace here : this will be done by node builders Configuration config = tree.getChild("components", false); if (config == null) { if (getLogger().isDebugEnabled()) { getLogger().debug("Sitemap has no components definition at " + tree.getLocation()); } config = new DefaultConfiguration("", ""); } final CocoonComponentManager manager = new CocoonComponentManager(this.parentManager); manager.enableLogging(getLogger()); final LoggerManager loggerManager = (LoggerManager) this.parentManager.lookup(LoggerManager.ROLE); manager.setLoggerManager(loggerManager); if (null != this.context ) { manager.contextualize(this.context); } if (null != this.roleManager) { manager.setRoleManager(this.roleManager); } manager.configure(config); manager.initialize(); return manager; } //---- Views management /** Collection of view names for each label */ private Map labelViews = new HashMap(); /** The views CategoryNode */ private CategoryNode viewsNode; /** Are we currently building a view ? */ private boolean isBuildingView = false; /** Are we currently building a view ? */ private boolean isBuildingErrorHandler = false; /** * Pseudo-label for views <code>from-position="first"</code> (i.e. generator). */ public static final String FIRST_POS_LABEL = "!first!"; /** * Pseudo-label for views <code>from-position="last"</code> (i.e. serializer). */ public static final String LAST_POS_LABEL = "!last!"; public void recycle() { super.recycle(); // Go back to initial state this.labelViews.clear(); this.viewsNode = null; this.isBuildingView = false; this.isBuildingErrorHandler = false; } /** * Set to <code>true</code> while building the internals of a <map:view> */ public void setBuildingView(boolean building) { this.isBuildingView = building; } /** * Are we currently building a view ? */ public boolean isBuildingView() { return this.isBuildingView; } /** * Set to <code>true</code> while building the internals of a <map:handle-errors> */ public void setBuildingErrorHandler(boolean building) { this.isBuildingErrorHandler = building; } /** * Are we currently building an error handler ? */ public boolean isBuildingErrorHandler() { return this.isBuildingErrorHandler; } /** * Add a view for a label. This is used to register all views that start from * a given label. * * @param label the label (or pseudo-label) for the view * @param view the view name */ public void addViewForLabel(String label, String view) { if (getLogger().isDebugEnabled()) { getLogger().debug("views:addViewForLabel(" + label + ", " + view + ")"); } Set views = (Set)this.labelViews.get(label); if (views == null) { views = new HashSet(); this.labelViews.put(label, views); } views.add(view); } /** * Get the names of views for a given statement. If the cocoon view exists in the returned * collection, the statement can directly branch to the view-handling node. * * @param role the component role (e.g. <code>Generator.ROLE</code>) * @param hint the component hint, i.e. the 'type' attribute * @param statement the sitemap statement * @return the view names for this statement */ public Collection getViewsForStatement(String role, String hint, Configuration statement) throws Exception { String statementLabels = statement.getAttribute("label", null); if (this.isBuildingView) { // Labels are forbidden inside view definition if (statementLabels != null) { String msg = "Cannot put a 'label' attribute inside view definition at " + statement.getLocation(); throw new ConfigurationException(msg); } // We are currently building a view. Don't recurse ! return null; } // Compute the views attached to this component Set views = null; // Build the set for all labels for this statement Set labels = new HashSet(); // 1 - labels defined on the component if (role != null && role.length() > 0) { SitemapComponentSelector selector = null; try { selector = (SitemapComponentSelector)this.manager.lookup(role + "Selector"); String[] compLabels = selector.getLabels(hint); if (compLabels != null) { for (int i = 0; i < compLabels.length; i++) { labels.add(compLabels[i]); } } } catch(Exception e) { // Ignore (no selector for this role) getLogger().warn("No selector for role " + role); } finally { this.manager.release( selector ); } } // 2 - labels defined on this statement if (statementLabels != null) { labels.addAll(splitLabels(statementLabels)); } // 3 - pseudo-label depending on the role if (Generator.ROLE.equals(role)) { labels.add("!first!"); } else if (Serializer.ROLE.equals(role)) { labels.add("!last!"); } // Build the set of views attached to these labels views = new HashSet(); // Iterate on all labels for this statement Iterator labelIter = labels.iterator(); while(labelIter.hasNext()) { // Iterate on all views for this labek Collection coll = (Collection)this.labelViews.get(labelIter.next()); if (coll != null) { Iterator viewIter = coll.iterator(); while(viewIter.hasNext()) { String viewName = (String)viewIter.next(); views.add(viewName); } } } // Don't keep empty result if (views.size() == 0) { views = null; if (getLogger().isDebugEnabled()) { getLogger().debug(statement.getName() + " has no views at " + statement.getLocation()); } } else { if (getLogger().isDebugEnabled()) { // Dump matching views StringBuffer buf = new StringBuffer(statement.getName() + " will match views ["); Iterator iter = views.iterator(); while(iter.hasNext()) { buf.append(iter.next()).append(" "); } buf.append("] at ").append(statement.getLocation()); getLogger().debug(buf.toString()); } } return views; } /** * Before linking nodes, lookup the view category node used in {@link #getViewNodes(Collection)}. */ protected void linkNodes() throws Exception { // Get the views category node this.viewsNode = CategoryNodeBuilder.getCategoryNode(this, "views"); super.linkNodes(); } /** * Get the {view name, view node} map for a collection of view names. * This allows to resolve view nodes at build time, thus avoiding runtime lookup. * * @param viewNames the view names * @return association of names to views */ public Map getViewNodes(Collection viewNames) throws Exception { if (viewNames == null || viewNames.size() == 0) { return null; } if (this.viewsNode == null) { return null; } Map result = new HashMap(); Iterator iter = viewNames.iterator(); while(iter.hasNext()) { String viewName = (String)iter.next(); result.put(viewName, viewsNode.getNodeByName(viewName)); } return result; } /** * Extract pipeline-hints from the given statement (if any exist) * * @param role the component role (e.g. <code>Generator.ROLE</code>) * @param hint the component hint, i.e. the 'type' attribute * @param statement the sitemap statement * @return the hint params <code>Map</code> for this statement, or null * if none exist */ public Map getHintsForStatement(String role, String hint, Configuration statement) throws Exception { // This method implemets the hintParam Syntax as follows: // A hints attribute has one or more comma separated hints // hints-attr :: hint [ ',' hint ]* // A hint is a name and an optional (string) value // If there is no value, it is considered as boolean string "true" // hint :: literal [ '=' litteral ] // literal :: <a character string where the chars ',' and '=' are not permitted> // // A ConfigurationException is thrown if there is a problem "parsing" // the hint. String statementHintParams = statement.getAttribute("pipeline-hints", null); String componentHintParams = null; String hintParams = null; // firstly, determine if any pipeline-hints are defined at the component level // if so, inherit these pipeline-hints (these hints can be overriden by local pipeline-hints) SitemapComponentSelector selector = null; try { selector = (SitemapComponentSelector)this.manager.lookup(role + "Selector"); componentHintParams = selector.getPipelineHint(hint); } catch (Exception ex) { if (getLogger().isWarnEnabled()) { getLogger().warn("pipeline-hints: Component Exception: could not " + "check for component level hints " + ex); } } finally { this.manager.release(selector); } if (componentHintParams != null) { hintParams = componentHintParams; if (statementHintParams != null) { hintParams = hintParams + "," + statementHintParams; } } else { hintParams = statementHintParams; } // if there are no pipeline-hints defined then // it makes no sense to continue so, return null if (hintParams == null) { return null; } Map params = new HashMap(); RE commaSplit = new RE(COMMA_SPLIT_REGEXP); RE equalsSplit = new RE(EQUALS_SPLIT_REGEXP); String[] expressions = commaSplit.split(hintParams.trim()); if (getLogger().isDebugEnabled()) { getLogger().debug("pipeline-hints: (aggregate-hint) " + hintParams); } for (int i=0; i<expressions.length;i++) { String [] nameValuePair = equalsSplit.split(expressions[i]); try { if (nameValuePair.length < 2) { if (getLogger().isDebugEnabled()) { getLogger().debug("pipeline-hints: (name) " + nameValuePair[0] + "\npipeline-hints: (value) [implicit] true"); } params.put( VariableResolverFactory.getResolver(nameValuePair[0], this.manager), VariableResolverFactory.getResolver("true", this.manager)); } else { if (getLogger().isDebugEnabled()) { getLogger().debug("pipeline-hints: (name) " + nameValuePair[0] + "\npipeline-hints: (value) " + nameValuePair[1]); } params.put( VariableResolverFactory.getResolver(nameValuePair[0], this.manager), VariableResolverFactory.getResolver(nameValuePair[1], this.manager)); } } catch(PatternException pe) { String msg = "Invalid pattern '" + hintParams + "' at " + statement.getLocation(); getLogger().error(msg, pe); throw new ConfigurationException(msg, pe); } } return params; } /** * Split a list of space/comma separated labels into a Collection * * @return the collection of labels (may be empty, nut never null) */ private static final Collection splitLabels(String labels) { if (labels == null) { return Collections.EMPTY_SET; } else { return Arrays.asList(StringUtils.split(labels, ", \t\n\r")); } } }