Friday, March 15, 2013

XML Schema, WSDL and SOAP UI Generator

Following a recent request, the next release of Metawidget will add support for XML Schema (XSD) files. It should be useful for anybody wanting to automatically generate User Interfaces from XSD schemas, Web Services Description Language (WSDL) files, or SOAP.

To use, simply take an existing XML Schema or WSDL file:

<?xml version="1.0"?>
<wsdl:definitions ...>

   ...
   <wsdl:types>
      <xsd:schema>
         <xsd:element name="GetEndorsingBoarder">
            <xsd:complexType>
               <xsd:sequence>
                  <xsd:element name="name" type="string" minOccurs="1" />
                  <xsd:element name="manufacturer" type="string" />
                  <xsd:element name="model" type="xs:integer" />
                  <xsd:element name="quantity">
                     <xsd:simpleType>
                        <xsd:restriction base="xs:integer">
                           <xsd:minInclusive value="0" />
                           <xsd:maxInclusive value="8" />
                        </xsd:restriction>
                     </xsd:simpleType>
                  </xsd:element>
                  <xsd:element name="active" type="xs:boolean" />
               </xsd:sequence>
            </xsd:complexType>
         </xsd:element>

   ...

</wsdl:definitions>

...point Metawidget at it...

public class Main {

   public static void main( String[] args ) {

      final SwingMetawidget metawidget = new SwingMetawidget();
      metawidget.setInspector( new WsdlInspector(
         new XmlSchemaInspectorConfig().setInputStream( new SimpleResourceResolver()
            .openResource( "endorsementSearch.wsdl" ) ) ) );
      metawidget.addInspectionResultProcessor( new XmlSchemaToJavaTypeMappingProcessor() );
      metawidget.setPath( "GetEndorsingBoarder" );


      JFrame frame = new JFrame( "Metawidget WSDL" );
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.getContentPane().add( metawidget );
      frame.setSize( 400, 210 );
      metawidget.setBorder( BorderFactory.createEmptyBorder( 5,5,5,5 ) );
      frame.setVisible( true );
   }
}

...and this will create the screenshot seen below:

Getting data into the UI components and back out again will vary depending on your requirements (choice of UI toolkit, choice of data storage etc). Here's a simple implementation that reads/writes from a DOM document to Swing JComponents:

public class DocumentBindingProcessor
   implements AdvancedWidgetProcessor<JComponent, SwingMetawidget> {

   //
   // Public methods
   //

   @Override
   public void onStartBuild( SwingMetawidget metawidget ) {

      getWrittenComponents( metawidget ).clear();
   }

   /**
    * Retrieve the values from the Document and put them in the Components.
    */

   @Override
   public JComponent processWidget( JComponent component, String elementName, Map<String, String> attributes, SwingMetawidget metawidget ) {

      String attributeName = attributes.get( NAME );
      getWrittenComponents( metawidget ).put( attributeName, component );

      // Fetch the value...

      Document toInspect = metawidget.getToInspect();
      Element root = toInspect.getDocumentElement();
      Element valueNode = XmlUtils.getChildNamed( root, attributeName );

      if ( valueNode == null ) {
         return component;
      }

      // ...and apply it to the component. For simplicity, we won't worry about converters

      String componentProperty = metawidget.getValueProperty( component );
      ClassUtils.setProperty( component, componentProperty, valueNode.getTextContent() );

      return component;
   }

   @Override
   public void onEndBuild( SwingMetawidget metawidget ) {

      // Do nothing
   }

   /**
    * Store the values from the Components back into the Document.
    */

   public void save( SwingMetawidget metawidget ) {

      Document toInspect = metawidget.getToInspect();
      Element root = toInspect.getDocumentElement();

      for ( Map.Entry<String, JComponent> entry : getWrittenComponents( metawidget ).entrySet() ) {

         JComponent component = entry.getValue();
         String componentProperty = metawidget.getValueProperty( component );
         Object value = ClassUtils.getProperty( component, componentProperty );

         Element valueNode = XmlUtils.getChildNamed( root, entry.getKey() );

         if ( valueNode == null ) {
            continue;
         }

         valueNode.setTextContent( (String) value );
      }
   }

   //
   // Private methods
   //

   /**
    * During load-time we keep track of all the components. At save-time we write them all back
    * again.
    */

   private Map<String, JComponent> getWrittenComponents( SwingMetawidget metawidget ) {

      @SuppressWarnings( "unchecked" )
      Map<String, JComponent> writtenComponents = (Map<String, JComponent>) metawidget.getClientProperty( DocumentBindingProcessor.class );

      if ( writtenComponents == null ) {
         writtenComponents = CollectionUtils.newHashMap();
         metawidget.putClientProperty( DocumentBindingProcessor.class, writtenComponents );
      }

      return writtenComponents;
   }
}

