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.ArrayList;
020: import java.util.Stack;
021:
022: import org.apache.avalon.framework.activity.Disposable;
023: import org.apache.avalon.framework.activity.Initializable;
024: import org.apache.avalon.framework.configuration.Configurable;
025: import org.apache.avalon.framework.configuration.Configuration;
026: import org.apache.avalon.framework.configuration.ConfigurationException;
027: import org.apache.avalon.framework.context.Context;
028: import org.apache.avalon.framework.context.ContextException;
029: import org.apache.avalon.framework.context.Contextualizable;
030: import org.apache.avalon.framework.logger.AbstractLogEnabled;
031: import org.apache.avalon.framework.logger.Logger;
032: import org.apache.avalon.framework.service.ServiceException;
033: import org.apache.avalon.framework.service.ServiceManager;
034: import org.apache.avalon.framework.service.Serviceable;
035: import org.apache.avalon.framework.thread.ThreadSafe;
036: import org.apache.excalibur.source.Source;
037: import org.apache.excalibur.source.SourceResolver;
038:
039: import org.apache.cocoon.components.LifecycleHelper;
040: import org.apache.cocoon.forms.CacheManager;
041: import org.apache.cocoon.forms.binding.library.Library;
042: import org.apache.cocoon.forms.binding.library.LibraryException;
043: import org.apache.cocoon.forms.binding.library.LibraryManager;
044: import org.apache.cocoon.forms.binding.library.LibraryManagerImpl;
045: import org.apache.cocoon.forms.datatype.DatatypeManager;
046: import org.apache.cocoon.forms.util.DomHelper;
047: import org.apache.cocoon.forms.util.SimpleServiceSelector;
048: import org.apache.cocoon.util.location.LocationAttributes;
049:
050: import org.apache.commons.lang.exception.NestableRuntimeException;
051: import org.w3c.dom.Document;
052: import org.w3c.dom.Element;
053: import org.xml.sax.InputSource;
054:
055: /**
056: * JXPathBindingManager provides an implementation of {@link BindingManager}by
057: * usage of the <a href="http://jakarta.apache.org/commons/jxpath/index.html">
058: * JXPath package </a>.
059: *
060: * @version $Id: JXPathBindingManager.java 517733 2007-03-13 15:37:22Z vgritsenko $
061: */
062: public class JXPathBindingManager extends AbstractLogEnabled implements
063: BindingManager, Contextualizable, Serviceable, Configurable,
064: Initializable, Disposable, ThreadSafe {
065:
066: private static final String PREFIX = "CocoonFormBinding:";
067:
068: protected ServiceManager manager;
069:
070: protected DatatypeManager datatypeManager;
071:
072: private Configuration configuration;
073:
074: protected SimpleServiceSelector bindingBuilderSelector;
075:
076: private CacheManager cacheManager;
077:
078: private Context avalonContext;
079:
080: protected LibraryManagerImpl libraryManager;
081:
082: /**
083: * Java 1.3 logger access method.
084: * <br>
085: * Access to {#getLogger} from inner class on Java 1.3 causes NoSuchMethod error.
086: */
087: protected Logger getMyLogger() {
088: return getLogger();
089: }
090:
091: public void contextualize(Context context) throws ContextException {
092: this .avalonContext = context;
093: }
094:
095: public void service(ServiceManager manager) throws ServiceException {
096: this .manager = manager;
097: this .datatypeManager = (DatatypeManager) manager
098: .lookup(DatatypeManager.ROLE);
099: this .cacheManager = (CacheManager) manager
100: .lookup(CacheManager.ROLE);
101: }
102:
103: public void configure(Configuration configuration)
104: throws ConfigurationException {
105: this .configuration = configuration;
106: }
107:
108: public void initialize() throws Exception {
109: bindingBuilderSelector = new SimpleServiceSelector("binding",
110: JXPathBindingBuilderBase.class);
111: LifecycleHelper.setupComponent(bindingBuilderSelector,
112: getLogger(), this .avalonContext, this .manager,
113: configuration.getChild("bindings"));
114:
115: libraryManager = new LibraryManagerImpl();
116: libraryManager.setBindingManager(this );
117: LifecycleHelper.setupComponent(libraryManager, getLogger(),
118: this .avalonContext, this .manager, configuration
119: .getChild("library"));
120: }
121:
122: public Binding createBinding(Source source) throws BindingException {
123:
124: Binding binding = (Binding) this .cacheManager.get(source,
125: PREFIX);
126: if (binding != null && !binding.isValid()) {
127: binding = null; //invalidate
128: }
129:
130: if (binding == null) {
131: try {
132: // Retrieve the input source of the binding file
133: InputSource is = new InputSource(source
134: .getInputStream());
135: is.setSystemId(source.getURI());
136:
137: Document doc = DomHelper.parse(is, this .manager);
138: binding = createBinding(doc.getDocumentElement());
139: this .cacheManager.set(binding, source, PREFIX);
140: } catch (BindingException e) {
141: throw e;
142: } catch (Exception e) {
143: throw new BindingException(
144: "Error creating binding from "
145: + source.getURI(), e);
146: }
147: }
148:
149: return binding;
150: }
151:
152: public Binding createBinding(String bindingURI)
153: throws BindingException {
154: SourceResolver sourceResolver = null;
155: Source source = null;
156:
157: try {
158: try {
159: sourceResolver = (SourceResolver) manager
160: .lookup(SourceResolver.ROLE);
161: source = sourceResolver.resolveURI(bindingURI);
162: } catch (Exception e) {
163: throw new BindingException(
164: "Error resolving binding source: " + bindingURI);
165: }
166: return createBinding(source);
167: } finally {
168: if (source != null) {
169: sourceResolver.release(source);
170: }
171: if (sourceResolver != null) {
172: manager.release(sourceResolver);
173: }
174: }
175: }
176:
177: public Binding createBinding(Element bindingElement)
178: throws BindingException {
179: Binding binding = null;
180: if (BindingManager.NAMESPACE.equals(bindingElement
181: .getNamespaceURI())) {
182: binding = getBuilderAssistant()
183: .getBindingForConfigurationElement(bindingElement);
184: ((JXPathBindingBase) binding).enableLogging(getLogger());
185: if (getLogger().isDebugEnabled()) {
186: getLogger().debug(
187: "Creation of new binding finished. " + binding);
188: }
189: } else {
190: if (getLogger().isDebugEnabled()) {
191: getLogger()
192: .debug(
193: "Root Element of said binding file is in wrong namespace.");
194: }
195: }
196: return binding;
197: }
198:
199: public Assistant getBuilderAssistant() {
200: return new Assistant();
201: }
202:
203: public void dispose() {
204: if (this .bindingBuilderSelector != null) {
205: this .bindingBuilderSelector.dispose();
206: this .bindingBuilderSelector = null;
207: }
208: this .manager.release(this .datatypeManager);
209: this .datatypeManager = null;
210: this .manager.release(this .cacheManager);
211: this .cacheManager = null;
212: this .manager = null;
213: }
214:
215: /**
216: * Assistant Inner class discloses enough features to the created
217: * childBindings to recursively
218: *
219: * This patterns was chosen to prevent Inversion Of Control between this
220: * factory and its builder classes (that could be provided by third
221: * parties.)
222: */
223: public class Assistant {
224:
225: private BindingBuilderContext context = new BindingBuilderContext();
226: private Stack contextStack = new Stack();
227:
228: private JXPathBindingBuilderBase getBindingBuilder(
229: String bindingType) throws BindingException {
230: try {
231: return (JXPathBindingBuilderBase) bindingBuilderSelector
232: .select(bindingType);
233: } catch (ServiceException e) {
234: throw new BindingException(
235: "Cannot handle binding element '" + bindingType
236: + "'.", e);
237: }
238: }
239:
240: /**
241: * Creates a {@link Binding} following the specification in the
242: * provided config element.
243: */
244: public JXPathBindingBase getBindingForConfigurationElement(
245: Element configElm) throws BindingException {
246: String bindingType = configElm.getLocalName();
247: JXPathBindingBuilderBase bindingBuilder = getBindingBuilder(bindingType);
248:
249: boolean flag = false;
250: if (context.getLocalLibrary() == null) {
251: // FIXME Use newLibrary()?
252: Library lib = new Library(libraryManager,
253: getBuilderAssistant());
254: lib.enableLogging(getMyLogger());
255: context.setLocalLibrary(lib);
256: lib.setSourceURI(LocationAttributes.getURI(configElm));
257: flag = true;
258: }
259:
260: if (context.getLocalLibrary() != null
261: && configElm.hasAttribute("extends")) {
262: try {
263: context.setSuperBinding(context.getLocalLibrary()
264: .getBinding(
265: configElm.getAttribute("extends")));
266: } catch (LibraryException e) {
267: // throw new RuntimeException("Error extending binding! (at "+DomHelper.getLocation(configElm)+")", e);
268: throw new NestableRuntimeException(
269: "Error extending binding! (at "
270: + DomHelper.getLocation(configElm)
271: + ")", e);
272: }
273: } else {
274: context.setSuperBinding(null);
275: }
276:
277: JXPathBindingBase childBinding = bindingBuilder
278: .buildBinding(configElm, this );
279: if (flag && childBinding != null) {
280: childBinding.setEnclosingLibrary(context
281: .getLocalLibrary());
282: }
283:
284: // this might get called unnecessarily, but solves issues with the libraries
285: if (childBinding != null) {
286: childBinding.enableLogging(getMyLogger());
287: }
288: return childBinding;
289: }
290:
291: private JXPathBindingBase[] mergeBindings(
292: JXPathBindingBase[] existing, JXPathBindingBase[] extra) {
293:
294: if (existing == null || existing.length == 0) {
295: return extra;
296: }
297: if (extra == null || extra.length == 0) {
298: return existing;
299: }
300: // have to do it the stupid painter way..
301: ArrayList list = new ArrayList(existing.length);
302: for (int i = 0; i < existing.length; i++) {
303: list.add(existing[i]);
304: }
305: for (int i = 0; i < extra.length; i++) {
306: if (extra[i].getId() == null) {
307: list.add(extra[i]);
308: } else {
309: // try to replace existing one
310: boolean match = false;
311: for (int j = 0; j < list.size(); j++) {
312: if (extra[i].getId().equals(
313: ((JXPathBindingBase) list.get(j))
314: .getId())) {
315: list.set(j, extra[i]);
316: match = true;
317: break; // stop searching
318: }
319: }
320: // if no match, just add
321: if (!match) {
322: list.add(extra[i]);
323: }
324: }
325: }
326: return (JXPathBindingBase[]) list
327: .toArray(new JXPathBindingBase[list.size()]);
328: }
329:
330: /**
331: * proxy for compatibility
332: *
333: */
334: public JXPathBindingBase[] makeChildBindings(
335: Element parentElement) throws BindingException {
336: return makeChildBindings(parentElement,
337: new JXPathBindingBase[0]);
338: }
339:
340: /**
341: * Makes an array of childBindings for the child-elements of the
342: * provided configuration element.
343: */
344: public JXPathBindingBase[] makeChildBindings(
345: Element parentElement,
346: JXPathBindingBase[] existingBindings)
347: throws BindingException {
348: if (existingBindings == null) {
349: existingBindings = new JXPathBindingBase[0];
350: }
351:
352: if (parentElement != null) {
353: Element[] childElements = DomHelper.getChildElements(
354: parentElement, BindingManager.NAMESPACE);
355: if (childElements.length > 0) {
356: JXPathBindingBase[] childBindings = new JXPathBindingBase[childElements.length];
357: for (int i = 0; i < childElements.length; i++) {
358: pushContext();
359: context.setSuperBinding(null);
360:
361: String id = DomHelper.getAttribute(
362: childElements[i], "id", null);
363: String path = DomHelper.getAttribute(
364: childElements[i], "path", null);
365: if (context.getLocalLibrary() != null
366: && childElements[i]
367: .getAttribute("extends") != null) {
368: try {
369: context
370: .setSuperBinding(context
371: .getLocalLibrary()
372: .getBinding(
373: childElements[i]
374: .getAttribute("extends")));
375:
376: if (context.getSuperBinding() == null) {
377: // not found in library
378: context
379: .setSuperBinding(getBindingByIdOrPath(
380: id, path,
381: existingBindings));
382: }
383: } catch (LibraryException e) {
384: throw new BindingException(
385: "Error extending binding! (at "
386: + DomHelper
387: .getLocation(childElements[i])
388: + ")", e);
389: }
390: }
391: childBindings[i] = getBindingForConfigurationElement(childElements[i]);
392: popContext();
393: }
394: return mergeBindings(existingBindings,
395: childBindings);
396: }
397: }
398: return existingBindings;
399: }
400:
401: private JXPathBindingBase getBindingByIdOrPath(String id,
402: String path, JXPathBindingBase[] bindings) {
403: String name = id;
404: if (name == null) {
405: name = "Context:" + path;
406: }
407: for (int i = 0; i < bindings.length; i++) {
408: if (name.equals(bindings[i].getId())) {
409: return bindings[i];
410: }
411: }
412: return null;
413: }
414:
415: public DatatypeManager getDatatypeManager() {
416: return datatypeManager;
417: }
418:
419: public ServiceManager getServiceManager() {
420: return manager;
421: }
422:
423: public LibraryManager getLibraryManager() {
424: return libraryManager;
425: }
426:
427: public BindingBuilderContext getContext() {
428: return this .context;
429: }
430:
431: private void pushContext() {
432: BindingBuilderContext c = new BindingBuilderContext(context);
433: contextStack.push(context);
434: context = c;
435: }
436:
437: private void popContext() {
438: if (!contextStack.empty()) {
439: context = (BindingBuilderContext) contextStack.pop();
440: } else {
441: context = new BindingBuilderContext();
442: }
443: }
444: }
445: }
|