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.modules.input;
018:
019: import java.util.HashMap;
020: import java.util.Iterator;
021: import java.util.Map;
022: import java.util.SortedSet;
023: import java.util.TreeSet;
024: import java.util.Vector;
025:
026: import org.apache.avalon.framework.component.Component;
027: import org.apache.avalon.framework.component.ComponentException;
028: import org.apache.avalon.framework.component.ComponentManager;
029: import org.apache.avalon.framework.configuration.Configuration;
030: import org.apache.avalon.framework.configuration.ConfigurationException;
031: import org.apache.avalon.framework.thread.ThreadSafe;
032:
033: import org.apache.cocoon.environment.ObjectModelHelper;
034: import org.apache.cocoon.environment.Request;
035: import org.apache.cocoon.xml.dom.DOMUtil;
036: import org.apache.cocoon.xml.dom.DocumentWrapper;
037: import org.apache.excalibur.xml.xpath.XPathProcessor;
038:
039: import org.w3c.dom.Document;
040: import org.w3c.dom.Element;
041: import org.w3c.dom.Node;
042:
043: /**
044: * Meta module that obtains values from other module and returns all
045: * parameters as XML.
046: *
047: * <p>Config</p>
048: * <pre>
049: * <!-- in cocoon.xconf -->
050: * <ignore>do-</ignore>
051: * <strip>user.</strip>
052: * <input-module name="request-param"/>
053: *
054: * <!-- e.g. in database.xml -->
055: * <mode type="all" name="xmlmeta"/>
056: * <ignore>foo.</ignore>
057: * <strip>f</strip>
058: * <use>foo</use>
059: * <root>my-root</root>
060: * <input-module name="request-param"/>
061: * </mode>
062: * </pre>
063: *
064: * <p>If present, "ignore" gives a prefix of parameters to ignore,
065: * ignore has precedence over the "use" attribute, "strip" a prefix
066: * that will be removed from the final parameter names in the produced
067: * XML, "use" is a prefix for parameters to include in the XML, and
068: * "root" is the name of the root element in the created XML.</p>
069: *
070: * <p>Input</p>
071: * <pre>
072: * foo.one = ['abc']
073: * foo.two = ['def']
074: * foo1 = ['bar']
075: * foo2 = ['one','two','three']
076: * bar = ['rubber duck']
077: * </pre>
078: *
079: * <p>Output</p>
080: * <pre>
081: * <my-root>
082: * <item name="oo1">bar</item>
083: * <item name="oo2">
084: * <value>one</value>
085: * <value>two</value>
086: * <value>three</value>
087: * </item>
088: * </my-root>
089: * </pre>
090: *
091: * <p>Produces Objects of type {@link org.apache.cocoon.xml.dom.DocumentWrapper DocumentWrapper}</p>
092: *
093: * @author <a href="mailto:haul@apache.org">Christian Haul</a>
094: * @version $Id: XMLMetaModule.java 433543 2006-08-22 06:22:54Z crossley $
095: */
096: public class XMLMetaModule extends AbstractMetaModule implements
097: ThreadSafe {
098:
099: protected String rootName = "root";
100: protected String ignore;
101: protected String use;
102: protected String strip;
103: protected Object config;
104: protected XPathProcessor xpathProcessor;
105:
106: protected static final String CACHE_OBJECT_NAME = "org.apache.cocoon.component.modules.input.XMLMetaModule";
107:
108: final static Vector returnNames;
109: static {
110: Vector tmp = new Vector();
111: tmp.add("XML");
112: returnNames = tmp;
113: }
114:
115: public void configure(Configuration config)
116: throws ConfigurationException {
117:
118: this .inputConf = config.getChild("input-module");
119: this .defaultInput = this .inputConf.getAttribute("name",
120: this .defaultInput);
121: this .rootName = this .inputConf.getAttribute("root",
122: this .rootName);
123: this .ignore = this .inputConf
124: .getAttribute("ignore", this .ignore);
125: this .use = this .inputConf.getAttribute("use", this .use);
126: this .strip = this .inputConf.getAttribute("strip", this .strip);
127: this .config = config;
128:
129: // preferred
130: this .rootName = config.getChild("root").getValue(this .rootName);
131: this .ignore = config.getChild("ignore").getValue(this .ignore);
132: this .use = config.getChild("use").getValue(this .use);
133: this .strip = config.getChild("strip").getValue(this .strip);
134: }
135:
136: public Object getAttribute(String name, Configuration modeConf,
137: Map objectModel) throws ConfigurationException {
138:
139: if (!this .initialized) {
140: this .lazy_initialize();
141: }
142: if (this .defaultInput == null) {
143: if (getLogger().isWarnEnabled())
144: getLogger().warn("No input module given. FAILING");
145: return null;
146: }
147:
148: // obtain correct configuration objects
149: // default vs dynamic
150: Configuration inputConfig = null;
151: String inputName = null;
152: String rootName = this .rootName;
153: String ignore = this .ignore;
154: String use = this .use;
155: String strip = this .strip;
156: if (modeConf != null) {
157: inputName = modeConf.getChild("input-module").getAttribute(
158: "name", null);
159: rootName = modeConf.getAttribute("root", this .rootName);
160: ignore = modeConf.getAttribute("ignore", this .ignore);
161: use = modeConf.getAttribute("use", this .use);
162: strip = modeConf.getAttribute("strip", this .strip);
163:
164: // preferred
165: rootName = modeConf.getChild("root").getValue(rootName);
166: ignore = modeConf.getChild("ignore").getValue(ignore);
167: use = modeConf.getChild("use").getValue(use);
168: strip = modeConf.getChild("strip").getValue(strip);
169: if (inputName != null) {
170: inputConfig = modeConf.getChild("input-module");
171: }
172: }
173:
174: // see whether the Document is already stored as request
175: // attribute and return that
176: Request request = ObjectModelHelper.getRequest(objectModel);
177: Map cache = (Map) request.getAttribute(CACHE_OBJECT_NAME);
178: Object key = (modeConf != null ? modeConf : this .config);
179: Document doc = null;
180:
181: if (cache != null && cache.containsKey(key)) {
182: doc = (Document) cache.get(key);
183: if (getLogger().isDebugEnabled())
184: getLogger().debug("using cached copy " + doc);
185: return doc;
186: }
187: if (getLogger().isDebugEnabled())
188: getLogger().debug("no cached copy " + cache + " / " + key);
189:
190: // get InputModule and all attribute names
191: InputModule input = null;
192: if (inputName != null)
193: input = obtainModule(inputName);
194:
195: Iterator names = getNames(objectModel, this .input,
196: this .defaultInput, this .inputConf, input, inputName,
197: inputConfig);
198:
199: // first, sort all attribute names that the DOM can be created in one go
200: // while doing so, remove unwanted attributes
201: SortedSet set = new TreeSet();
202: String aName = null;
203: while (names.hasNext()) {
204: aName = (String) names.next();
205: if ((use == null || aName.startsWith(use))
206: && (ignore == null || !aName.startsWith(ignore))) {
207: set.add(aName);
208: }
209: }
210:
211: try {
212: names = set.iterator();
213:
214: // create new document and append root node
215: doc = DOMUtil.createDocument();
216: Element elem = doc.createElement(rootName);
217: doc.appendChild(elem);
218:
219: while (names.hasNext()) {
220: aName = (String) names.next();
221: // obtain values from input module
222: Object[] value = getValues(aName, objectModel,
223: this .input, this .defaultInput, this .inputConf,
224: input, inputName, inputConfig);
225:
226: // strip unwanted prefix from attribute name if present
227: if (strip != null && aName.startsWith(strip))
228: aName = aName.substring(strip.length());
229:
230: if (value.length > 0) {
231: // add new node from xpath
232: // (since the names are in a set, the node cannot exist already)
233: Node node = DOMUtil.selectSingleNode(doc
234: .getDocumentElement(), aName,
235: this .xpathProcessor);
236: node.appendChild(node.getOwnerDocument()
237: .createTextNode(value[0].toString()));
238:
239: if (value.length > 1) {
240: // if more than one value was obtained, append
241: // further nodes (same name)
242:
243: // isolate node name, selection expressions
244: // "[...]" may not be part of it
245: int endPos = aName.length()
246: - (aName.endsWith("/") ? 1 : 0);
247: int startPos = aName.lastIndexOf("/", endPos) + 1;
248: String nodeName = aName.substring(startPos,
249: endPos);
250: if (nodeName.indexOf("[") != -1) {
251: endPos = nodeName.lastIndexOf("]");
252: startPos = nodeName.indexOf("[") + 1;
253: nodeName = nodeName.substring(startPos,
254: endPos);
255: }
256:
257: // append more nodes
258: Node parent = node.getParentNode();
259: for (int i = 1; i < value.length; i++) {
260: Node newNode = parent.getOwnerDocument()
261: .createElementNS(null, nodeName);
262: parent.appendChild(newNode);
263: newNode.appendChild(newNode
264: .getOwnerDocument().createTextNode(
265: value[i].toString()));
266: }
267: }
268: }
269: }
270: } catch (Exception e) {
271: throw new ConfigurationException(e.getMessage());
272: }
273:
274: if (input != null)
275: releaseModule(input);
276:
277: // create a wrapped instance that is XMLizable
278: doc = new DocumentWrapper(doc);
279:
280: // store Document as request attribute
281: if (cache == null)
282: cache = new HashMap();
283: if (getLogger().isDebugEnabled())
284: getLogger().debug("no cached copy " + cache + " / " + key);
285: cache.put(key, doc);
286: request.setAttribute(CACHE_OBJECT_NAME, cache);
287:
288: if (getLogger().isDebugEnabled())
289: getLogger().debug("returning " + doc.toString());
290: return doc;
291: }
292:
293: public Iterator getAttributeNames(Configuration modeConf,
294: Map objectModel) throws ConfigurationException {
295:
296: if (!this .initialized) {
297: this .lazy_initialize();
298: }
299: if (this .defaultInput == null) {
300: if (getLogger().isWarnEnabled())
301: getLogger().warn("No input module given. FAILING");
302: return null;
303: }
304:
305: return XMLMetaModule.returnNames.iterator();
306: }
307:
308: public Object[] getAttributeValues(String name,
309: Configuration modeConf, Map objectModel)
310: throws ConfigurationException {
311:
312: if (!this .initialized) {
313: this .lazy_initialize();
314: }
315: if (this .defaultInput == null) {
316: if (getLogger().isWarnEnabled())
317: getLogger().warn("No input module given. FAILING");
318: return null;
319: }
320:
321: Object[] values = new Object[1];
322: values[0] = this .getAttribute(name, modeConf, objectModel);
323: return values;
324: }
325:
326: /* (non-Javadoc)
327: * @see org.apache.avalon.framework.component.Composable#compose(org.apache.avalon.framework.component.ComponentManager)
328: */
329: public void compose(ComponentManager manager)
330: throws ComponentException {
331: super .compose(manager);
332: this .xpathProcessor = (XPathProcessor) this .manager
333: .lookup(XPathProcessor.ROLE);
334: }
335:
336: /* (non-Javadoc)
337: * @see org.apache.avalon.framework.activity.Disposable#dispose()
338: */
339: public void dispose() {
340: if (this .manager != null) {
341: this .manager.release((Component) this.xpathProcessor);
342: this.xpathProcessor = null;
343: }
344: super.dispose();
345: }
346:
347: }
|