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.Collection;
020: import java.util.Iterator;
021: import java.util.Map;
022:
023: import org.apache.avalon.framework.logger.AbstractLogEnabled;
024:
025: import org.apache.cocoon.forms.binding.library.Library;
026: import org.apache.cocoon.forms.formmodel.Widget;
027: import org.apache.cocoon.util.jxpath.DOMFactory;
028:
029: import org.apache.commons.jxpath.AbstractFactory;
030: import org.apache.commons.jxpath.JXPathContext;
031: import org.apache.commons.jxpath.Pointer;
032: import org.apache.commons.jxpath.ri.model.beans.BeanPropertyPointer;
033: import org.apache.commons.jxpath.util.TypeUtils;
034: import org.apache.commons.lang.exception.NestableRuntimeException;
035:
036: /**
037: * Provides a base class for hooking up Binding implementations that use the
038: * Jakarta Commons <a href="http://jakarta.apache.org/commons/jxpath/index.html">
039: * JXPath package</a>.
040: *
041: * @version $Id: JXPathBindingBase.java 506442 2007-02-12 13:52:53Z cziegeler $
042: */
043: public abstract class JXPathBindingBase extends AbstractLogEnabled
044: implements Binding {
045:
046: /**
047: * the local library, if this is the top binding
048: */
049: private Library enclosingLibrary;
050:
051: /**
052: * Object holding the values of the common objects on all Bindings.
053: */
054: private final JXPathBindingBuilderBase.CommonAttributes commonAtts;
055:
056: /**
057: * Parent binding of this binding.
058: */
059: protected Binding parent;
060:
061: /**
062: * Cache of class definitions
063: */
064: protected Map classes;
065:
066: protected JXPathBindingBase(
067: JXPathBindingBuilderBase.CommonAttributes commonAtts) {
068: this .commonAtts = commonAtts;
069: }
070:
071: /**
072: * @see org.apache.cocoon.forms.binding.Binding#getEnclosingLibrary()
073: */
074: public Library getEnclosingLibrary() {
075: if (parent != null) {
076: return parent.getEnclosingLibrary();
077: } else {
078: return enclosingLibrary;
079: }
080: }
081:
082: /**
083: * @see org.apache.cocoon.forms.binding.Binding#setEnclosingLibrary(org.apache.cocoon.forms.binding.library.Library)
084: */
085: public void setEnclosingLibrary(Library lib) {
086: this .enclosingLibrary = lib;
087: }
088:
089: /**
090: * @see org.apache.cocoon.forms.binding.Binding#isValid()
091: */
092: public boolean isValid() {
093: if (this .enclosingLibrary == null) {
094: if (parent != null) {
095: return parent.isValid();
096: }
097: return true; // no library used
098: }
099:
100: try {
101: return !this .enclosingLibrary.dependenciesHaveChanged();
102: } catch (Exception e) {
103: getLogger().error("Error checking dependencies!", e);
104: throw new NestableRuntimeException(
105: "Error checking dependencies!", e);
106: }
107: }
108:
109: public JXPathBindingBuilderBase.CommonAttributes getCommonAtts() {
110: return this .commonAtts;
111: }
112:
113: /**
114: * Gets source location of this binding.
115: */
116: public String getLocation() {
117: return this .commonAtts.location;
118: }
119:
120: /**
121: * Sets parent binding.
122: */
123: public void setParent(Binding binding) {
124: this .parent = binding;
125: }
126:
127: /**
128: * Returns binding definition id.
129: */
130: public String getId() {
131: return null;
132: }
133:
134: /**
135: * @see org.apache.cocoon.forms.binding.Binding#getClass(java.lang.String)
136: */
137: public Binding getClass(String id) {
138:
139: Binding classBinding = null;
140: try {
141: if (this .enclosingLibrary != null
142: && (classBinding = this .enclosingLibrary
143: .getBinding(id)) != null) {
144: return classBinding;
145: }
146: } catch (Exception e) { /* ignored */
147: }
148:
149: if (classes != null) {
150: // Query cache for class
151: classBinding = (Binding) classes.get(id);
152: }
153:
154: if (classBinding == null) {
155: // Query parent for class
156: if (parent != null) {
157: classBinding = parent.getClass(id);
158: // dont cache, doesn't matter and makes things complicated with libraries
159: // **************************************
160: // Cache result
161: /*if (classes == null) {
162: classes = new HashMap();
163: }
164: classes.put(id, classBinding);*/
165: // **************************************
166: } else {
167: throw new RuntimeException("Class \"" + id
168: + "\" not found (" + getLocation() + ")");
169: }
170: }
171:
172: return classBinding;
173: }
174:
175: /**
176: * Helper method that selects a child-widget with a given id from a parent.
177: *
178: * @param parent containing the child-widget to return.
179: * @param id of the childWidget to find, if this is <code>null</code> then the parent is returned.
180: * @return the selected widget
181: *
182: * @throws RuntimeException if the id is not null and points to a
183: * child-widget that cannot be found.
184: */
185: protected Widget selectWidget(Widget parent, String id) {
186: if (id == null) {
187: return parent;
188: }
189:
190: Widget childWidget = parent.lookupWidget(id);
191: if (childWidget == null) {
192: String containerId = parent.getRequestParameterName();
193: if (containerId == null || "".equals(containerId)) {
194: containerId = "top-level form-widget";
195: } else {
196: containerId = "container \"" + containerId + "\"";
197: }
198: throw new RuntimeException(getClass().getName() + " ("
199: + getLocation() + "): Widget \"" + id
200: + "\" does not exist in the " + containerId + " ("
201: + parent.getLocation() + ").");
202: }
203:
204: return childWidget;
205: }
206:
207: /**
208: * Performs the actual load binding regardless of the configured value of the "direction" attribute.
209: * Abstract method that subclasses need to implement for specific activity.
210: */
211: public abstract void doLoad(Widget frmModel, JXPathContext jxpc)
212: throws BindingException;
213:
214: /**
215: * Redefines the Binding action as working on a JXPathContext Type rather
216: * then on generic objects.
217: * Executes the actual loading via {@link #doLoad(Widget, JXPathContext)}
218: * depending on the configured value of the "direction" attribute.
219: */
220: public final void loadFormFromModel(Widget frmModel,
221: JXPathContext jxpc) throws BindingException {
222: boolean inheritedLeniency = jxpc.isLenient();
223: applyLeniency(jxpc);
224: applyNSDeclarations(jxpc);
225: if (this .commonAtts.loadEnabled) {
226: doLoad(frmModel, jxpc);
227: }
228: jxpc.setLenient(inheritedLeniency);
229: }
230:
231: /**
232: * Hooks up with the more generic Binding of any objectModel by wrapping
233: * it up in a JXPathContext object and then transfering control over to
234: * the new overloaded version of this method.
235: *
236: * @see org.apache.cocoon.forms.binding.Binding#loadFormFromModel(org.apache.cocoon.forms.formmodel.Widget, java.lang.Object)
237: */
238: public final void loadFormFromModel(Widget frmModel, Object objModel)
239: throws BindingException {
240: if (objModel != null) {
241: JXPathContext jxpc = makeJXPathContext(objModel);
242: loadFormFromModel(frmModel, jxpc);
243: } else {
244: throw new NullPointerException(
245: "null object passed to loadFormFromModel() method");
246: }
247: }
248:
249: /**
250: * Performs the actual save binding regardless of the configured value of the "direction" attribute.
251: * Abstract method that subclasses need to implement for specific activity.
252: */
253: public abstract void doSave(Widget frmModel, JXPathContext jxpc)
254: throws BindingException;
255:
256: /**
257: * Redefines the Binding action as working on a JXPathContext Type rather
258: * then on generic objects.
259: * Executes the actual saving via {@link #doSave(Widget, JXPathContext)}
260: * depending on the configured value of the "direction" attribute.
261: */
262: public final void saveFormToModel(Widget frmModel,
263: JXPathContext jxpc) throws BindingException {
264: boolean inheritedLeniency = jxpc.isLenient();
265: applyLeniency(jxpc);
266: applyNSDeclarations(jxpc);
267: if (this .commonAtts.saveEnabled) {
268: doSave(frmModel, jxpc);
269: }
270: jxpc.setLenient(inheritedLeniency);
271: }
272:
273: /**
274: * Hooks up with the more generic Binding of any objectModel by wrapping
275: * it up in a JXPathContext object and then transfering control over to
276: * the new overloaded version of this method.
277: *
278: * @see org.apache.cocoon.forms.binding.Binding#saveFormToModel(org.apache.cocoon.forms.formmodel.Widget, java.lang.Object)
279: */
280: public void saveFormToModel(Widget frmModel, Object objModel)
281: throws BindingException {
282: if (objModel != null) {
283: JXPathContext jxpc = makeJXPathContext(objModel);
284: saveFormToModel(frmModel, jxpc);
285: } else {
286: throw new NullPointerException(
287: "null object passed to saveFormToModel() method");
288: }
289: }
290:
291: private void applyLeniency(JXPathContext jxpc) {
292: if (this .commonAtts.leniency != null) {
293: jxpc.setLenient(this .commonAtts.leniency.booleanValue());
294: }
295: }
296:
297: private void applyNSDeclarations(JXPathContext jxpc) {
298: if (this .commonAtts.nsDeclarations != null) {
299: Iterator keysIter = this .commonAtts.nsDeclarations.keySet()
300: .iterator();
301: while (keysIter.hasNext()) {
302: String nsuri = (String) keysIter.next();
303: String pfx = (String) this .commonAtts.nsDeclarations
304: .get(nsuri);
305: jxpc.registerNamespace(pfx, nsuri);
306: }
307: }
308: }
309:
310: private JXPathContext makeJXPathContext(Object objModel) {
311: JXPathContext jxpc;
312: if (!(objModel instanceof JXPathContext)) {
313: jxpc = JXPathContext.newContext(objModel);
314: jxpc.setLenient(true);
315:
316: AbstractFactory jxPathFactory;
317: if (commonAtts.jxPathFactory != null)
318: jxPathFactory = commonAtts.jxPathFactory;
319: else
320: jxPathFactory = new BindingJXPathFactory();
321: jxpc.setFactory(jxPathFactory);
322: } else {
323: jxpc = (JXPathContext) objModel;
324: }
325: return jxpc;
326: }
327:
328: /**
329: * JXPath factory that combines the DOMFactory and support for collections.
330: */
331: private static class BindingJXPathFactory extends DOMFactory {
332:
333: public boolean createObject(JXPathContext context,
334: Pointer pointer, Object parent, String name, int index) {
335: if (createCollectionItem(context, pointer, parent, name,
336: index)) {
337: return true;
338: // AG: If this is a bean, then the object is supposed to exists.
339: } else if (pointer instanceof BeanPropertyPointer) {
340: return createBeanField(context, pointer, parent, name,
341: index);
342: } else {
343: return super .createObject(context, pointer, parent,
344: name, index);
345: }
346: }
347:
348: private boolean createCollectionItem(JXPathContext context,
349: Pointer pointer, Object parent, String name, int index) {
350: // FIXME: don't clearly understand how this works.
351: // see http://marc.theaimsgroup.com/?l=xml-cocoon-dev&m=111148567029114&w=2
352: final Object o = context.getValue(name);
353: if (o == null) {
354: return false;
355: }
356: if (o instanceof Collection) {
357: ((Collection) o).add(null);
358: } else if (o.getClass().isArray()) {
359: // not yet supported
360: return false;
361: } else {
362: return false;
363: }
364: return true;
365: }
366:
367: // AG: Create the Object for the field as defined in the Bean.
368: // The value we will set here is not important. JXPath knows that it is UNITIALIZED.
369: // if we set it Pointer Value to null then the code will throw an exception.
370: //
371: // In short, there is no harm. The value will never show up.
372: // TODO: Manage other forms' types as Date, Bean and others not covered by this method.
373: private boolean createBeanField(JXPathContext context,
374: Pointer pointer, Object parent, String name, int index) {
375: try {
376: Class clazz = parent.getClass().getDeclaredField(name)
377: .getType();
378: Object o = context.getValue(name);
379: if (o == null) {
380: final Class[] parametersTypes = { String.class };
381: final Object[] initArgs = { "0" };
382: try {
383: // AG: Here we service Booleans, Strings and Number() + his Direct know subclasses:
384: // (BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, Short)
385: // as well as other classes that use an String as Constructor parameter.
386: o = clazz.getConstructor(parametersTypes)
387: .newInstance(initArgs);
388: } catch (Exception e) {
389: // AG: The class has not a constructor using a String as a parameter.
390: // ie: Boolean(String), Integer(String), etc.
391: // Lets try with a constructor with no parameters. ie: Number().
392: o = clazz.newInstance();
393: }
394: } else if (TypeUtils.canConvert(o, clazz)) {
395: o = TypeUtils.convert(o, clazz);
396: }
397: if (o != null) {
398: pointer.setValue(o);
399: return true; // OK. We have an initial Object of the right Class initialized.
400: }
401: } catch (Exception e) {
402: // TODO: Output info in logs.
403: }
404: return false;
405: }
406: }
407: }
|