001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.forms.binding;
018:
019: import java.util.Map;
020:
021: import org.apache.avalon.framework.logger.LogEnabled;
022: import org.apache.avalon.framework.logger.Logger;
023:
024: import org.apache.cocoon.forms.util.DomHelper;
025:
026: import org.apache.commons.jxpath.AbstractFactory;
027: import org.apache.commons.lang.BooleanUtils;
028: import org.w3c.dom.Element;
029:
030: /**
031: * Abstract base class enabling logging and supporting the interpretation of
032: * common configuration settings on all specific implementations of
033: * {@link org.apache.cocoon.forms.binding.JXPathBindingBase}.
034: *
035: * Common supported configurations: {@link #getCommonAttributes(Element)}
036: * <ul>
037: * <li>Attribute direction="load|save|both": defaults to 'both'</li>
038: * <li>Attribute lenient="true|false|[undefined]": defaults to [undefined]
039: * which means: "lenient mode inherited from parent" </li>
040: * </ul>
041: *
042: * @version $Id: JXPathBindingBuilderBase.java 517733 2007-03-13 15:37:22Z vgritsenko $
043: */
044: public abstract class JXPathBindingBuilderBase implements LogEnabled {
045:
046: private Logger logger;
047:
048: /**
049: * Receives the Avalon logger to use.
050: */
051: public void enableLogging(Logger logger) {
052: this .logger = logger;
053: if (logger.isDebugEnabled()) {
054: logger.debug("JXPathBindingBuilderBase got logger...");
055: }
056: }
057:
058: /**
059: * Makes the logger available to the subclasses.
060: * @return Logger
061: */
062: protected Logger getLogger() {
063: return this .logger;
064: }
065:
066: /**
067: * Builds a configured binding object based on the configuration as
068: * described in the bindingElement. The BuilderMap can be used to
069: * find appropriate builders for possible subBinders.
070: *
071: * @param bindingElm
072: * @param assistant
073: * @return JXPathBindingBase
074: */
075: public abstract JXPathBindingBase buildBinding(Element bindingElm,
076: JXPathBindingManager.Assistant assistant)
077: throws BindingException;
078:
079: /**
080: * Helper method for interpreting the common attributes which are supported
081: * on each of the Bindings. These are
082: * <br>
083: * <code>@direction</code> can hold one of the following values:
084: * <ol><li><code>'load'</code>: This binding will only load.</li>
085: * <li><code>'save'</code>: This binding will only save.</li>
086: * <li><code>'both'</code>: This binding will perform both operations.</li>
087: * </ol>
088: * <br>
089: * <code>@lenient</code> can either be:
090: * <ol><li><code>'true'</code>: This binding will set the jxpath context to
091: * be lenient towards the usage of inexisting paths on the back-end model.</li>
092: * <li><code>'false'</code>: This binding will set the jxpath context to be
093: * strict and throwing exceptions for the usage of inexisting paths on the
094: * back-end model.</li>
095: * <li><code>(unset)</code>: This binding will not change the leniency behaviour
096: * on the jxpath this binding receives from his parent binding.</li>
097: * </ol>
098: * @param bindingElm
099: * @return an instance of CommonAttributes
100: * @throws BindingException
101: */
102: protected static CommonAttributes getCommonAttributes(
103: Element bindingElm) throws BindingException {
104: try {
105: String location = DomHelper.getLocation(bindingElm);
106: //TODO: should we eventually remove this?
107: //throw an error if people are still using the old-style @read-only or @readonly
108: if (DomHelper.getAttributeAsBoolean(bindingElm, "readonly",
109: false)) {
110: throw new BindingException(
111: "Error in the binding."
112: + "\nThe usage of the attribute @readonly has been deprecated in favour of @direction.",
113: DomHelper.getLocationObject(bindingElm));
114: }
115: if (DomHelper.getAttributeAsBoolean(bindingElm,
116: "read-only", false)) {
117: throw new BindingException(
118: "Error in the binding."
119: + "\nThe usage of the attribute @read-only has been deprecated in favour of @direction.",
120: DomHelper.getLocationObject(bindingElm));
121: }
122:
123: String direction = DomHelper.getAttribute(bindingElm,
124: "direction", "both");
125:
126: String leniency = DomHelper.getAttribute(bindingElm,
127: "lenient", null);
128:
129: //TODO: current jxpath is not inheriting registered namespaces over to
130: // child-relative jxpath contexts --> because of that we can't just
131: // remember the getLocalNSDeclarations but need the full set from
132: // getInheritedNSDeclarations
133: // IMPORTANT NOTE: if jxpath would change this behaviour we would however
134: // still need to be able to unregister namespace-declarations
135: // (in a smart way: unhide what is possably available from your parent.
136: // So both changes to jxpath need to be available before changing the below.
137: Map nsDeclarationMap = DomHelper
138: .getInheritedNSDeclarations(bindingElm);
139: // we (actually jxpath) doesn't support un-prefixed namespace-declarations:
140: // so we decide to break on those above silently ignoring them
141: if (nsDeclarationMap != null
142: && nsDeclarationMap.values().contains(null)) {
143: throw new BindingException(
144: "Error in the binding."
145: + "\nBinding doesn't support having namespace-declarations without explicit prefixes.",
146: DomHelper.getLocationObject(bindingElm));
147: }
148:
149: String jxPathFactoryName = bindingElm
150: .getAttribute("jxpath-factory");
151: AbstractFactory jxPathFactory = null;
152: if (jxPathFactoryName != null
153: && jxPathFactoryName.trim().length() > 0) {
154: try {
155: Class jxPathFactoryClass = JXPathBindingBuilderBase.class
156: .getClassLoader().loadClass(
157: jxPathFactoryName);
158: jxPathFactory = (AbstractFactory) jxPathFactoryClass
159: .newInstance();
160: } catch (Exception e) {
161: throw new BindingException(
162: "Error with specified jxpath factory "
163: + jxPathFactoryName, e, DomHelper
164: .getLocationObject(bindingElm));
165: }
166: }
167:
168: return new CommonAttributes(location, direction, leniency,
169: nsDeclarationMap, jxPathFactory);
170: } catch (BindingException e) {
171: throw e;
172: } catch (Exception e) {
173: throw new BindingException("Error building binding", e,
174: DomHelper.getLocationObject(bindingElm));
175: }
176: }
177:
178: public static CommonAttributes mergeCommonAttributes(
179: CommonAttributes existing, CommonAttributes extra) {
180: if (extra == null) {
181: return existing;
182: }
183:
184: Boolean leniency;
185: if (existing.leniency == null) {
186: leniency = extra.leniency;
187: } else {
188: leniency = existing.leniency;
189: }
190:
191: String strLeniency = null;
192: if (leniency != null) {
193: strLeniency = leniency.toString();
194: }
195:
196: String direction = existing.direction;
197: if (extra.direction != null) {
198: // was defined
199: direction = extra.direction;
200: }
201:
202: AbstractFactory jxPathFactory = existing.jxPathFactory;
203: if (extra.jxPathFactory != null) {
204: jxPathFactory = extra.jxPathFactory;
205: }
206:
207: return new CommonAttributes(extra.location, direction,
208: strLeniency, extra.nsDeclarations, jxPathFactory);
209: }
210:
211: /**
212: * CommonAttributes is a simple helper class for holding the distinct data
213: * member fields indicating the activity of the separate load and save
214: * actions of a given binding.
215: */
216: public static class CommonAttributes {
217:
218: /**
219: * store direction (load/save enabledness) too for easier merging
220: */
221: String direction;
222: /**
223: * Source location of this binding.
224: */
225: final String location;
226: /**
227: * Flag which controls whether a binding is active during loading.
228: */
229: final boolean loadEnabled;
230: /**
231: * Flag which controls whether a binding is active during saving.
232: */
233: final boolean saveEnabled;
234: /**
235: * Flag which controls whether the jxpath context used by this binding
236: * should be operating in lenient mode or not
237: */
238: final Boolean leniency;
239: /**
240: * Array of namespace-declarations (prefix-uri pairs) that need to be set on the jxpath
241: */
242: final Map nsDeclarations;
243: /**
244: * The factory to be set on the JXPath Context object
245: */
246: final AbstractFactory jxPathFactory;
247:
248: final static CommonAttributes DEFAULT = new CommonAttributes(
249: "location unknown", true, true, null, null, null);
250:
251: CommonAttributes(String location, String direction,
252: String leniency, Map nsDeclarations,
253: AbstractFactory jxPathFactory) {
254: this (location, isLoadEnabled(direction),
255: isSaveEnabled(direction), decideLeniency(leniency),
256: nsDeclarations, jxPathFactory);
257: this .direction = direction;
258: }
259:
260: CommonAttributes(String location, boolean loadEnabled,
261: boolean saveEnabled, Boolean leniency,
262: Map nsDeclarations, AbstractFactory jxPathFactory) {
263: this .direction = null;
264: this .location = location;
265: this .loadEnabled = loadEnabled;
266: this .saveEnabled = saveEnabled;
267: this .leniency = leniency;
268: this .nsDeclarations = nsDeclarations;
269: this .jxPathFactory = jxPathFactory;
270: }
271:
272: /**
273: * Interprets the value of the direction attribute into activity of the load action.
274: * @param direction
275: * @return true if direction is either set to "both" or "load"
276: */
277: private static boolean isLoadEnabled(String direction) {
278: return "both".equals(direction) || "load".equals(direction);
279: }
280:
281: /**
282: * Interprets the value of the direction attribute into activity of the save action.
283: * @param direction value of the @direction attribute
284: * @return true if direction is either set to "both" or "save"
285: */
286: private static boolean isSaveEnabled(String direction) {
287: return "both".equals(direction) || "save".equals(direction);
288: }
289:
290: /**
291: * Interprets the value of the lenient attribute into a Boolean object
292: * allowing three-state logic (true/false/unset)
293: * @param leniency value of the @lenient attribute
294: * @return null if the leniency parameter is null or a String otherwise the allowed values
295: */
296: private static Boolean decideLeniency(String leniency) {
297: return BooleanUtils.toBooleanObject(leniency);
298: }
299:
300: }
301: }
|