/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can * select the license that you prefer but you may not use this file except in * compliance with one of these Licenses. * * You can obtain a copy of the Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * See the Licenses for the specific language governing permissions and * limitations under the Licenses. * * Alternatively, you can obtain a royalty free commercial license with less * limitations, transferable or non-transferable, directly at * http://restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet S.A.S. */ package org.restlet.ext.wadl; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import org.restlet.Context; import org.restlet.data.Language; import org.restlet.data.MediaType; import org.restlet.data.Method; import org.restlet.data.Reference; import org.restlet.data.Status; import org.restlet.engine.Engine; import org.restlet.ext.xml.DomRepresentation; import org.restlet.ext.xml.SaxRepresentation; import org.restlet.ext.xml.TransformRepresentation; import org.restlet.ext.xml.XmlRepresentation; import org.restlet.ext.xml.XmlWriter; import org.restlet.representation.InputRepresentation; import org.restlet.representation.Representation; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.helpers.DefaultHandler; /** * Root of a WADL description document. For the {@link #getHtmlRepresentation()} * method to work properly, you will certainly have to update your classpath * with a recent version of <a href="http://xml.apache.org/xalan-j/"> Apache * Xalan XSLT engine</a> (version 2.7.1 has been successfully tested). This is * due to the <a href="http://github.com/mnot/wadl_stylesheets/">XSLT * stylesheet</a> bundled which relies on EXSLT features.<br> * * @author Jerome Louvel */ public class WadlRepresentation extends SaxRepresentation { // ------------------- // Content reader part // ------------------- private static class ContentReader extends DefaultHandler implements LexicalHandler { public enum MixedContentState { CDATA, COMMENT, ELEMENT, ENTITY, NONE, TEXT } public enum State { APPLICATION, DOCUMENTATION, FAULT, GRAMMARS, INCLUDE, LINK, METHOD, NONE, OPTION, PARAMETER, REPRESENTATION, REQUEST, RESOURCE, RESOURCES, RESOURCETYPE, RESPONSE } /** The current parsed "application" tag. */ private ApplicationInfo currentApplication; /** The current parsed "documentation" tag. */ private DocumentationInfo currentDocumentation; /** The current parsed "grammars" tag. */ private GrammarsInfo currentGrammars; /** The current parsed "include" tag. */ private IncludeInfo currentInclude; /** The current parsed "link" tag. */ private LinkInfo currentLink; /** The current parsed "method" tag. */ private MethodInfo currentMethod; /** The current mixed content CDataSection. */ private CDATASection currentMixedContentCDataSection; /** The current mixed content node. */ private Node currentMixedContentNode; /** The current parsed "option" tag. */ private OptionInfo currentOption; /** The current parsed "param" tag. */ private ParameterInfo currentParameter; /** The current parsed "representation" tag. */ private RepresentationInfo currentRepresentation; /** The current parsed "request" tag. */ private RequestInfo currentRequest; /** The current parsed "resources" tag. */ private ResourcesInfo currentResources; /** The list of the current parsed "resource" tags. */ private final List<ResourceInfo> currentResourcesList; /** The current parsed "resource_type" tag. */ private ResourceTypeInfo currentResourceType; /** The current parsed "response" tag. */ private ResponseInfo currentResponse; /** The document used to create the mixed content. */ private Document mixedContentDocument; /** The stack of mixed content parser states. */ private final List<MixedContentState> mixedContentStates; /** The top node of mixed content nodes. */ private Node mixedContentTopNode; /** * Map of namespaces used in the WADL document. The key is the URI of * the namespace and the value, the prefix. */ private Map<String, String> namespaces; /** The stack of parser states. */ private final List<State> states; /** The WadlRepresentation instance that represents the parsed document. */ private final WadlRepresentation wadlRepresentation; /** * Constructor * * @param wadlRepresentation * The WadlRepresentation instance that represents the parsed * document. */ public ContentReader(WadlRepresentation wadlRepresentation) { this.states = new ArrayList<State>(); this.states.add(State.NONE); this.mixedContentStates = new ArrayList<MixedContentState>(); this.mixedContentStates.add(MixedContentState.NONE); this.currentApplication = null; this.currentDocumentation = null; this.currentGrammars = null; this.currentInclude = null; this.currentLink = null; this.currentMethod = null; this.currentOption = null; this.currentParameter = null; this.currentRepresentation = null; this.currentRequest = null; this.currentResourcesList = new ArrayList<ResourceInfo>(); this.currentResources = null; this.currentResourceType = null; this.currentResponse = null; try { this.mixedContentDocument = new DomRepresentation( MediaType.TEXT_XML).getDocument(); } catch (IOException e) { } this.currentMixedContentCDataSection = null; this.namespaces = new HashMap<String, String>(); this.wadlRepresentation = wadlRepresentation; } /** * Receive notification of character data. * * @param ch * The characters from the XML document. * @param start * The start position in the array. * @param length * The number of characters to read from the array. */ @Override public void characters(char[] ch, int start, int length) throws SAXException { if (getState() == State.DOCUMENTATION) { if (getMixedContentState() == MixedContentState.CDATA) { this.currentMixedContentCDataSection.appendData(new String( ch, start, length)); } else if (getMixedContentState() != MixedContentState.ENTITY) { this.currentMixedContentNode .appendChild(this.mixedContentDocument .createTextNode(new String(ch, start, length))); } } } /** * Receive notification of a comment section. * * @param ch * The characters from the XML document. * @param start * The start position in the array. * @param length * The number of characters to read from the array. */ public void comment(char[] ch, int start, int length) throws SAXException { if (getState() == State.DOCUMENTATION) { this.currentMixedContentNode .appendChild(this.mixedContentDocument .createComment(new String(ch, start, length))); } } /** * Receive notification of the end of a CDATA sectionn. */ public void endCDATA() throws SAXException { if (getState() == State.DOCUMENTATION) { popMixedContentState(); } } /** * Receive notification of the end of a document. */ @Override public void endDocument() throws SAXException { popState(); if (this.namespaces != null && !this.namespaces.isEmpty() && this.currentApplication != null) { this.currentApplication.setNamespaces(namespaces); } this.wadlRepresentation.setApplication(this.currentApplication); } public void endDTD() throws SAXException { } /** * Receive notification of the end of an element. * * @param uri * The Namespace URI, or the empty string if the element has * no Namespace URI or if Namespace processing is not being * performed. * @param localName * The local name (without prefix), or the empty string if * Namespace processing is not being performed. * @param qName * The qualified XML name (with prefix), or the empty string * if qualified names are not available. */ @Override public void endElement(String uri, String localName, String qName) throws SAXException { if (uri.equalsIgnoreCase(APP_NAMESPACE)) { if (localName.equals("application")) { popState(); } else if (localName.equals("doc")) { this.currentDocumentation .setMixedContent(mixedContentTopNode); popState(); } else if (localName.equals("fault")) { popState(); } else if (localName.equals("grammars")) { popState(); } else if (localName.equals("include")) { popState(); } else if (localName.equals("link")) { popState(); } else if (localName.equals("method")) { popState(); } else if (localName.equals("option")) { popState(); } else if (localName.equals("param")) { popState(); } else if (localName.equals("representation")) { popState(); } else if (localName.equals("request")) { popState(); } else if (localName.equals("resource")) { this.currentResourcesList.remove(0); popState(); } else if (localName.equals("resources")) { popState(); } else if (localName.equals("resource_type")) { popState(); } else if (localName.equals("response")) { popState(); } } else { if (getState() == State.DOCUMENTATION) { popMixedContentState(); this.currentMixedContentNode = this.currentMixedContentNode .getParentNode(); } } } /** * Receive notification of the end of an entity section. * * @param name * The name of the entity. */ public void endEntity(String name) throws SAXException { popMixedContentState(); } /** * Returns the current state when processing mixed content sections. * * @return The current state when processing mixed content sections. */ private MixedContentState getMixedContentState() { final MixedContentState result = this.mixedContentStates.get(0); return result; } /** * Returns a parameterStyle value according to the given string or null. * * @param parameterStyle * The given string. * @return The parameterStyle value that corresponds to the given style * name, or null otherwise. */ public ParameterStyle getParameterStyle(String parameterStyle) { ParameterStyle result = null; if ("header".equalsIgnoreCase(parameterStyle)) { result = ParameterStyle.HEADER; } else if ("matrix".equalsIgnoreCase(parameterStyle)) { result = ParameterStyle.MATRIX; } else if ("plain".equalsIgnoreCase(parameterStyle)) { result = ParameterStyle.PLAIN; } else if ("query".equalsIgnoreCase(parameterStyle)) { result = ParameterStyle.QUERY; } else if ("template".equalsIgnoreCase(parameterStyle)) { result = ParameterStyle.TEMPLATE; } return result; } /** * Returns the current state when processing the WADL document. * * @return the current state when processing the WADL document. */ private State getState() { final State result = this.states.get(0); return result; } /** * Drops the current state from the stack and returns it. This state * becomes the former current state. * * @return the former current state. */ private MixedContentState popMixedContentState() { return this.mixedContentStates.remove(0); } /** * Drops the current state from the stack and returns it. This state * becomes the former current state. * * @return the former current state. */ private State popState() { return this.states.remove(0); } /** * Adds the given state. * * @param state * The given state. */ private void pushMixedContentState(MixedContentState state) { this.mixedContentStates.add(0, state); } /** * Adds the given state. * * @param state * The given state. */ private void pushState(State state) { this.states.add(0, state); } /** * Receive notification of the beginning of a CDATA section. */ public void startCDATA() throws SAXException { if (getState() == State.DOCUMENTATION) { pushMixedContentState(MixedContentState.CDATA); this.currentMixedContentCDataSection = this.mixedContentDocument .createCDATASection(""); this.currentMixedContentNode .appendChild(this.currentMixedContentCDataSection); } } /** * Receive notification of the beginning of a document. */ @Override public void startDocument() throws SAXException { } public void startDTD(String name, String publicId, String systemId) throws SAXException { } /** * Receive notification of the beginning of an element. * * @param uri * The Namespace URI, or the empty string if the element has * no Namespace URI or if Namespace processing is not being * performed. * @param localName * The local name (without prefix), or the empty string if * Namespace processing is not being performed. * @param qName * The qualified name (with prefix), or the empty string if * qualified names are not available. * @param attrs * The attributes attached to the element. If there are no * attributes, it shall be an empty Attributes object. The * value of this object after startElement returns is * undefined. */ @Override public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException { if (uri.equalsIgnoreCase(APP_NAMESPACE)) { if (localName.equals("application")) { this.currentApplication = new ApplicationInfo(); pushState(State.APPLICATION); } else if (localName.equals("doc")) { this.currentDocumentation = new DocumentationInfo(); this.mixedContentTopNode = this.mixedContentDocument .createDocumentFragment(); this.currentMixedContentNode = mixedContentTopNode; this.currentMixedContentCDataSection = null; if (attrs.getIndex("xml:lang") != -1) { this.currentDocumentation.setLanguage(Language .valueOf(attrs.getValue("xml:lang"))); } if (attrs.getIndex("lang") != -1) { this.currentDocumentation.setLanguage(Language .valueOf(attrs.getValue("lang"))); } if (attrs.getIndex("title") != -1) { this.currentDocumentation.setTitle(attrs .getValue("title")); } if (getState() == State.APPLICATION) { this.currentApplication.getDocumentations().add( this.currentDocumentation); } else if (getState() == State.GRAMMARS) { this.currentGrammars.getDocumentations().add( this.currentDocumentation); } else if (getState() == State.INCLUDE) { this.currentInclude.getDocumentations().add( this.currentDocumentation); } else if (getState() == State.LINK) { this.currentLink.getDocumentations().add( this.currentDocumentation); } else if (getState() == State.METHOD) { this.currentMethod.getDocumentations().add( this.currentDocumentation); } else if (getState() == State.OPTION) { this.currentOption.getDocumentations().add( this.currentDocumentation); } else if (getState() == State.PARAMETER) { this.currentParameter.getDocumentations().add( this.currentDocumentation); } else if (getState() == State.REPRESENTATION) { this.currentRepresentation.getDocumentations().add( this.currentDocumentation); } else if (getState() == State.REQUEST) { this.currentRequest.getDocumentations().add( this.currentDocumentation); } else if (getState() == State.RESOURCE) { this.currentResourcesList.get(0).getDocumentations() .add(this.currentDocumentation); } else if (getState() == State.RESOURCES) { this.currentResources.getDocumentations().add( this.currentDocumentation); } else if (getState() == State.RESOURCETYPE) { this.currentResourceType.getDocumentations().add( this.currentDocumentation); } else if (getState() == State.RESPONSE) { this.currentResponse.getDocumentations().add( this.currentDocumentation); } pushState(State.DOCUMENTATION); } else if (localName.equals("grammars")) { this.currentGrammars = new GrammarsInfo(); if (getState() == State.APPLICATION) { this.currentApplication .setGrammars(this.currentGrammars); } pushState(State.GRAMMARS); } else if (localName.equals("include")) { this.currentInclude = new IncludeInfo(); if (attrs.getIndex("href") != -1) { this.currentInclude.setTargetRef(new Reference(attrs .getValue("href"))); } if (getState() == State.GRAMMARS) { this.currentGrammars.getIncludes().add( this.currentInclude); } pushState(State.INCLUDE); } else if (localName.equals("link")) { this.currentLink = new LinkInfo(); if (attrs.getIndex("rel") != -1) { this.currentLink.setRelationship(attrs.getValue("rel")); } if (attrs.getIndex("rev") != -1) { this.currentLink.setReverseRelationship(attrs .getValue("rev")); } if (attrs.getIndex("resource_type") != -1) { this.currentLink.setResourceType(new Reference(attrs .getValue("resource_type"))); } if (getState() == State.PARAMETER) { this.currentParameter.setLink(this.currentLink); } pushState(State.LINK); } else if (localName.equals("method")) { this.currentMethod = new MethodInfo(); if (attrs.getIndex("href") != -1) { this.currentMethod.setTargetRef(new Reference(attrs .getValue("href"))); } if (attrs.getIndex("id") != -1) { this.currentMethod.setIdentifier(attrs.getValue("id")); } if (attrs.getIndex("name") != -1) { this.currentMethod.setName(Method.valueOf(attrs .getValue("name"))); } if (getState() == State.APPLICATION) { this.currentApplication.getMethods().add( this.currentMethod); } else if (getState() == State.RESOURCE) { this.currentResourcesList.get(0).getMethods() .add(this.currentMethod); } else if (getState() == State.RESOURCETYPE) { this.currentResourceType.getMethods().add( this.currentMethod); } pushState(State.METHOD); } else if (localName.equals("option")) { this.currentOption = new OptionInfo(); if (attrs.getIndex("value") != -1) { this.currentOption.setValue(attrs.getValue("value")); } if (getState() == State.PARAMETER) { this.currentParameter.getOptions().add( this.currentOption); } pushState(State.OPTION); } else if (localName.equals("param")) { this.currentParameter = new ParameterInfo(); if (attrs.getIndex("default") != -1) { this.currentParameter.setDefaultValue(attrs .getValue("default")); } if (attrs.getIndex("fixed") != -1) { this.currentParameter.setFixed(attrs.getValue("fixed")); } if (attrs.getIndex("id") != -1) { this.currentParameter.setIdentifier(attrs .getValue("id")); } if (attrs.getIndex("path") != -1) { this.currentParameter.setPath(attrs.getValue("path")); } if (attrs.getIndex("style") != -1) { this.currentParameter.setStyle(getParameterStyle(attrs .getValue("style"))); } if (attrs.getIndex("name") != -1) { this.currentParameter.setName(attrs.getValue("name")); } if (attrs.getIndex("type") != -1) { this.currentParameter.setType(attrs.getValue("type")); } if (attrs.getIndex("repeating") != -1) { this.currentParameter.setRepeating(Boolean .parseBoolean(attrs.getValue("repeating"))); } if (attrs.getIndex("required") != -1) { this.currentParameter.setRequired(Boolean .parseBoolean(attrs.getValue("required"))); } if (getState() == State.REPRESENTATION) { this.currentRepresentation.getParameters().add( this.currentParameter); } else if (getState() == State.REQUEST) { this.currentRequest.getParameters().add( this.currentParameter); } else if (getState() == State.RESOURCE) { this.currentResourcesList.get(0).getParameters() .add(this.currentParameter); } else if (getState() == State.RESOURCETYPE) { this.currentRequest.getParameters().add( this.currentParameter); } else if (getState() == State.RESPONSE) { this.currentRequest.getParameters().add( this.currentParameter); } pushState(State.PARAMETER); } else if (localName.equals("representation")) { this.currentRepresentation = new RepresentationInfo(); if (attrs.getIndex("id") != -1) { this.currentRepresentation.setIdentifier(attrs .getValue("id")); } if (attrs.getIndex("mediaType") != -1) { this.currentRepresentation.setMediaType(MediaType .valueOf(attrs.getValue("mediaType"))); } if (attrs.getIndex("element") != -1) { this.currentRepresentation.setXmlElement(attrs .getValue("element")); } if (attrs.getIndex("profile") != -1) { final String[] profiles = attrs.getValue("profile") .split(" "); for (final String string : profiles) { this.currentRepresentation.getProfiles().add( new Reference(string)); } } if (getState() == State.APPLICATION) { this.currentApplication.getRepresentations().add( this.currentRepresentation); } else if (getState() == State.REQUEST) { this.currentRequest.getRepresentations().add( this.currentRepresentation); } else if (getState() == State.RESPONSE) { this.currentResponse.getRepresentations().add( this.currentRepresentation); } pushState(State.REPRESENTATION); } else if (localName.equals("request")) { this.currentRequest = new RequestInfo(); if (getState() == State.METHOD) { this.currentMethod.setRequest(this.currentRequest); } pushState(State.REQUEST); } else if (localName.equals("resource")) { final ResourceInfo resourceInfo = new ResourceInfo(); if (attrs.getIndex("id") != -1) { resourceInfo.setIdentifier(attrs.getValue("id")); } if (attrs.getIndex("path") != -1) { resourceInfo.setPath(attrs.getValue("path")); } if (attrs.getIndex("queryType") != -1) { resourceInfo.setQueryType(MediaType.valueOf(attrs .getValue("queryType"))); } if (attrs.getIndex("type") != -1) { final String[] type = attrs.getValue("type").split(" "); for (final String string : type) { resourceInfo.getType().add(new Reference(string)); } } if (getState() == State.RESOURCE) { this.currentResourcesList.get(0).getChildResources() .add(resourceInfo); } else if (getState() == State.RESOURCES) { this.currentResources.getResources().add(resourceInfo); } this.currentResourcesList.add(0, resourceInfo); pushState(State.RESOURCE); } else if (localName.equals("resources")) { this.currentResources = new ResourcesInfo(); if (attrs.getIndex("base") != -1) { this.currentResources.setBaseRef(new Reference(attrs .getValue("base"))); } if (getState() == State.APPLICATION) { this.currentApplication .setResources(this.currentResources); } pushState(State.RESOURCES); } else if (localName.equals("resource_type")) { this.currentResourceType = new ResourceTypeInfo(); if (attrs.getIndex("id") != -1) { this.currentResourceType.setIdentifier(attrs .getValue("id")); } if (getState() == State.APPLICATION) { this.currentApplication.getResourceTypes().add( this.currentResourceType); } pushState(State.RESOURCETYPE); } else if (localName.equals("response")) { this.currentResponse = new ResponseInfo(); if (attrs.getIndex("status") != -1) { final String[] statuses = attrs.getValue("status") .split(" "); for (final String string : statuses) { this.currentResponse.getStatuses().add( Status.valueOf(Integer.parseInt(string))); } } if (getState() == State.METHOD) { this.currentMethod.getResponses().add( this.currentResponse); } pushState(State.RESPONSE); } } else { if (getState() == State.DOCUMENTATION) { // We are handling a new element pushMixedContentState(MixedContentState.ELEMENT); Node node = null; if (("".equals(qName) || qName == null)) { node = this.mixedContentDocument.createElementNS(uri, localName); } else { node = this.mixedContentDocument.createElementNS(uri, qName); } for (int i = 0; i < attrs.getLength(); i++) { Attr attr = this.mixedContentDocument .createAttributeNS(attrs.getURI(i), attrs.getLocalName(i)); attr.setNodeValue(attrs.getValue(i)); node.getAttributes().setNamedItemNS(attr); } // This element becomes the current one and is added to its // parent. this.currentMixedContentNode.appendChild(node); this.currentMixedContentNode = node; } } } /** * Receive notification of the beginning of an entity. * * @param name * The name of the entity. */ public void startEntity(String name) throws SAXException { pushMixedContentState(MixedContentState.ENTITY); this.currentMixedContentNode.appendChild(mixedContentDocument .createEntityReference(name)); } /** * Receive notification of the beginning of a prefix-URI Namespace * mapping. * * @param name * The name of the entity. */ @Override public void startPrefixMapping(String arg0, String arg1) throws SAXException { this.namespaces.put(arg1, arg0); } } /** Web Application Description Language namespace. */ public static final String APP_NAMESPACE = "http://wadl.dev.java.net/2009/02"; /** The root element of the WADL document. */ private ApplicationInfo application; /** * Constructor. */ public WadlRepresentation() { super(MediaType.APPLICATION_WADL); setNamespaceAware(true); } /** * Constructor. * * @param application * The root element of the WADL document. */ public WadlRepresentation(ApplicationInfo application) { super(MediaType.APPLICATION_WADL); setNamespaceAware(true); this.application = application; } /** * Constructor. * * @param xmlRepresentation * The XML WADL document. * @throws IOException */ public WadlRepresentation(Representation xmlRepresentation) throws IOException { super(xmlRepresentation); setNamespaceAware(true); setMediaType(MediaType.APPLICATION_WADL); // Parse the given document using SAX to produce an ApplicationInfo // instance. parse(new ContentReader(this)); } /** * Constructor. The title of the resource, that is to say the title of its * first documentation tag is transfered to the title of the first * documentation tag of the main application tag. * * @param resource * The root element of the WADL document. */ public WadlRepresentation(ResourceInfo resource) { super(MediaType.APPLICATION_WADL); setNamespaceAware(true); this.application = resource.createApplication(); } /** * Returns the root element of the WADL document. * * @return The root element of the WADL document. */ public ApplicationInfo getApplication() { return this.application; } /** * Returns an HTML representation. Note that the internal XSLT stylesheet * used comes from <a href="http://www.mnot.net/webdesc/">Mark * Nottingham</a>. This stylesheet requires advanced XSLT features, * including EXSLT extensions. Usage of a recent version of Xalan-J is * suggested. It has been tested successfully with Xalan-J 2.7.1. * * @return An HTML representation. */ public Representation getHtmlRepresentation() { Representation representation = null; URL wadl2htmlXsltUrl = Engine .getResource("org/restlet/ext/wadl/wadl2html.xslt"); if (wadl2htmlXsltUrl != null) { try { // The SAX source is systematically generated: // - when instantiated using an ApplicationInfo or a // ResourceInfo the sax source is null. // - when instantiated using an XML representation, the // underlying sax source is exhausted, because we parse it in // order to recover the WADL document. setSaxSource(XmlRepresentation.getSaxSource(this)); InputRepresentation xslRep = new InputRepresentation( wadl2htmlXsltUrl.openStream(), MediaType.APPLICATION_W3C_XSLT); representation = new TransformRepresentation( Context.getCurrent(), this, xslRep); representation.setMediaType(MediaType.TEXT_HTML); } catch (IOException e) { Context.getCurrent() .getLogger() .log(Level.WARNING, "Unable to generate the WADL HTML representation", e); } } return representation; } /** * Sets the root element of the WADL document. * * @param application * The root element of the WADL document. */ public void setApplication(ApplicationInfo application) { this.application = application; } @Override public void write(XmlWriter writer) throws IOException { try { writer.forceNSDecl(APP_NAMESPACE, ""); writer.setDataFormat(true); writer.setIndentStep(3); writer.processingInstruction("xml", "version=\"1.0\" standalone=\"yes\""); writer.processingInstruction("xml-stylesheet", "type=\"text/xsl\" href=\"wadl2html.xslt\""); this.application.writeElement(writer); writer.endDocument(); } catch (SAXException e) { Context.getCurrentLogger().log(Level.SEVERE, "Error when writing the WADL Representation.", e); } } }