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.matching;
018:
019: import org.apache.avalon.framework.configuration.Configuration;
020: import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
021: import org.apache.avalon.framework.logger.AbstractLogEnabled;
022: import org.apache.avalon.framework.parameters.ParameterException;
023: import org.apache.avalon.framework.parameters.Parameterizable;
024: import org.apache.avalon.framework.parameters.Parameters;
025: import org.apache.avalon.framework.service.ServiceException;
026: import org.apache.avalon.framework.service.ServiceManager;
027: import org.apache.avalon.framework.service.Serviceable;
028: import org.apache.avalon.framework.thread.ThreadSafe;
029:
030: import org.apache.cocoon.components.source.SourceUtil;
031: import org.apache.cocoon.environment.ObjectModelHelper;
032: import org.apache.cocoon.environment.Request;
033: import org.apache.cocoon.sitemap.PatternException;
034:
035: import org.apache.excalibur.source.Source;
036: import org.apache.excalibur.source.SourceResolver;
037: import org.apache.excalibur.source.SourceValidity;
038:
039: import java.util.Collections;
040: import java.util.HashMap;
041: import java.util.Iterator;
042: import java.util.Map;
043:
044: /**
045: * A matcher that manages a "mount table", allowing to add subsitemaps to a Cocoon application without
046: * modifying the main sitemap. This is especially useful for prototypes and demos where installing
047: * a separate instance of Cocoon is overkill.
048: * <p>
049: * The mount table is an xml file which has a format similar to the <code>map:mount</code> syntax:
050: * <pre>
051: * <mount-table>
052: * <mount uri-prefix="foo" src="file://path/to/foo/directory/"/>
053: * <mount uri-prefix="bar/baz" src="file://path/to/bar-baz/directory/"/>
054: * </mount-table>
055: * </pre>
056: * The matcher will scan the mount table for an "uri-prefix" value matching the beginning of the current
057: * request URI, and if found, succeed and populate the "src" and "uri-prefix" sitemap variables.
058: * <p>
059: * Usage in the sitemap is therefore as follows:
060: * <pre>
061: * <map:match type="mount-table" pattern="path/to/mount-table.xml">
062: * <map:mount uri-prefix="{uri-prefix}" src="{src}"/>
063: * </map:match>
064: * </pre>
065: * <p>
066: * This matcher accepts a single configuration parameter, indicating if missing mount tables should be
067: * silently ignored (defaults is <code>false</code>, meaning "don't ignore"):
068: * <pre>
069: * <map:matcher type="mount-table" src="org.apache.cocoon.matching.MountTableMatcher">
070: * <map:parameter name="ignore-missing-tables" value="true"/>
071: * </map:matcher>
072: * </pre>
073: * <p>
074: * This configuration is used in the main sitemap of Cocoon samples, to allow users to define their own mount
075: * table, but not fail if it does not exist.
076: *
077: * @author <a href="http://www.apache.org/~sylvain/">Sylvain Wallez</a>
078: * @version $Id: MountTableMatcher.java 433543 2006-08-22 06:22:54Z crossley $
079: */
080: public class MountTableMatcher extends AbstractLogEnabled implements
081: Matcher, ThreadSafe, Serviceable, Parameterizable {
082:
083: private ServiceManager manager;
084: private SourceResolver resolver;
085: private Map mountTables = Collections
086: .synchronizedMap(new HashMap());
087: private boolean ignoreMissingTables;
088:
089: public void service(ServiceManager manager) throws ServiceException {
090: this .manager = manager;
091: this .resolver = (SourceResolver) this .manager
092: .lookup(SourceResolver.ROLE);
093: }
094:
095: public void parameterize(Parameters params)
096: throws ParameterException {
097: this .ignoreMissingTables = params.getParameterAsBoolean(
098: "ignore-missing-tables", false);
099: }
100:
101: private Map getMountTable(String src) throws Exception {
102: Source source = null;
103: try {
104: source = this .resolver.resolveURI(src);
105: final String uri = source.getURI();
106:
107: // Check if source exists
108: if (!source.exists()) {
109: if (this .ignoreMissingTables) {
110: return Collections.EMPTY_MAP;
111: } else {
112: throw new PatternException(
113: "Mount table does not exist: '" + uri + "'");
114: }
115: }
116:
117: // Source exists
118: Object[] values = (Object[]) this .mountTables.get(uri);
119: if (values != null) {
120: // Check validity
121: SourceValidity oldValidity = (SourceValidity) values[1];
122:
123: int valid = oldValidity != null ? oldValidity.isValid()
124: : SourceValidity.INVALID;
125: if (valid == SourceValidity.VALID) {
126: // Valid without needing the new validity
127: return (Map) values[0];
128: }
129:
130: if (valid == SourceValidity.UNKNOWN
131: && oldValidity.isValid(source.getValidity()) == SourceValidity.VALID) {
132: // Valid after comparing with the new validity
133: return (Map) values[0];
134: }
135:
136: // Invalid: fallback below to read the mount table
137: } else {
138: values = new Object[2];
139: }
140:
141: // Read the mount table
142: Map mounts = new HashMap();
143: DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
144: Configuration config = builder.build(SourceUtil
145: .getInputSource(source));
146:
147: Configuration[] children = config.getChildren();
148: for (int i = 0; i < children.length; i++) {
149: Configuration child = children[i];
150: if ("mount".equals(child.getName())) {
151: String prefix = children[i]
152: .getAttribute("uri-prefix");
153: // Append a '/' at the end of a not-empty prefix
154: // this avoids flat uri matching which would cause
155: // exceptions in the sub sitemap!
156: if (!prefix.endsWith("/") && prefix.length() != 0) {
157: prefix = prefix + '/';
158: }
159: mounts.put(prefix, children[i].getAttribute("src"));
160: } else {
161: throw new PatternException("Unexpected element '"
162: + child.getName()
163: + "' (awaiting 'mount'), at "
164: + child.getLocation());
165: }
166: }
167: values[0] = mounts;
168: values[1] = source.getValidity();
169:
170: // Cache it with the source validity
171: this .mountTables.put(uri, values);
172:
173: return mounts;
174:
175: } catch (SecurityException e) {
176: if (this .ignoreMissingTables) {
177: return Collections.EMPTY_MAP;
178: } else {
179: throw new PatternException(
180: "Mount table is not accessible: '" + src
181: + "' (" + e + ")");
182: }
183:
184: } finally {
185: if (source != null) {
186: this .resolver.release(source);
187: }
188: }
189: }
190:
191: public Map match(String pattern, Map objectModel,
192: Parameters parameters) throws PatternException {
193: Map mounts;
194: try {
195: mounts = getMountTable(pattern);
196: } catch (PatternException pe) {
197: throw pe;
198: } catch (Exception e) {
199: throw new PatternException(e);
200: }
201:
202: // Get the request URI
203: Request request = ObjectModelHelper.getRequest(objectModel);
204: String uri = request.getSitemapURI();
205:
206: // and search for a matching prefix
207: Iterator iter = mounts.entrySet().iterator();
208: while (iter.hasNext()) {
209: Map.Entry entry = (Map.Entry) iter.next();
210: String prefix = (String) entry.getKey();
211: if (uri.startsWith(prefix)) {
212: // Found it
213: Map result = new HashMap(2);
214: result.put("uri-prefix", prefix);
215: result.put("src", entry.getValue());
216:
217: // Return immediately
218: return result;
219: }
220: }
221:
222: // Not found
223: return null;
224: }
225: }
|