| /* |
| * 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.Collection; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import org.apache.avalon.framework.logger.AbstractLogEnabled; |
| |
| import org.apache.cocoon.forms.binding.library.Library; |
| import org.apache.cocoon.forms.formmodel.Widget; |
| import org.apache.cocoon.util.jxpath.DOMFactory; |
| |
| import org.apache.commons.jxpath.AbstractFactory; |
| import org.apache.commons.jxpath.JXPathContext; |
| import org.apache.commons.jxpath.Pointer; |
| import org.apache.commons.jxpath.ri.model.beans.BeanPropertyPointer; |
| import org.apache.commons.jxpath.util.TypeUtils; |
| import org.apache.commons.lang.exception.NestableRuntimeException; |
| |
| /** |
| * Provides a base class for hooking up Binding implementations that use the |
| * Jakarta Commons <a href="http://jakarta.apache.org/commons/jxpath/index.html"> |
| * JXPath package</a>. |
| * |
| * @version $Id$ |
| */ |
| public abstract class JXPathBindingBase extends AbstractLogEnabled |
| implements Binding { |
| |
| /** |
| * the local library, if this is the top binding |
| */ |
| private Library enclosingLibrary; |
| |
| /** |
| * Object holding the values of the common objects on all Bindings. |
| */ |
| private final JXPathBindingBuilderBase.CommonAttributes commonAtts; |
| |
| /** |
| * Parent binding of this binding. |
| */ |
| protected Binding parent; |
| |
| /** |
| * Cache of class definitions |
| */ |
| protected Map classes; |
| |
| |
| protected JXPathBindingBase(JXPathBindingBuilderBase.CommonAttributes commonAtts) { |
| this.commonAtts = commonAtts; |
| } |
| |
| /** |
| * @see org.apache.cocoon.forms.binding.Binding#getEnclosingLibrary() |
| */ |
| public Library getEnclosingLibrary() { |
| if (parent != null) { |
| return parent.getEnclosingLibrary(); |
| } else { |
| return enclosingLibrary; |
| } |
| } |
| |
| /** |
| * @see org.apache.cocoon.forms.binding.Binding#setEnclosingLibrary(org.apache.cocoon.forms.binding.library.Library) |
| */ |
| public void setEnclosingLibrary(Library lib) { |
| this.enclosingLibrary = lib; |
| } |
| |
| /** |
| * @see org.apache.cocoon.forms.binding.Binding#isValid() |
| */ |
| public boolean isValid() { |
| if (this.enclosingLibrary == null) { |
| if (parent != null) { |
| return parent.isValid(); |
| } |
| return true; // no library used |
| } |
| |
| try { |
| return !this.enclosingLibrary.dependenciesHaveChanged(); |
| } catch (Exception e) { |
| getLogger().error("Error checking dependencies!", e); |
| throw new NestableRuntimeException("Error checking dependencies!", e); |
| } |
| } |
| |
| public JXPathBindingBuilderBase.CommonAttributes getCommonAtts() { |
| return this.commonAtts; |
| } |
| |
| /** |
| * Gets source location of this binding. |
| */ |
| public String getLocation() { |
| return this.commonAtts.location; |
| } |
| |
| /** |
| * Sets parent binding. |
| */ |
| public void setParent(Binding binding) { |
| this.parent = binding; |
| } |
| |
| /** |
| * Returns binding definition id. |
| */ |
| public String getId() { |
| return null; |
| } |
| |
| /** |
| * @see org.apache.cocoon.forms.binding.Binding#getClass(java.lang.String) |
| */ |
| public Binding getClass(String id) { |
| |
| Binding classBinding = null; |
| try { |
| if (this.enclosingLibrary != null && (classBinding = this.enclosingLibrary.getBinding(id)) != null) { |
| return classBinding; |
| } |
| } catch (Exception e) { /* ignored */ } |
| |
| if (classes != null) { |
| // Query cache for class |
| classBinding = (Binding)classes.get(id); |
| } |
| |
| if (classBinding == null) { |
| // Query parent for class |
| if (parent != null) { |
| classBinding = parent.getClass(id); |
| // dont cache, doesn't matter and makes things complicated with libraries |
| // ************************************** |
| // Cache result |
| /*if (classes == null) { |
| classes = new HashMap(); |
| } |
| classes.put(id, classBinding);*/ |
| // ************************************** |
| } else { |
| throw new RuntimeException("Class \"" + id + "\" not found (" + getLocation() + ")"); |
| } |
| } |
| |
| return classBinding; |
| } |
| |
| /** |
| * Helper method that selects a child-widget with a given id from a parent. |
| * |
| * @param parent containing the child-widget to return. |
| * @param id of the childWidget to find, if this is <code>null</code> then the parent is returned. |
| * @return the selected widget |
| * |
| * @throws RuntimeException if the id is not null and points to a |
| * child-widget that cannot be found. |
| */ |
| protected Widget selectWidget(Widget parent, String id) { |
| if (id == null) { |
| return parent; |
| } |
| |
| Widget childWidget = parent.lookupWidget(id); |
| if (childWidget == null) { |
| String containerId = parent.getRequestParameterName(); |
| if (containerId == null || "".equals(containerId)) { |
| containerId = "top-level form-widget"; |
| } else { |
| containerId = "container \"" + containerId + "\""; |
| } |
| throw new RuntimeException(getClass().getName() + " (" + getLocation() + "): Widget \"" + |
| id + "\" does not exist in the " + containerId + |
| " (" + parent.getLocation() + ")."); |
| } |
| |
| return childWidget; |
| } |
| |
| /** |
| * Performs the actual load binding regardless of the configured value of the "direction" attribute. |
| * Abstract method that subclasses need to implement for specific activity. |
| */ |
| public abstract void doLoad(Widget frmModel, JXPathContext jxpc) |
| throws BindingException; |
| |
| /** |
| * Redefines the Binding action as working on a JXPathContext Type rather |
| * then on generic objects. |
| * Executes the actual loading via {@link #doLoad(Widget, JXPathContext)} |
| * depending on the configured value of the "direction" attribute. |
| */ |
| public final void loadFormFromModel(Widget frmModel, JXPathContext jxpc) |
| throws BindingException { |
| boolean inheritedLeniency = jxpc.isLenient(); |
| applyLeniency(jxpc); |
| applyNSDeclarations(jxpc); |
| if (this.commonAtts.loadEnabled) { |
| doLoad(frmModel, jxpc); |
| } |
| jxpc.setLenient(inheritedLeniency); |
| } |
| |
| /** |
| * Hooks up with the more generic Binding of any objectModel by wrapping |
| * it up in a JXPathContext object and then transfering control over to |
| * the new overloaded version of this method. |
| * |
| * @see org.apache.cocoon.forms.binding.Binding#loadFormFromModel(org.apache.cocoon.forms.formmodel.Widget, java.lang.Object) |
| */ |
| public final void loadFormFromModel(Widget frmModel, Object objModel) |
| throws BindingException { |
| if (objModel != null) { |
| JXPathContext jxpc = makeJXPathContext(objModel); |
| loadFormFromModel(frmModel, jxpc); |
| } else { |
| throw new NullPointerException( |
| "null object passed to loadFormFromModel() method"); |
| } |
| } |
| |
| /** |
| * Performs the actual save binding regardless of the configured value of the "direction" attribute. |
| * Abstract method that subclasses need to implement for specific activity. |
| */ |
| public abstract void doSave(Widget frmModel, JXPathContext jxpc) |
| throws BindingException; |
| |
| /** |
| * Redefines the Binding action as working on a JXPathContext Type rather |
| * then on generic objects. |
| * Executes the actual saving via {@link #doSave(Widget, JXPathContext)} |
| * depending on the configured value of the "direction" attribute. |
| */ |
| public final void saveFormToModel(Widget frmModel, JXPathContext jxpc) |
| throws BindingException{ |
| boolean inheritedLeniency = jxpc.isLenient(); |
| applyLeniency(jxpc); |
| applyNSDeclarations(jxpc); |
| if (this.commonAtts.saveEnabled) { |
| doSave(frmModel, jxpc); |
| } |
| jxpc.setLenient(inheritedLeniency); |
| } |
| |
| /** |
| * Hooks up with the more generic Binding of any objectModel by wrapping |
| * it up in a JXPathContext object and then transfering control over to |
| * the new overloaded version of this method. |
| * |
| * @see org.apache.cocoon.forms.binding.Binding#saveFormToModel(org.apache.cocoon.forms.formmodel.Widget, java.lang.Object) |
| */ |
| public void saveFormToModel(Widget frmModel, Object objModel) |
| throws BindingException { |
| if (objModel != null) { |
| JXPathContext jxpc = makeJXPathContext(objModel); |
| saveFormToModel(frmModel, jxpc); |
| } else { |
| throw new NullPointerException( |
| "null object passed to saveFormToModel() method"); |
| } |
| } |
| |
| private void applyLeniency(JXPathContext jxpc) { |
| if (this.commonAtts.leniency != null) { |
| jxpc.setLenient(this.commonAtts.leniency.booleanValue()); |
| } |
| } |
| |
| private void applyNSDeclarations(JXPathContext jxpc) { |
| if (this.commonAtts.nsDeclarations != null) { |
| Iterator keysIter = this.commonAtts.nsDeclarations.keySet().iterator(); |
| while (keysIter.hasNext()) { |
| String nsuri = (String) keysIter.next(); |
| String pfx = (String) this.commonAtts.nsDeclarations.get(nsuri); |
| jxpc.registerNamespace(pfx, nsuri); |
| } |
| } |
| } |
| |
| private JXPathContext makeJXPathContext(Object objModel) { |
| JXPathContext jxpc; |
| if (!(objModel instanceof JXPathContext)) { |
| jxpc = JXPathContext.newContext(objModel); |
| jxpc.setLenient(true); |
| |
| AbstractFactory jxPathFactory; |
| if (commonAtts.jxPathFactory != null) |
| jxPathFactory = commonAtts.jxPathFactory; |
| else |
| jxPathFactory = new BindingJXPathFactory(); |
| jxpc.setFactory(jxPathFactory); |
| } else { |
| jxpc = (JXPathContext) objModel; |
| } |
| return jxpc; |
| } |
| |
| /** |
| * JXPath factory that combines the DOMFactory and support for collections. |
| */ |
| private static class BindingJXPathFactory extends DOMFactory { |
| |
| public boolean createObject(JXPathContext context, Pointer pointer, Object parent, String name, int index) { |
| if (createCollectionItem(context, pointer, parent, name, index)) { |
| return true; |
| // AG: If this is a bean, then the object is supposed to exists. |
| } else if (pointer instanceof BeanPropertyPointer) { |
| return createBeanField(context, pointer, parent, name, index); |
| } else { |
| return super.createObject(context, pointer, parent, name, index); |
| } |
| } |
| |
| private boolean createCollectionItem(JXPathContext context, Pointer pointer, Object parent, String name, int index) { |
| // FIXME: don't clearly understand how this works. |
| // see http://marc.theaimsgroup.com/?l=xml-cocoon-dev&m=111148567029114&w=2 |
| final Object o = context.getValue(name); |
| if (o == null) { |
| return false; |
| } |
| if (o instanceof Collection) { |
| ((Collection)o).add(null); |
| } else if(o.getClass().isArray()) { |
| // not yet supported |
| return false; |
| } else { |
| return false; |
| } |
| return true; |
| } |
| |
| // AG: Create the Object for the field as defined in the Bean. |
| // The value we will set here is not important. JXPath knows that it is UNITIALIZED. |
| // if we set it Pointer Value to null then the code will throw an exception. |
| // |
| // In short, there is no harm. The value will never show up. |
| // TODO: Manage other forms' types as Date, Bean and others not covered by this method. |
| private boolean createBeanField(JXPathContext context, Pointer pointer, Object parent, String name, int index) { |
| try { |
| Class clazz = parent.getClass().getDeclaredField(name).getType(); |
| Object o = context.getValue(name); |
| if (o == null) { |
| final Class[] parametersTypes = {String.class}; |
| final Object[] initArgs = {"0"}; |
| try { |
| // AG: Here we service Booleans, Strings and Number() + his Direct know subclasses: |
| // (BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, Short) |
| // as well as other classes that use an String as Constructor parameter. |
| o = clazz.getConstructor(parametersTypes).newInstance(initArgs); |
| } catch (Exception e) { |
| // AG: The class has not a constructor using a String as a parameter. |
| // ie: Boolean(String), Integer(String), etc. |
| // Lets try with a constructor with no parameters. ie: Number(). |
| o = clazz.newInstance(); |
| } |
| } else if (TypeUtils.canConvert(o, clazz)) { |
| o = TypeUtils.convert(o, clazz); |
| } |
| if (o != null) { |
| pointer.setValue(o); |
| return true; // OK. We have an initial Object of the right Class initialized. |
| } |
| } catch (Exception e) { |
| // TODO: Output info in logs. |
| } |
| return false; |
| } |
| } |
| } |