/* * 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.Iterator; import java.util.LinkedList; import java.util.Locale; import org.apache.avalon.framework.logger.Logger; import org.apache.cocoon.forms.datatype.convertor.ConversionResult; import org.apache.cocoon.forms.datatype.convertor.Convertor; import org.apache.cocoon.forms.formmodel.Widget; import org.apache.commons.jxpath.JXPathContext; import org.apache.commons.jxpath.Pointer; /** * Simple binding for multi fields: on save, first deletes the target data * before recreating it from scratch. * * @version $Id$ */ public class MultiValueJXPathBinding extends JXPathBindingBase { private final String multiValueId; private final String multiValuePath; private final String rowPath; private final JXPathBindingBase updateBinding; private final Convertor convertor; private final Locale convertorLocale; public MultiValueJXPathBinding(JXPathBindingBuilderBase.CommonAttributes commonAtts, String multiValueId, String multiValuePath, String rowPath, JXPathBindingBase[] updateBindings, Convertor convertor, Locale convertorLocale) { super(commonAtts); this.multiValueId = multiValueId; this.multiValuePath = multiValuePath; this.rowPath = rowPath; this.updateBinding = new ComposedJXPathBindingBase(JXPathBindingBuilderBase.CommonAttributes.DEFAULT, updateBindings); this.convertor = convertor; this.convertorLocale = convertorLocale; } public void enableLogging(Logger logger) { super.enableLogging(logger); this.updateBinding.enableLogging(logger); } public String getId() { return multiValueId; } public String getMultiValuePath() { return multiValuePath; } public String getRowPath() { return rowPath; } public ComposedJXPathBindingBase getUpdateBinding() { return (ComposedJXPathBindingBase)updateBinding; } public Convertor getConvertor() { return convertor; } public Locale getLocale() { return convertorLocale; } public void doLoad(Widget frmModel, JXPathContext jctx) throws BindingException { Widget widget = selectWidget(frmModel, this.multiValueId); if (widget == null) { throw new BindingException("The widget with the ID [" + this.multiValueId + "] referenced in the binding does not exist in the form definition."); } // Move to multi value context Pointer ptr = jctx.getPointer(this.multiValuePath); if (ptr.getNode() != null) { // There are some nodes to load from JXPathContext multiValueContext = jctx.getRelativeContext(ptr); // build a jxpath iterator for pointers Iterator rowPointers = multiValueContext.iterate(this.rowPath); LinkedList list = new LinkedList(); while (rowPointers.hasNext()) { Object value = rowPointers.next(); if (value != null && convertor != null) { if (value instanceof String) { ConversionResult conversionResult = convertor.convertFromString((String)value, convertorLocale, null); if (conversionResult.isSuccessful()) value = conversionResult.getResult(); else value = null; } else { getLogger().warn("Convertor ignored on backend-value which isn't of type String."); } } list.add(value); } widget.setValue(list.toArray()); } if (getLogger().isDebugEnabled()) { getLogger().debug("done loading values " + this); } } public void doSave(Widget frmModel, JXPathContext jctx) throws BindingException { Widget widget = selectWidget(frmModel,this.multiValueId); Object[] values = (Object[])widget.getValue(); JXPathContext multiValueContext = jctx.getRelativeContext(jctx.createPath(this.multiValuePath)); // Delete all that is already present // Unfortunately the following statement doesn't work (it doesn't removes all elements from the // list because of a bug in JXPath I wasn't able to locate). //multiValueContext.removeAll(this.rowPath); // TODO: This is a workaround until the bug in commons-jxpath is found, fixed and released Iterator rowPointers = multiValueContext.iteratePointers(this.rowPath); int cnt = 0; while( rowPointers.hasNext() ) { cnt++; rowPointers.next(); } while( cnt >= 1 ) { String thePath = this.rowPath + "[" + cnt + "]"; multiValueContext.removePath(thePath); cnt--; } boolean update = false; if (values != null) { // first update the values for (int i = 0; i < values.length; i++) { String path = this.rowPath + '[' + (i+1) + ']'; Pointer rowPtr = multiValueContext.createPath(path); Object value = values[i]; if (value != null && convertor != null) { value = convertor.convertToString(value, convertorLocale, null); } rowPtr.setValue(value); } // now perform any other bindings that need to be performed when the value is updated this.updateBinding.saveFormToModel(frmModel, multiValueContext); update = true; } if (getLogger().isDebugEnabled()) { getLogger().debug("done saving " + this + " -- on-update == " + update); } } public String toString() { return "MultiValueJXPathBinding [widget=" + this.multiValueId + ", xpath=" + this.multiValuePath + "]"; } }