To use it, simply add it into your Metawidget's pipeline:

metawidget.addInspectionResultProcessor( ... );
metawidget.addWidgetProcessor( new DocumentBindingProcessor() );
String xml = "<endorsingBoarder><name>Richard</name><manufacturer>Kennard Consulting</manufacturer></endorsingBoarder>";

final Document document = XmlUtils.documentFromString( xml );
metawidget.setToInspect( document );

metawidget.setPath( "GetEndorsingBoarder" );

...

metawidget.add( new JButton( new AbstractAction( "Save" ) {

   public void actionPerformed( ActionEvent e ) {
      metawidget.getWidgetProcessor( DocumentBindingProcessor.class ).save( metawidget );
      System.out.println( XmlUtils.documentToString( document, true ));

   }
} ));

And you'll get this:

Feedback welcome!

12 comments:

Ron Schultz said...

Richard, Any timeline for when this feature will be available? And will this be available via the Javascript UI also? I have a number of schemas and XML that this facility would assist greatly with for processing.

Ron Schultz said...

Richard, Any timeline for when this feature will be available? I was so excited I immediately tried your example - and saw that the feature was not yet present. :-( Will the Javascript UI also be supported? I have a number of XML schemas that this would be useful for. Thanks.

Also - do you have any support offerings for less-than corporate use. I would like to purchase support for assistance with a personal research project integrating Metawidget and Stanford's Protege Frames - but being an individual and not a corporation 1500 exceeds my budget.

Thanks again for a great offering!

Ron

Richard Kennard said...

Ron,

Thanks for your interest. It's available now in the 3.3-SNAPSHOT release if you'd like to try it:

https://repository.jboss.org/nexus/content/repositories/snapshots/org/metawidget/modules/metawidget-all/3.3-SNAPSHOT

The implementation is Java-based, not JavaScript-based. So you could either a) rewrite it as a JavaScript-based Inspector (tricky); b) compile it using GWT (haven't tried) or c) call the Inspector server-side and pass the inspection result back to the JavaScript Metawidget (easier). Would c) suit you?

Regards,

Richard.

Ron Schultz said...

Thanks Richard. I downloaded the latest snapshot but the WsdlInspector and other new classes were not yet present in metawidget-all-3.3-20130318.024457-46.jar.

WRT your response - Calling the inspector server side would work fine in my case.

Ron

Richard Kennard said...

My apologies. Could you please try again now?

Also, I have added a utility method XmlUtils.elementToJson which should help you return the inspection-result to the client via a REST service (see http://blog.kennardconsulting.com/2013/02/metawidget-and-rest.html)

Ron Schultz said...

Richard,

Not sure if I am doing this correctly or not. I am not all that familiar with metawidget - but here is the schema I am trying to process - an ISO 20022 standard message:

The schema I am trying to process is located here: http://www.iso20022.org/documents/messages/acmt/schemas/acmt.010.001.01.zip

And here is the code I am using to process this schema based on your example:

package test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

import javax.swing.BorderFactory;
import javax.swing.JFrame;

import org.metawidget.inspectionresultprocessor.xsd.XmlSchemaToJavaTypeMappingProcessor;
import org.metawidget.inspector.xsd.XmlSchemaInspector;
import org.metawidget.inspector.xsd.XmlSchemaInspectorConfig;
import org.metawidget.swing.SwingMetawidget;

public class SwingXSD {

public static void main( String[] args ) {

final SwingMetawidget metawidget = new SwingMetawidget();
try {
metawidget.setInspector( new XmlSchemaInspector(
new XmlSchemaInspectorConfig().setInputStream( new FileInputStream( "C:/Users/schulron/git/ebam/iso-messages/schemas/acmt.010.001.01.xsd" ) ) ) );
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
metawidget.addInspectionResultProcessor( new XmlSchemaToJavaTypeMappingProcessor() );
metawidget.setPath( "name="AccountRequestAcknowledgementV01">" );

JFrame frame = new JFrame( "Metawidget WSDL" );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.getContentPane().add( metawidget );
frame.setSize( 400, 210 );
metawidget.setBorder( BorderFactory.createEmptyBorder( 5,5,5,5 ) );
frame.setVisible( true );
}
}

And the error I get is:

Exception in thread "main" org.metawidget.inspector.iface.InspectorException: Unexpected child node 'restriction'
at org.metawidget.inspector.iface.InspectorException.newException(InspectorException.java:56)
at org.metawidget.inspector.xsd.XmlSchemaInspector.traverseFromTopLevelTypeToNamedChildren(XmlSchemaInspector.java:129)
at org.metawidget.inspector.impl.BaseXmlInspector.traverse(BaseXmlInspector.java:703)
at org.metawidget.inspector.impl.BaseXmlInspector.inspectAsDom(BaseXmlInspector.java:306)
at org.metawidget.inspector.impl.BaseXmlInspector.inspectAsDom(BaseXmlInspector.java:106)
at org.metawidget.pipeline.base.BasePipeline.inspectAsDom(BasePipeline.java:366)
at org.metawidget.swing.SwingMetawidget.inspect(SwingMetawidget.java:891)

Thank you,

Ron

Ron Schultz said...

Richard,

And here is a simpler schema I am trying to process. I noticed that restrictions and annotations are creating issues for the inspector. I stripped the annotations from this example.
































































Regards,

Ron

Richard Kennard said...

Thanks for helping me test this new functionality!

Could you please send a sample project to support@metawidget.org? I note in the code snippet you pasted above the 'metawidget.setPath' line appears corrupted?

Manu said...

Hi Richard,
Can you please share a complete working solution/example which can generate the UI using an xsd and then binding an existing xml (based off the xsd above) to bind it to the generated UI ?

Thanks

Richard Kennard said...

Hi Manu,

Thanks for your interest in Metawidget!

I'm unclear what you're asking for here. The project you described is basically what this blog already is!

Are you asking for this blog post packaged into a working project? If so, I have now done that for you at:

https://github.com/metawidget/metawidget/tree/master/integration-tests/java/swing/xsd

Let me know if that works for you.

Regards,

Richard.

Anonymous said...

Seems you can't have annotation elements in your XSD?


e.g.,

Exception in thread "main" org.metawidget.inspector.iface.InspectorException: Unexpected child node 'annotation'
at org.metawidget.inspector.iface.InspectorException.newException(Unknown Source)
at org.metawidget.inspector.xsd.XmlSchemaInspector.traverseFromTopLevelTypeToNamedChildren(Unknown Source)
at org.metawidget.inspector.impl.BaseXmlInspector.traverse(Unknown Source)
at org.metawidget.inspector.impl.BaseXmlInspector.inspectAsDom(Unknown Source)
at org.metawidget.inspector.impl.BaseXmlInspector.inspectAsDom(Unknown Source)
at org.metawidget.pipeline.base.BasePipeline.inspectAsDom(Unknown Source)

Richard Kennard said...

Yes, apologies. I am hoping to improve XSD parsing support soon. Contributions welcome!