All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.metawidget.inspector.composite.CompositeInspector Maven / Gradle / Ivy

There is a newer version: 4.2
Show newest version
// Metawidget
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package org.metawidget.inspector.composite;

import static org.metawidget.inspector.InspectionResultConstants.*;

import org.metawidget.inspector.iface.DomInspector;
import org.metawidget.inspector.iface.Inspector;
import org.metawidget.inspector.iface.InspectorException;
import org.metawidget.util.ArrayUtils;
import org.metawidget.util.LogUtils;
import org.metawidget.util.LogUtils.Log;
import org.metawidget.util.XmlUtils;
import org.metawidget.util.simple.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * Delegates inspection to one or more sub-inspectors, then combines the resulting DOMs.
 * 

* The combining algorithm works as follows. For each element: *

    *
  • top-level elements with the same type attribute in both DOMs are combined *
  • child elements with the same name attribute in both DOMs are combined *
  • the ordering of elements in the existing DOM is preserved. New child elements are added * either at the end or immediately after the last combined child *
  • element attributes from the new DOM override ones in the existing DOM *
*

* This algorithm should be suitable for most use cases, but one benefit of having a separate * CompositeInspector is that developers can replace it with their own version, with its own * combining algorithm, if required. *

* Note: the name CompositeInspector refers to the Composite design pattern. * * @author Richard Kennard */ public class CompositeInspector implements DomInspector { // // Private statics // private static final Log LOG = LogUtils.getLog( CompositeInspector.class ); // // Private members // /* package private */final Inspector[] mInspectors; // // Constructor // public CompositeInspector( CompositeInspectorConfig config ) { Inspector[] inspectors = config.getInspectors(); // Must have at least one Inspector. At least two, really, but one can be useful // if we want to validate what the sub-Inspector is returning (ie. using LOG.debug) if ( inspectors == null || inspectors.length == 0 ) { throw InspectorException.newException( "CompositeInspector needs at least one Inspector" ); } // Defensive copy mInspectors = new Inspector[inspectors.length]; for ( int loop = 0, length = inspectors.length; loop < length; loop++ ) { Inspector inspector = inspectors[loop]; for ( int checkDuplicates = 0; checkDuplicates < loop; checkDuplicates++ ) { if ( mInspectors[checkDuplicates].equals( inspector ) ) { throw InspectorException.newException( "CompositeInspector's list of Inspectors contains two of the same " + inspector.getClass().getName() ); } } mInspectors[loop] = inspector; } } // // Public methods // /** * Inspect the given Object according to the given path, and return the result as a String * conforming to inspection-result-1.0.xsd. *

* This method is marked final because most Metawidget implementations will call * inspectAsDom directly instead. So subclasses need to override * inspectAsDom, not inspect. */ public final String inspect( Object toInspect, String type, String... names ) { return inspect( null, toInspect, type, names ); } /** * If your architecture is strongly separated, some metadata may only be available in one tier * (eg. JPA annotations in the backend) and some only available in another tier (eg. * struts-config.xml in the front-end). *

* For this, CompositeInspector supplies this overloaded method outside the normal * Inspector interface. It takes an additional XML string of inspection results, * and merges forthcoming inspection results with it. *

* This method is marked final because most Metawidget implementations will call * inspectAsDom directly instead. So subclasses need to override * inspectAsDom, not inspect. */ public final String inspect( String master, Object toInspect, String type, String... names ) { Element element = inspectAsDom( XmlUtils.documentFromString( master ), toInspect, type, names ); if ( element == null ) { return null; } return XmlUtils.nodeToString( element, false ); } /** * This method is marked final as it delegates directly to * inspectAsDom( Document, Object, String, String... ). Subclasses should override * that method instead. */ public final Element inspectAsDom( Object toInspect, String type, String... names ) { return inspectAsDom( null, toInspect, type, names ); } /** * If your architecture is strongly separated, some metadata may only be available in one tier * (eg. JPA annotations in the backend) and some only available in another tier (eg. * struts-config.xml in the front-end). *

* For this, CompositeInspector supplies this overloaded method outside the normal * DomInspector interface. It takes an additional DOM of inspection results, and * merges forthcoming inspection results with it. */ public Element inspectAsDom( Document masterDocument, Object toInspect, String type, String... names ) { try { Document masterDocumentToUse = runInspectors( masterDocument, toInspect, type, names ); if ( masterDocumentToUse == null || !masterDocumentToUse.hasChildNodes() ) { if ( toInspect != null && type != null && LOG.isWarnEnabled() ) { LOG.warn( "No inspectors matched path == {0}{1}", type, ArrayUtils.toString( names, StringUtils.SEPARATOR_FORWARD_SLASH, true, false ) ); } return null; } // (debug) if ( LOG.isDebugEnabled() ) { String formattedXml = XmlUtils.documentToString( masterDocumentToUse, true ); LOG.debug( "Inspected {0}{1}\r\n{2}", type, ArrayUtils.toString( names, StringUtils.SEPARATOR_FORWARD_SLASH, true, false ), formattedXml ); } // (warn) Element root = masterDocumentToUse.getDocumentElement(); if ( toInspect != null && type != null && LOG.isWarnEnabled() && !root.hasChildNodes() ) { LOG.warn( "No inspectors matched path == {0}{1}", type, ArrayUtils.toString( names, StringUtils.SEPARATOR_FORWARD_SLASH, true, false ) ); return root; } return root; } catch ( Exception e ) { throw InspectorException.newException( e ); } } // // Protected methods // /** * Run the sub-Inspectors on the given toInspect and combine the result. *

* Subclasses may override this method to, say, run some other Inspectors concurrently. */ protected Document runInspectors( Document masterDocument, Object toInspect, String type, String... names ) throws Exception { Document masterDocumentToUse = masterDocument; // Run each Inspector... for ( Inspector inspector : mInspectors ) { // ...parse the result... Document inspectionDocument = runInspector( inspector, toInspect, type, names ); // ...combine them... masterDocumentToUse = combineInspectionResult( masterDocumentToUse, inspectionDocument ); } // ...and return them return masterDocumentToUse; } protected Document runInspector( Inspector inspector, Object toInspect, String type, String... names ) throws Exception { // DomInspector... if ( inspector instanceof DomInspector ) { @SuppressWarnings( "unchecked" ) DomInspector domInspector = (DomInspector) inspector; Element element = domInspector.inspectAsDom( toInspect, type, names ); if ( element == null ) { return null; } if ( LOG.isTraceEnabled() ) { String xml = XmlUtils.nodeToString( element, true ); LOG.trace( "{0} inspected {1}{2}\r\n{3}", inspector.getClass(), type, ArrayUtils.toString( names, StringUtils.SEPARATOR_FORWARD_SLASH, true, false ), xml ); } validate( element.getOwnerDocument() ); return element.getOwnerDocument(); } // ...or just regular Inspector String xml = inspector.inspect( toInspect, type, names ); if ( xml == null ) { return null; } LOG.trace( "{0} inspected {1}{2}\r\n{3}", inspector.getClass(), type, ArrayUtils.toString( names, StringUtils.SEPARATOR_FORWARD_SLASH, true, false ), xml ); Document document = XmlUtils.documentFromString( xml ); validate( document ); return document; } protected Document combineInspectionResult( Document masterDocument, Document inspectionDocument ) { // Short circuit... if ( inspectionDocument == null || !inspectionDocument.hasChildNodes() ) { return masterDocument; } if ( masterDocument == null || !masterDocument.hasChildNodes() ) { return inspectionDocument; } // ...or full combine XmlUtils.combineElements( masterDocument.getDocumentElement(), inspectionDocument.getDocumentElement(), TYPE, NAME ); return masterDocument; } /** * Does nothing by default. *

* Subclasses can override this method to hook in validation. * * @param document * the inspection result DOM */ protected void validate( Document document ) throws Exception { // Do nothing } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy