| /* |
| * 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.forms.datatype; |
| |
| import java.io.IOException; |
| import java.io.UnsupportedEncodingException; |
| import java.util.Locale; |
| |
| import org.apache.avalon.framework.context.Context; |
| import org.apache.avalon.framework.service.ServiceManager; |
| import org.apache.cocoon.ProcessingException; |
| import org.apache.cocoon.components.ContextHelper; |
| import org.apache.cocoon.components.source.SourceUtil; |
| import org.apache.cocoon.environment.Request; |
| import org.apache.cocoon.forms.FormsConstants; |
| import org.apache.cocoon.forms.datatype.convertor.ConversionResult; |
| import org.apache.cocoon.forms.datatype.convertor.Convertor; |
| import org.apache.cocoon.forms.datatype.convertor.DefaultFormatCache; |
| import org.apache.cocoon.util.NetUtils; |
| import org.apache.cocoon.xml.AbstractXMLPipe; |
| import org.apache.cocoon.xml.AttributesImpl; |
| import org.apache.cocoon.xml.SaxBuffer; |
| import org.apache.cocoon.xml.XMLUtils; |
| import org.apache.cocoon.xml.dom.DOMBuilder; |
| import org.apache.excalibur.source.Source; |
| import org.apache.excalibur.source.SourceResolver; |
| import org.w3c.dom.Element; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * SelectionList implementation that always reads its content from the source |
| * each time it is requested. |
| * <p> |
| * This list is filterable, and if a filter is provided, the "filter" parameter |
| * is appended to the URL, e.g. <code><fd:selection-list src="cocoon://pipeline.xml"/></code> |
| * will call, given the "<code>foo</code>" filter value, the URL <code>cocoon://pipeline.xml?filter=foo</code>. |
| * <p> |
| * Note: the class {@link SelectionListBuilder} also interprets the same |
| * <code>fd:selection-list</code> XML, so if anything changes here to how that |
| * XML is interpreted, it also needs to change over there and vice versa.</p> |
| * |
| * @version $Id$ |
| */ |
| public class DynamicSelectionList implements FilterableSelectionList { |
| private String src; |
| private boolean usePerRequestCache; |
| private Datatype datatype; |
| private ServiceManager serviceManager; |
| private Context context; |
| |
| /** |
| * @param datatype |
| * @param src |
| * @param usePerRequestCache |
| * @param serviceManager |
| * @param context |
| */ |
| public DynamicSelectionList(Datatype datatype, String src, boolean usePerRequestCache, ServiceManager serviceManager, Context context) { |
| this.datatype = datatype; |
| this.src = src; |
| this.serviceManager = serviceManager; |
| this.usePerRequestCache = usePerRequestCache; |
| this.context = context; |
| } |
| |
| /** |
| * Creates a DynamicSelectionList without caching |
| * @param datatype - |
| * @param src - |
| * @param serviceManager - |
| */ |
| public DynamicSelectionList(Datatype datatype, String src, ServiceManager serviceManager) { |
| this.usePerRequestCache = false; |
| this.context = null; |
| this.datatype = datatype; |
| this.src = src; |
| this.serviceManager = serviceManager; |
| } |
| |
| public Datatype getDatatype() { |
| return datatype; |
| } |
| |
| /* |
| * This method is only used by a test case and by the public version |
| * of generateSaxFragment. |
| */ |
| void generateSaxFragment(ContentHandler contentHandler, Locale locale, Source source) |
| throws ProcessingException, SAXException, IOException { |
| SelectionListHandler handler = new SelectionListHandler(locale); |
| handler.setContentHandler(contentHandler); |
| SourceUtil.toSAX(serviceManager, source, null, handler); |
| } |
| |
| /* |
| * This method generate SaxFragment directly from source. |
| */ |
| private void generateSaxFragmentFromSrc(String url, ContentHandler contentHandler, Locale locale) throws SAXException { |
| SourceResolver sourceResolver = null; |
| Source source = null; |
| try { |
| sourceResolver = (SourceResolver)serviceManager.lookup(SourceResolver.ROLE); |
| source = sourceResolver.resolveURI(url); |
| generateSaxFragment(contentHandler, locale, source); |
| } catch (SAXException e) { |
| throw e; |
| } catch (Exception e) { |
| throw new SAXException("Error while generating selection list: " + e.getMessage(), e); |
| } finally { |
| if (sourceResolver != null) { |
| if (source != null) { |
| try { sourceResolver.release(source); } catch (Exception e) {} |
| } |
| serviceManager.release(sourceResolver); |
| } |
| } |
| } |
| |
| public void generateSaxFragment(ContentHandler contentHandler, Locale locale, String filter) throws SAXException { |
| |
| String url = this.src; |
| if (filter != null) { |
| if (url.indexOf('?') != -1) { |
| try { |
| url += "&filter=" + NetUtils.encode(filter, "utf-8"); |
| } catch (UnsupportedEncodingException ignore) { |
| // utf-8 is always supported |
| } |
| } else { |
| try { |
| url += "?filter=" + NetUtils.encode(filter, "utf-8"); |
| } catch (UnsupportedEncodingException ignore) { |
| // utf-8 is always supported |
| } |
| } |
| } |
| |
| if (usePerRequestCache) { |
| // Search the sax buffer in request attributes |
| Request request = ContextHelper.getRequest(this.context); |
| String attributeName = "DynamicSelectionListCache/" + url; |
| SaxBuffer saxBuffer = (SaxBuffer)request.getAttribute(attributeName); |
| |
| if (saxBuffer == null) { |
| // Not found: generate the list and store it |
| saxBuffer = new SaxBuffer(); |
| generateSaxFragmentFromSrc(url, saxBuffer, locale); |
| request.setAttribute(attributeName, saxBuffer); |
| } |
| |
| // Output the stored saxBuffer to the contentHandler |
| saxBuffer.toSAX(contentHandler); |
| } else { // We don't use usePerRequestCache => re-read from the source. |
| generateSaxFragmentFromSrc(url, contentHandler, locale); |
| } |
| } |
| |
| public void generateSaxFragment(ContentHandler contentHandler, Locale locale) throws SAXException { |
| // Generate with no filter |
| generateSaxFragment(contentHandler, locale, (String)null); |
| } |
| |
| /** |
| * XMLConsumer used to handle selection lists generated on the fly. |
| */ |
| public class SelectionListHandler extends AbstractXMLPipe { |
| private Object currentValue; |
| private String currentValueAsString; |
| private boolean hasLabel; |
| private Locale locale; |
| /** The convertor used to parse the values in the selection list. */ |
| private Convertor convertor; |
| private DOMBuilder convertorConfigDOMBuilder; |
| private int convertorConfigNestingLevel = 0; |
| private Convertor.FormatCache fromFormatCache = new DefaultFormatCache(); |
| private Convertor.FormatCache toFormatCache = new DefaultFormatCache(); |
| |
| public SelectionListHandler(Locale locale) { |
| this.locale = locale; |
| } |
| |
| public void startDocument() |
| throws SAXException { |
| } |
| |
| public void endDocument() |
| throws SAXException { |
| } |
| |
| public void endDTD() |
| throws SAXException { |
| } |
| |
| public void startDTD(String name, String publicId, String systemId) |
| throws SAXException { |
| } |
| |
| public void startElement(String namespaceURI, String localName, String qName, Attributes attributes) |
| throws SAXException { |
| if (convertorConfigNestingLevel > 0) { |
| convertorConfigNestingLevel++; |
| convertorConfigDOMBuilder.startElement(namespaceURI, localName, qName, attributes); |
| } else if (namespaceURI.equals(FormsConstants.DEFINITION_NS)) { |
| if (localName.equals("item")) { |
| if (convertor == null) { |
| // if no convertor was explicitely configured, use the default one of the datatype |
| convertor = getDatatype().getConvertor(); |
| } |
| hasLabel = false; |
| |
| String unparsedValue = attributes.getValue("value"); |
| if (unparsedValue == null || "".equals(unparsedValue)) { |
| // Empty (or null) value translates into the empty string |
| currentValueAsString = ""; |
| } else { |
| ConversionResult conversionResult = convertor.convertFromString(unparsedValue, locale, fromFormatCache); |
| if (!conversionResult.isSuccessful()) { |
| throw new SAXException("Could not interpret the following value: \"" + unparsedValue + "\"."); |
| } |
| currentValue = conversionResult.getResult(); |
| currentValueAsString = getDatatype().getConvertor().convertToString(currentValue, locale, toFormatCache); |
| } |
| AttributesImpl attrs = new AttributesImpl(); |
| attrs.addCDATAAttribute("value", currentValueAsString); |
| super.startElement(FormsConstants.INSTANCE_NS, localName, FormsConstants.INSTANCE_PREFIX_COLON + localName, attrs); |
| } else if (localName.equals("label")) { |
| hasLabel = true; |
| super.startElement(FormsConstants.INSTANCE_NS, localName, FormsConstants.INSTANCE_PREFIX_COLON + localName, attributes); |
| } else if (localName.equals("selection-list")) { |
| super.startElement(FormsConstants.INSTANCE_NS, localName, FormsConstants.INSTANCE_PREFIX_COLON + localName, attributes); |
| } else if (convertor == null && localName.equals("convertor")) { |
| // record the content of this element in a dom-tree |
| convertorConfigDOMBuilder = new DOMBuilder(); |
| convertorConfigDOMBuilder.startElement(namespaceURI, localName, qName, attributes); |
| convertorConfigNestingLevel++; |
| } else { |
| super.startElement(namespaceURI, localName, qName, attributes); |
| } |
| } else { |
| super.startElement(namespaceURI, localName, qName, attributes); |
| } |
| } |
| |
| private static final String LABEL_EL = "label"; |
| |
| public void endElement(String namespaceURI, String localName, String qName) |
| throws SAXException { |
| if (convertorConfigNestingLevel > 0) { |
| convertorConfigNestingLevel--; |
| convertorConfigDOMBuilder.endElement(namespaceURI, localName, qName); |
| if (convertorConfigNestingLevel == 0) { |
| Element convertorElement = convertorConfigDOMBuilder.getDocument().getDocumentElement(); |
| try { |
| convertor = getDatatype().getBuilder().buildConvertor(convertorElement); |
| } catch (Exception e) { |
| throw new SAXException("Error building convertor from convertor configuration embedded in selection list XML.", e); |
| } |
| } |
| } else if (namespaceURI.equals(FormsConstants.DEFINITION_NS)) { |
| if (localName.equals("item")) { |
| if (!hasLabel) { |
| // make the label now |
| super.startElement(FormsConstants.INSTANCE_NS, LABEL_EL, FormsConstants.INSTANCE_PREFIX_COLON + LABEL_EL, XMLUtils.EMPTY_ATTRIBUTES); |
| super.characters(currentValueAsString.toCharArray(), 0, currentValueAsString.length()); |
| super.endElement(FormsConstants.INSTANCE_NS, LABEL_EL, FormsConstants.INSTANCE_PREFIX_COLON + LABEL_EL); |
| } |
| super.endElement(FormsConstants.INSTANCE_NS, localName, FormsConstants.INSTANCE_PREFIX_COLON + localName); |
| } else if (localName.equals("label")) { |
| super.endElement(FormsConstants.INSTANCE_NS, localName, FormsConstants.INSTANCE_PREFIX_COLON + localName); |
| } else if (localName.equals("selection-list")) { |
| super.endElement(FormsConstants.INSTANCE_NS, localName, FormsConstants.INSTANCE_PREFIX_COLON + localName); |
| } else { |
| super.endElement(namespaceURI, localName, qName); |
| } |
| } else { |
| super.endElement(namespaceURI, localName, qName); |
| } |
| } |
| |
| public void comment(char ch[], int start, int len) |
| throws SAXException { |
| if (convertorConfigNestingLevel > 0) { |
| convertorConfigDOMBuilder.comment(ch, start, len); |
| } else |
| super.comment(ch, start, len); |
| } |
| |
| public void startPrefixMapping(String prefix, String uri) |
| throws SAXException { |
| if (convertorConfigNestingLevel > 0) { |
| convertorConfigDOMBuilder.startPrefixMapping(prefix, uri); |
| } else |
| super.startPrefixMapping(prefix, uri); |
| } |
| |
| public void endPrefixMapping(String prefix) |
| throws SAXException { |
| if (convertorConfigNestingLevel > 0) { |
| convertorConfigDOMBuilder.endPrefixMapping(prefix); |
| } else |
| super.endPrefixMapping(prefix); |
| } |
| |
| public void characters(char c[], int start, int len) |
| throws SAXException { |
| if (convertorConfigNestingLevel > 0) { |
| convertorConfigDOMBuilder.characters(c, start, len); |
| } else |
| super.characters(c, start, len); |
| } |
| |
| public void ignorableWhitespace(char c[], int start, int len) |
| throws SAXException { |
| if (convertorConfigNestingLevel > 0) { |
| convertorConfigDOMBuilder.ignorableWhitespace(c, start, len); |
| } else |
| super.ignorableWhitespace(c, start, len); |
| } |
| |
| public void processingInstruction(String target, String data) |
| throws SAXException { |
| if (convertorConfigNestingLevel > 0) { |
| convertorConfigDOMBuilder.processingInstruction(target, data); |
| } else |
| super.processingInstruction(target, data); |
| } |
| |
| public void skippedEntity(String name) |
| throws SAXException { |
| if (convertorConfigNestingLevel > 0) { |
| convertorConfigDOMBuilder.skippedEntity(name); |
| } else |
| super.skippedEntity(name); |
| } |
| |
| public void startEntity(String name) |
| throws SAXException { |
| if (convertorConfigNestingLevel > 0) { |
| convertorConfigDOMBuilder.startEntity(name); |
| } else |
| super.startEntity(name); |
| } |
| |
| public void endEntity(String name) |
| throws SAXException { |
| if (convertorConfigNestingLevel > 0) { |
| convertorConfigDOMBuilder.endEntity(name); |
| } else |
| super.endEntity(name); |
| } |
| |
| public void startCDATA() |
| throws SAXException { |
| if (convertorConfigNestingLevel > 0) { |
| convertorConfigDOMBuilder.startCDATA(); |
| } else |
| super.startCDATA(); |
| } |
| |
| public void endCDATA() |
| throws SAXException { |
| if (convertorConfigNestingLevel > 0) { |
| convertorConfigDOMBuilder.endCDATA(); |
| } else |
| super.endCDATA(); |
| } |
| } |
| } |