/* * 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.binding; import java.util.ArrayList; import java.util.Stack; import org.apache.avalon.framework.activity.Disposable; import org.apache.avalon.framework.activity.Initializable; import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.avalon.framework.context.Context; import org.apache.avalon.framework.context.ContextException; import org.apache.avalon.framework.context.Contextualizable; import org.apache.avalon.framework.logger.AbstractLogEnabled; import org.apache.avalon.framework.logger.Logger; import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; import org.apache.avalon.framework.service.Serviceable; import org.apache.avalon.framework.thread.ThreadSafe; import org.apache.excalibur.source.Source; import org.apache.excalibur.source.SourceResolver; import org.apache.cocoon.components.LifecycleHelper; import org.apache.cocoon.forms.CacheManager; import org.apache.cocoon.forms.binding.library.Library; import org.apache.cocoon.forms.binding.library.LibraryException; import org.apache.cocoon.forms.binding.library.LibraryManager; import org.apache.cocoon.forms.binding.library.LibraryManagerImpl; import org.apache.cocoon.forms.datatype.DatatypeManager; import org.apache.cocoon.forms.util.DomHelper; import org.apache.cocoon.forms.util.SimpleServiceSelector; import org.apache.cocoon.util.location.LocationAttributes; import org.apache.commons.lang.exception.NestableRuntimeException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.InputSource; /** * JXPathBindingManager provides an implementation of {@link BindingManager}by * usage of the <a href="http://jakarta.apache.org/commons/jxpath/index.html"> * JXPath package </a>. * * @version $Id$ */ public class JXPathBindingManager extends AbstractLogEnabled implements BindingManager, Contextualizable, Serviceable, Configurable, Initializable, Disposable, ThreadSafe { private static final String PREFIX = "CocoonFormBinding:"; protected ServiceManager manager; protected DatatypeManager datatypeManager; private Configuration configuration; protected SimpleServiceSelector bindingBuilderSelector; private CacheManager cacheManager; private Context avalonContext; protected LibraryManagerImpl libraryManager; /** * Java 1.3 logger access method. * <br> * Access to {#getLogger} from inner class on Java 1.3 causes NoSuchMethod error. */ protected Logger getMyLogger() { return getLogger(); } public void contextualize(Context context) throws ContextException { this.avalonContext = context; } public void service(ServiceManager manager) throws ServiceException { this.manager = manager; this.datatypeManager = (DatatypeManager) manager.lookup(DatatypeManager.ROLE); this.cacheManager = (CacheManager) manager.lookup(CacheManager.ROLE); } public void configure(Configuration configuration) throws ConfigurationException { this.configuration = configuration; } public void initialize() throws Exception { bindingBuilderSelector = new SimpleServiceSelector("binding", JXPathBindingBuilderBase.class); LifecycleHelper.setupComponent(bindingBuilderSelector, getLogger(), this.avalonContext, this.manager, configuration.getChild("bindings")); libraryManager = new LibraryManagerImpl(); libraryManager.setBindingManager(this); LifecycleHelper.setupComponent(libraryManager, getLogger(), this.avalonContext, this.manager, configuration.getChild("library")); } public Binding createBinding(Source source) throws BindingException { Binding binding = (Binding) this.cacheManager.get(source, PREFIX); if (binding != null && !binding.isValid()) { binding = null; //invalidate } if (binding == null) { try { // Retrieve the input source of the binding file InputSource is = new InputSource(source.getInputStream()); is.setSystemId(source.getURI()); Document doc = DomHelper.parse(is, this.manager); binding = createBinding(doc.getDocumentElement()); this.cacheManager.set(binding, source, PREFIX); } catch (BindingException e) { throw e; } catch (Exception e) { throw new BindingException("Error creating binding from " + source.getURI(), e); } } return binding; } public Binding createBinding(String bindingURI) throws BindingException { SourceResolver sourceResolver = null; Source source = null; try { try { sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); source = sourceResolver.resolveURI(bindingURI); } catch (Exception e) { throw new BindingException("Error resolving binding source: " + bindingURI); } return createBinding(source); } finally { if (source != null) { sourceResolver.release(source); } if (sourceResolver != null) { manager.release(sourceResolver); } } } public Binding createBinding(Element bindingElement) throws BindingException { Binding binding = null; if (BindingManager.NAMESPACE.equals(bindingElement.getNamespaceURI())) { binding = getBuilderAssistant().getBindingForConfigurationElement(bindingElement); ((JXPathBindingBase) binding).enableLogging(getLogger()); if (getLogger().isDebugEnabled()) { getLogger().debug("Creation of new binding finished. " + binding); } } else { if (getLogger().isDebugEnabled()) { getLogger().debug("Root Element of said binding file is in wrong namespace."); } } return binding; } public Assistant getBuilderAssistant() { return new Assistant(); } public void dispose() { if (this.bindingBuilderSelector != null) { this.bindingBuilderSelector.dispose(); this.bindingBuilderSelector = null; } this.manager.release(this.datatypeManager); this.datatypeManager = null; this.manager.release(this.cacheManager); this.cacheManager = null; this.manager = null; } /** * Assistant Inner class discloses enough features to the created * childBindings to recursively * * This patterns was chosen to prevent Inversion Of Control between this * factory and its builder classes (that could be provided by third * parties.) */ public class Assistant { private BindingBuilderContext context = new BindingBuilderContext(); private Stack contextStack = new Stack(); private JXPathBindingBuilderBase getBindingBuilder(String bindingType) throws BindingException { try { return (JXPathBindingBuilderBase) bindingBuilderSelector.select(bindingType); } catch (ServiceException e) { throw new BindingException("Cannot handle binding element '" + bindingType + "'.", e); } } /** * Creates a {@link Binding} following the specification in the * provided config element. */ public JXPathBindingBase getBindingForConfigurationElement(Element configElm) throws BindingException { String bindingType = configElm.getLocalName(); JXPathBindingBuilderBase bindingBuilder = getBindingBuilder(bindingType); boolean flag = false; if (context.getLocalLibrary() == null) { // FIXME Use newLibrary()? Library lib = new Library(libraryManager, getBuilderAssistant()); lib.enableLogging(getMyLogger()); context.setLocalLibrary(lib); lib.setSourceURI(LocationAttributes.getURI(configElm)); flag = true; } if (context.getLocalLibrary() != null && configElm.hasAttribute("extends")) { try { context.setSuperBinding(context.getLocalLibrary().getBinding(configElm.getAttribute("extends"))); } catch (LibraryException e) { // throw new RuntimeException("Error extending binding! (at "+DomHelper.getLocation(configElm)+")", e); throw new NestableRuntimeException("Error extending binding! (at " + DomHelper.getLocation(configElm) + ")", e); } } else { context.setSuperBinding(null); } JXPathBindingBase childBinding = bindingBuilder.buildBinding(configElm, this); if (flag && childBinding != null) { childBinding.setEnclosingLibrary(context.getLocalLibrary()); } // this might get called unnecessarily, but solves issues with the libraries if (childBinding != null) { childBinding.enableLogging(getMyLogger()); } return childBinding; } private JXPathBindingBase[] mergeBindings(JXPathBindingBase[] existing, JXPathBindingBase[] extra) { if (existing == null || existing.length == 0) { return extra; } if (extra == null || extra.length == 0) { return existing; } // have to do it the stupid painter way.. ArrayList list = new ArrayList(existing.length); for (int i = 0; i < existing.length; i++) { list.add(existing[i]); } for (int i = 0; i < extra.length; i++) { if (extra[i].getId() == null) { list.add(extra[i]); } else { // try to replace existing one boolean match = false; for(int j=0; j<list.size(); j++) { if(extra[i].getId().equals(((JXPathBindingBase)list.get(j)).getId())) { list.set(j,extra[i]); match = true; break; // stop searching } } // if no match, just add if (!match) { list.add(extra[i]); } } } return (JXPathBindingBase[])list.toArray(new JXPathBindingBase[list.size()]); } /** * proxy for compatibility * */ public JXPathBindingBase[] makeChildBindings(Element parentElement) throws BindingException { return makeChildBindings(parentElement,new JXPathBindingBase[0]); } /** * Makes an array of childBindings for the child-elements of the * provided configuration element. */ public JXPathBindingBase[] makeChildBindings(Element parentElement, JXPathBindingBase[] existingBindings) throws BindingException { if (existingBindings == null) { existingBindings = new JXPathBindingBase[0]; } if (parentElement != null) { Element[] childElements = DomHelper.getChildElements( parentElement, BindingManager.NAMESPACE); if (childElements.length > 0) { JXPathBindingBase[] childBindings = new JXPathBindingBase[childElements.length]; for (int i = 0; i < childElements.length; i++) { pushContext(); context.setSuperBinding(null); String id = DomHelper.getAttribute(childElements[i], "id", null); String path = DomHelper.getAttribute(childElements[i], "path", null); if (context.getLocalLibrary() != null && childElements[i].getAttribute("extends") != null) { try { context.setSuperBinding(context.getLocalLibrary().getBinding(childElements[i].getAttribute("extends"))); if (context.getSuperBinding() == null) { // not found in library context.setSuperBinding(getBindingByIdOrPath(id, path, existingBindings)); } } catch (LibraryException e) { throw new BindingException("Error extending binding! (at "+DomHelper.getLocation(childElements[i])+")",e); } } childBindings[i] = getBindingForConfigurationElement(childElements[i]); popContext(); } return mergeBindings(existingBindings,childBindings); } } return existingBindings; } private JXPathBindingBase getBindingByIdOrPath(String id, String path, JXPathBindingBase[] bindings) { String name = id; if (name == null) { name = "Context:"+path; } for (int i = 0; i < bindings.length; i++) { if (name.equals(bindings[i].getId())) { return bindings[i]; } } return null; } public DatatypeManager getDatatypeManager() { return datatypeManager; } public ServiceManager getServiceManager() { return manager; } public LibraryManager getLibraryManager() { return libraryManager; } public BindingBuilderContext getContext() { return this.context; } private void pushContext() { BindingBuilderContext c = new BindingBuilderContext(context); contextStack.push(context); context = c; } private void popContext() { if (!contextStack.empty()) { context = (BindingBuilderContext) contextStack.pop(); } else { context = new BindingBuilderContext(); } } } }