/* * 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.Map; import org.apache.avalon.framework.logger.LogEnabled; import org.apache.avalon.framework.logger.Logger; import org.apache.cocoon.forms.util.DomHelper; import org.apache.commons.jxpath.AbstractFactory; import org.apache.commons.lang.BooleanUtils; import org.w3c.dom.Element; /** * Abstract base class enabling logging and supporting the interpretation of * common configuration settings on all specific implementations of * {@link org.apache.cocoon.forms.binding.JXPathBindingBase}. * * Common supported configurations: {@link #getCommonAttributes(Element)} * <ul> * <li>Attribute direction="load|save|both": defaults to 'both'</li> * <li>Attribute lenient="true|false|[undefined]": defaults to [undefined] * which means: "lenient mode inherited from parent" </li> * </ul> * * @version $Id$ */ public abstract class JXPathBindingBuilderBase implements LogEnabled { private Logger logger; /** * Receives the Avalon logger to use. */ public void enableLogging(Logger logger) { this.logger = logger; if (logger.isDebugEnabled()) { logger.debug("JXPathBindingBuilderBase got logger..."); } } /** * Makes the logger available to the subclasses. * @return Logger */ protected Logger getLogger() { return this.logger; } /** * Builds a configured binding object based on the configuration as * described in the bindingElement. The BuilderMap can be used to * find appropriate builders for possible subBinders. * * @param bindingElm * @param assistant * @return JXPathBindingBase */ public abstract JXPathBindingBase buildBinding(Element bindingElm, JXPathBindingManager.Assistant assistant) throws BindingException; /** * Helper method for interpreting the common attributes which are supported * on each of the Bindings. These are * <br> * <code>@direction</code> can hold one of the following values: * <ol><li><code>'load'</code>: This binding will only load.</li> * <li><code>'save'</code>: This binding will only save.</li> * <li><code>'both'</code>: This binding will perform both operations.</li> * </ol> * <br> * <code>@lenient</code> can either be: * <ol><li><code>'true'</code>: This binding will set the jxpath context to * be lenient towards the usage of inexisting paths on the back-end model.</li> * <li><code>'false'</code>: This binding will set the jxpath context to be * strict and throwing exceptions for the usage of inexisting paths on the * back-end model.</li> * <li><code>(unset)</code>: This binding will not change the leniency behaviour * on the jxpath this binding receives from his parent binding.</li> * </ol> * @param bindingElm * @return an instance of CommonAttributes * @throws BindingException */ protected static CommonAttributes getCommonAttributes(Element bindingElm) throws BindingException { try { String location = DomHelper.getLocation(bindingElm); //TODO: should we eventually remove this? //throw an error if people are still using the old-style @read-only or @readonly if (DomHelper.getAttributeAsBoolean(bindingElm, "readonly", false)) { throw new BindingException("Error in the binding." + "\nThe usage of the attribute @readonly has been deprecated in favour of @direction.", DomHelper.getLocationObject(bindingElm)); } if (DomHelper.getAttributeAsBoolean(bindingElm, "read-only", false)) { throw new BindingException("Error in the binding." + "\nThe usage of the attribute @read-only has been deprecated in favour of @direction.", DomHelper.getLocationObject(bindingElm)); } String direction = DomHelper.getAttribute(bindingElm, "direction", "both"); String leniency = DomHelper.getAttribute(bindingElm, "lenient", null); //TODO: current jxpath is not inheriting registered namespaces over to // child-relative jxpath contexts --> because of that we can't just // remember the getLocalNSDeclarations but need the full set from // getInheritedNSDeclarations // IMPORTANT NOTE: if jxpath would change this behaviour we would however // still need to be able to unregister namespace-declarations // (in a smart way: unhide what is possably available from your parent. // So both changes to jxpath need to be available before changing the below. Map nsDeclarationMap = DomHelper.getInheritedNSDeclarations(bindingElm); // we (actually jxpath) doesn't support un-prefixed namespace-declarations: // so we decide to break on those above silently ignoring them if (nsDeclarationMap != null && nsDeclarationMap.values().contains(null)) { throw new BindingException("Error in the binding." + "\nBinding doesn't support having namespace-declarations without explicit prefixes.", DomHelper.getLocationObject(bindingElm)); } String jxPathFactoryName = bindingElm.getAttribute("jxpath-factory"); AbstractFactory jxPathFactory = null; if (jxPathFactoryName != null && jxPathFactoryName.trim().length() > 0) { try { Class jxPathFactoryClass = JXPathBindingBuilderBase.class.getClassLoader().loadClass(jxPathFactoryName); jxPathFactory = (AbstractFactory)jxPathFactoryClass.newInstance(); } catch (Exception e) { throw new BindingException("Error with specified jxpath factory " + jxPathFactoryName, e, DomHelper.getLocationObject(bindingElm)); } } return new CommonAttributes(location, direction, leniency, nsDeclarationMap, jxPathFactory); } catch (BindingException e) { throw e; } catch (Exception e) { throw new BindingException("Error building binding", e, DomHelper.getLocationObject(bindingElm)); } } public static CommonAttributes mergeCommonAttributes(CommonAttributes existing, CommonAttributes extra) { if (extra == null) { return existing; } Boolean leniency; if (existing.leniency == null) { leniency = extra.leniency; } else { leniency = existing.leniency; } String strLeniency = null; if (leniency != null) { strLeniency = leniency.toString(); } String direction = existing.direction; if (extra.direction != null) { // was defined direction = extra.direction; } AbstractFactory jxPathFactory = existing.jxPathFactory; if (extra.jxPathFactory != null) { jxPathFactory = extra.jxPathFactory; } return new CommonAttributes(extra.location, direction, strLeniency, extra.nsDeclarations, jxPathFactory); } /** * CommonAttributes is a simple helper class for holding the distinct data * member fields indicating the activity of the separate load and save * actions of a given binding. */ public static class CommonAttributes { /** * store direction (load/save enabledness) too for easier merging */ String direction; /** * Source location of this binding. */ final String location; /** * Flag which controls whether a binding is active during loading. */ final boolean loadEnabled; /** * Flag which controls whether a binding is active during saving. */ final boolean saveEnabled; /** * Flag which controls whether the jxpath context used by this binding * should be operating in lenient mode or not */ final Boolean leniency; /** * Array of namespace-declarations (prefix-uri pairs) that need to be set on the jxpath */ final Map nsDeclarations; /** * The factory to be set on the JXPath Context object */ final AbstractFactory jxPathFactory; final static CommonAttributes DEFAULT = new CommonAttributes("location unknown", true, true, null, null, null); CommonAttributes(String location, String direction, String leniency, Map nsDeclarations, AbstractFactory jxPathFactory){ this(location, isLoadEnabled(direction), isSaveEnabled(direction), decideLeniency(leniency), nsDeclarations, jxPathFactory); this.direction = direction; } CommonAttributes(String location, boolean loadEnabled, boolean saveEnabled, Boolean leniency, Map nsDeclarations, AbstractFactory jxPathFactory){ this.direction = null; this.location = location; this.loadEnabled = loadEnabled; this.saveEnabled = saveEnabled; this.leniency = leniency; this.nsDeclarations = nsDeclarations; this.jxPathFactory = jxPathFactory; } /** * Interprets the value of the direction attribute into activity of the load action. * @param direction * @return true if direction is either set to "both" or "load" */ private static boolean isLoadEnabled(String direction) { return "both".equals(direction) || "load".equals(direction); } /** * Interprets the value of the direction attribute into activity of the save action. * @param direction value of the @direction attribute * @return true if direction is either set to "both" or "save" */ private static boolean isSaveEnabled(String direction) { return "both".equals(direction) || "save".equals(direction); } /** * Interprets the value of the lenient attribute into a Boolean object * allowing three-state logic (true/false/unset) * @param leniency value of the @lenient attribute * @return null if the leniency parameter is null or a String otherwise the allowed values */ private static Boolean decideLeniency(String leniency) { return BooleanUtils.toBooleanObject(leniency); } } }