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.forms.datatype;
018:
019: import java.io.IOException;
020: import java.io.UnsupportedEncodingException;
021: import java.util.Locale;
022:
023: import org.apache.avalon.framework.context.Context;
024: import org.apache.avalon.framework.service.ServiceManager;
025: import org.apache.cocoon.ProcessingException;
026: import org.apache.cocoon.components.ContextHelper;
027: import org.apache.cocoon.components.source.SourceUtil;
028: import org.apache.cocoon.environment.Request;
029: import org.apache.cocoon.forms.FormsConstants;
030: import org.apache.cocoon.forms.datatype.convertor.ConversionResult;
031: import org.apache.cocoon.forms.datatype.convertor.Convertor;
032: import org.apache.cocoon.forms.datatype.convertor.DefaultFormatCache;
033: import org.apache.cocoon.util.NetUtils;
034: import org.apache.cocoon.xml.AbstractXMLPipe;
035: import org.apache.cocoon.xml.AttributesImpl;
036: import org.apache.cocoon.xml.SaxBuffer;
037: import org.apache.cocoon.xml.XMLUtils;
038: import org.apache.cocoon.xml.dom.DOMBuilder;
039: import org.apache.excalibur.source.Source;
040: import org.apache.excalibur.source.SourceResolver;
041: import org.w3c.dom.Element;
042: import org.xml.sax.Attributes;
043: import org.xml.sax.ContentHandler;
044: import org.xml.sax.SAXException;
045:
046: /**
047: * SelectionList implementation that always reads its content from the source
048: * each time it is requested.
049: * <p>
050: * This list is filterable, and if a filter is provided, the "filter" parameter
051: * is appended to the URL, e.g. <code><fd:selection-list src="cocoon://pipeline.xml"/></code>
052: * will call, given the "<code>foo</code>" filter value, the URL <code>cocoon://pipeline.xml?filter=foo</code>.
053: * <p>
054: * Note: the class {@link SelectionListBuilder} also interprets the same
055: * <code>fd:selection-list</code> XML, so if anything changes here to how that
056: * XML is interpreted, it also needs to change over there and vice versa.</p>
057: *
058: * @version $Id: DynamicSelectionList.java 449149 2006-09-23 03:58:05Z crossley $
059: */
060: public class DynamicSelectionList implements FilterableSelectionList {
061: private String src;
062: private boolean usePerRequestCache;
063: private Datatype datatype;
064: private ServiceManager serviceManager;
065: private Context context;
066:
067: /**
068: * @param datatype
069: * @param src
070: * @param usePerRequestCache
071: * @param serviceManager
072: * @param context
073: */
074: public DynamicSelectionList(Datatype datatype, String src,
075: boolean usePerRequestCache, ServiceManager serviceManager,
076: Context context) {
077: this .datatype = datatype;
078: this .src = src;
079: this .serviceManager = serviceManager;
080: this .usePerRequestCache = usePerRequestCache;
081: this .context = context;
082: }
083:
084: /**
085: * Creates a DynamicSelectionList without caching
086: * @param datatype -
087: * @param src -
088: * @param serviceManager -
089: */
090: public DynamicSelectionList(Datatype datatype, String src,
091: ServiceManager serviceManager) {
092: this .usePerRequestCache = false;
093: this .context = null;
094: this .datatype = datatype;
095: this .src = src;
096: this .serviceManager = serviceManager;
097: }
098:
099: public Datatype getDatatype() {
100: return datatype;
101: }
102:
103: /*
104: * This method is only used by a test case and by the public version
105: * of generateSaxFragment.
106: */
107: void generateSaxFragment(ContentHandler contentHandler,
108: Locale locale, Source source) throws ProcessingException,
109: SAXException, IOException {
110: SelectionListHandler handler = new SelectionListHandler(locale);
111: handler.setContentHandler(contentHandler);
112: SourceUtil.toSAX(serviceManager, source, null, handler);
113: }
114:
115: /*
116: * This method generate SaxFragment directly from source.
117: */
118: private void generateSaxFragmentFromSrc(String url,
119: ContentHandler contentHandler, Locale locale)
120: throws SAXException {
121: SourceResolver sourceResolver = null;
122: Source source = null;
123: try {
124: sourceResolver = (SourceResolver) serviceManager
125: .lookup(SourceResolver.ROLE);
126: source = sourceResolver.resolveURI(url);
127: generateSaxFragment(contentHandler, locale, source);
128: } catch (SAXException e) {
129: throw e;
130: } catch (Exception e) {
131: throw new SAXException(
132: "Error while generating selection list: "
133: + e.getMessage(), e);
134: } finally {
135: if (sourceResolver != null) {
136: if (source != null) {
137: try {
138: sourceResolver.release(source);
139: } catch (Exception e) {
140: }
141: }
142: serviceManager.release(sourceResolver);
143: }
144: }
145: }
146:
147: public void generateSaxFragment(ContentHandler contentHandler,
148: Locale locale, String filter) throws SAXException {
149:
150: String url = this .src;
151: if (filter != null) {
152: if (url.indexOf('?') != -1) {
153: try {
154: url += "&filter="
155: + NetUtils.encode(filter, "utf-8");
156: } catch (UnsupportedEncodingException ignore) {
157: // utf-8 is always supported
158: }
159: } else {
160: try {
161: url += "?filter="
162: + NetUtils.encode(filter, "utf-8");
163: } catch (UnsupportedEncodingException ignore) {
164: // utf-8 is always supported
165: }
166: }
167: }
168:
169: if (usePerRequestCache) {
170: // Search the sax buffer in request attributes
171: Request request = ContextHelper.getRequest(this .context);
172: String attributeName = "DynamicSelectionListCache/" + url;
173: SaxBuffer saxBuffer = (SaxBuffer) request
174: .getAttribute(attributeName);
175:
176: if (saxBuffer == null) {
177: // Not found: generate the list and store it
178: saxBuffer = new SaxBuffer();
179: generateSaxFragmentFromSrc(url, saxBuffer, locale);
180: request.setAttribute(attributeName, saxBuffer);
181: }
182:
183: // Output the stored saxBuffer to the contentHandler
184: saxBuffer.toSAX(contentHandler);
185: } else { // We don't use usePerRequestCache => re-read from the source.
186: generateSaxFragmentFromSrc(url, contentHandler, locale);
187: }
188: }
189:
190: public void generateSaxFragment(ContentHandler contentHandler,
191: Locale locale) throws SAXException {
192: // Generate with no filter
193: generateSaxFragment(contentHandler, locale, (String) null);
194: }
195:
196: /**
197: * XMLConsumer used to handle selection lists generated on the fly.
198: */
199: public class SelectionListHandler extends AbstractXMLPipe {
200: private Object currentValue;
201: private String currentValueAsString;
202: private boolean hasLabel;
203: private Locale locale;
204: /** The convertor used to parse the values in the selection list. */
205: private Convertor convertor;
206: private DOMBuilder convertorConfigDOMBuilder;
207: private int convertorConfigNestingLevel = 0;
208: private Convertor.FormatCache fromFormatCache = new DefaultFormatCache();
209: private Convertor.FormatCache toFormatCache = new DefaultFormatCache();
210:
211: public SelectionListHandler(Locale locale) {
212: this .locale = locale;
213: }
214:
215: public void startDocument() throws SAXException {
216: }
217:
218: public void endDocument() throws SAXException {
219: }
220:
221: public void endDTD() throws SAXException {
222: }
223:
224: public void startDTD(String name, String publicId,
225: String systemId) throws SAXException {
226: }
227:
228: public void startElement(String namespaceURI, String localName,
229: String qName, Attributes attributes)
230: throws SAXException {
231: if (convertorConfigNestingLevel > 0) {
232: convertorConfigNestingLevel++;
233: convertorConfigDOMBuilder.startElement(namespaceURI,
234: localName, qName, attributes);
235: } else if (namespaceURI
236: .equals(FormsConstants.DEFINITION_NS)) {
237: if (localName.equals("item")) {
238: if (convertor == null) {
239: // if no convertor was explicitely configured, use the default one of the datatype
240: convertor = getDatatype().getConvertor();
241: }
242: hasLabel = false;
243:
244: String unparsedValue = attributes.getValue("value");
245: if (unparsedValue == null
246: || "".equals(unparsedValue)) {
247: // Empty (or null) value translates into the empty string
248: currentValueAsString = "";
249: } else {
250: ConversionResult conversionResult = convertor
251: .convertFromString(unparsedValue,
252: locale, fromFormatCache);
253: if (!conversionResult.isSuccessful()) {
254: throw new SAXException(
255: "Could not interpret the following value: \""
256: + unparsedValue + "\".");
257: }
258: currentValue = conversionResult.getResult();
259: currentValueAsString = getDatatype()
260: .getConvertor().convertToString(
261: currentValue, locale,
262: toFormatCache);
263: }
264: AttributesImpl attrs = new AttributesImpl();
265: attrs.addCDATAAttribute("value",
266: currentValueAsString);
267: super .startElement(FormsConstants.INSTANCE_NS,
268: localName,
269: FormsConstants.INSTANCE_PREFIX_COLON
270: + localName, attrs);
271: } else if (localName.equals("label")) {
272: hasLabel = true;
273: super .startElement(FormsConstants.INSTANCE_NS,
274: localName,
275: FormsConstants.INSTANCE_PREFIX_COLON
276: + localName, attributes);
277: } else if (localName.equals("selection-list")) {
278: super .startElement(FormsConstants.INSTANCE_NS,
279: localName,
280: FormsConstants.INSTANCE_PREFIX_COLON
281: + localName, attributes);
282: } else if (convertor == null
283: && localName.equals("convertor")) {
284: // record the content of this element in a dom-tree
285: convertorConfigDOMBuilder = new DOMBuilder();
286: convertorConfigDOMBuilder.startElement(
287: namespaceURI, localName, qName, attributes);
288: convertorConfigNestingLevel++;
289: } else {
290: super .startElement(namespaceURI, localName, qName,
291: attributes);
292: }
293: } else {
294: super .startElement(namespaceURI, localName, qName,
295: attributes);
296: }
297: }
298:
299: private static final String LABEL_EL = "label";
300:
301: public void endElement(String namespaceURI, String localName,
302: String qName) throws SAXException {
303: if (convertorConfigNestingLevel > 0) {
304: convertorConfigNestingLevel--;
305: convertorConfigDOMBuilder.endElement(namespaceURI,
306: localName, qName);
307: if (convertorConfigNestingLevel == 0) {
308: Element convertorElement = convertorConfigDOMBuilder
309: .getDocument().getDocumentElement();
310: try {
311: convertor = getDatatype().getBuilder()
312: .buildConvertor(convertorElement);
313: } catch (Exception e) {
314: throw new SAXException(
315: "Error building convertor from convertor configuration embedded in selection list XML.",
316: e);
317: }
318: }
319: } else if (namespaceURI
320: .equals(FormsConstants.DEFINITION_NS)) {
321: if (localName.equals("item")) {
322: if (!hasLabel) {
323: // make the label now
324: super .startElement(FormsConstants.INSTANCE_NS,
325: LABEL_EL,
326: FormsConstants.INSTANCE_PREFIX_COLON
327: + LABEL_EL,
328: XMLUtils.EMPTY_ATTRIBUTES);
329: super .characters(currentValueAsString
330: .toCharArray(), 0, currentValueAsString
331: .length());
332: super .endElement(FormsConstants.INSTANCE_NS,
333: LABEL_EL,
334: FormsConstants.INSTANCE_PREFIX_COLON
335: + LABEL_EL);
336: }
337: super .endElement(FormsConstants.INSTANCE_NS,
338: localName,
339: FormsConstants.INSTANCE_PREFIX_COLON
340: + localName);
341: } else if (localName.equals("label")) {
342: super .endElement(FormsConstants.INSTANCE_NS,
343: localName,
344: FormsConstants.INSTANCE_PREFIX_COLON
345: + localName);
346: } else if (localName.equals("selection-list")) {
347: super .endElement(FormsConstants.INSTANCE_NS,
348: localName,
349: FormsConstants.INSTANCE_PREFIX_COLON
350: + localName);
351: } else {
352: super .endElement(namespaceURI, localName, qName);
353: }
354: } else {
355: super .endElement(namespaceURI, localName, qName);
356: }
357: }
358:
359: public void comment(char ch[], int start, int len)
360: throws SAXException {
361: if (convertorConfigNestingLevel > 0) {
362: convertorConfigDOMBuilder.comment(ch, start, len);
363: } else
364: super .comment(ch, start, len);
365: }
366:
367: public void startPrefixMapping(String prefix, String uri)
368: throws SAXException {
369: if (convertorConfigNestingLevel > 0) {
370: convertorConfigDOMBuilder.startPrefixMapping(prefix,
371: uri);
372: } else
373: super .startPrefixMapping(prefix, uri);
374: }
375:
376: public void endPrefixMapping(String prefix) throws SAXException {
377: if (convertorConfigNestingLevel > 0) {
378: convertorConfigDOMBuilder.endPrefixMapping(prefix);
379: } else
380: super .endPrefixMapping(prefix);
381: }
382:
383: public void characters(char c[], int start, int len)
384: throws SAXException {
385: if (convertorConfigNestingLevel > 0) {
386: convertorConfigDOMBuilder.characters(c, start, len);
387: } else
388: super .characters(c, start, len);
389: }
390:
391: public void ignorableWhitespace(char c[], int start, int len)
392: throws SAXException {
393: if (convertorConfigNestingLevel > 0) {
394: convertorConfigDOMBuilder.ignorableWhitespace(c, start,
395: len);
396: } else
397: super .ignorableWhitespace(c, start, len);
398: }
399:
400: public void processingInstruction(String target, String data)
401: throws SAXException {
402: if (convertorConfigNestingLevel > 0) {
403: convertorConfigDOMBuilder.processingInstruction(target,
404: data);
405: } else
406: super .processingInstruction(target, data);
407: }
408:
409: public void skippedEntity(String name) throws SAXException {
410: if (convertorConfigNestingLevel > 0) {
411: convertorConfigDOMBuilder.skippedEntity(name);
412: } else
413: super .skippedEntity(name);
414: }
415:
416: public void startEntity(String name) throws SAXException {
417: if (convertorConfigNestingLevel > 0) {
418: convertorConfigDOMBuilder.startEntity(name);
419: } else
420: super .startEntity(name);
421: }
422:
423: public void endEntity(String name) throws SAXException {
424: if (convertorConfigNestingLevel > 0) {
425: convertorConfigDOMBuilder.endEntity(name);
426: } else
427: super .endEntity(name);
428: }
429:
430: public void startCDATA() throws SAXException {
431: if (convertorConfigNestingLevel > 0) {
432: convertorConfigDOMBuilder.startCDATA();
433: } else
434: super .startCDATA();
435: }
436:
437: public void endCDATA() throws SAXException {
438: if (convertorConfigNestingLevel > 0) {
439: convertorConfigDOMBuilder.endCDATA();
440: } else
441: super.endCDATA();
442: }
443: }
444: }
|