blob: f72c26c3fe111f50be9930f34aeeee39d6660993 [file] [log] [blame]
/*
* 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();
}
}
}
}