diff --git a/src/zutil/ClassUtil.java b/src/zutil/ClassUtil.java index 46cde80..c45524c 100755 --- a/src/zutil/ClassUtil.java +++ b/src/zutil/ClassUtil.java @@ -183,4 +183,15 @@ public class ClassUtil { } return null; } + + /** + * @param c a array class + * @return the base class the array is based on, if the input is not an array then the input is returned. + */ + public static Class getArrayClass(Class c) { + if (c != null && c.isArray()) { + return getArrayClass(c.getComponentType()); + } + return c; + } } diff --git a/src/zutil/net/upnp/service/BrowseRetObj.java b/src/zutil/net/upnp/service/BrowseRetObj.java index 47123ae..c20d1f5 100755 --- a/src/zutil/net/upnp/service/BrowseRetObj.java +++ b/src/zutil/net/upnp/service/BrowseRetObj.java @@ -24,16 +24,17 @@ package zutil.net.upnp.service; +import zutil.net.ws.WSInterface.WSParamName; import zutil.net.ws.WSReturnObject; public class BrowseRetObj extends WSReturnObject{ - @WSValueName("Result") + @WSParamName("Result") public String Result; - @WSValueName("NumberReturned") + @WSParamName("NumberReturned") public int NumberReturned; - @WSValueName("TotalMatches") + @WSParamName("TotalMatches") public int TotalMatches; - @WSValueName("UpdateID") + @WSParamName("UpdateID") public int UpdateID; } diff --git a/src/zutil/net/upnp/service/UPnPContentDirectory.java b/src/zutil/net/upnp/service/UPnPContentDirectory.java index fddcce7..da45890 100755 --- a/src/zutil/net/upnp/service/UPnPContentDirectory.java +++ b/src/zutil/net/upnp/service/UPnPContentDirectory.java @@ -141,13 +141,13 @@ public class UPnPContentDirectory implements UPnPService, HttpPage, WSInterface return ret; } public class BrowseRetObj extends WSReturnObject{ - @WSValueName("Result") + @WSParamName("Result") public String Result; - @WSValueName("NumberReturned") + @WSParamName("NumberReturned") public int NumberReturned; - @WSValueName("TotalMatches") + @WSParamName("TotalMatches") public int TotalMatches; - @WSValueName("UpdateID") + @WSParamName("UpdateID") public int UpdateID; } diff --git a/src/zutil/net/ws/WSInterface.java b/src/zutil/net/ws/WSInterface.java index 5ba4177..beba110 100755 --- a/src/zutil/net/ws/WSInterface.java +++ b/src/zutil/net/ws/WSInterface.java @@ -67,10 +67,11 @@ import java.lang.annotation.Target; public interface WSInterface { enum RequestType { - HTTP_GET, - HTTP_POST, - HTTP_PUT, - HTTP_DELETE + GET, + POST, + PUT, + DELETE, + PATCH } @@ -79,7 +80,7 @@ public interface WSInterface { * in an method. */ @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.PARAMETER) + @Target({ElementType.PARAMETER, ElementType.FIELD}) @interface WSParamName { String value(); boolean optional() default false; diff --git a/src/zutil/net/ws/WSMethodDef.java b/src/zutil/net/ws/WSMethodDef.java index 9b292ed..6518b39 100755 --- a/src/zutil/net/ws/WSMethodDef.java +++ b/src/zutil/net/ws/WSMethodDef.java @@ -147,13 +147,17 @@ public class WSMethodDef { for (Field field : fields) { WSParameterDef ret_param = new WSParameterDef(this); - WSReturnObject.WSValueName retValName = field.getAnnotation(WSReturnObject.WSValueName.class); - if (retValName != null) - ret_param.setName(retValName.value()); + WSInterface.WSParamName paramNameAnnotation = field.getAnnotation(WSInterface.WSParamName.class); + if (paramNameAnnotation != null) + ret_param.setName(paramNameAnnotation.value()); else ret_param.setName(field.getName()); + WSInterface.WSDocumentation documentationAnnotation = field.getAnnotation(WSInterface.WSDocumentation.class); + if (documentationAnnotation != null) + ret_param.setDocumentation(documentationAnnotation.value()); + ret_param.setParamClass(field.getType()); outputs.add(ret_param); } @@ -178,13 +182,15 @@ public class WSMethodDef { // Specific request type was not provided, try to figure it out by the method name if (name.startsWith("get")) - this.requestType = WSInterface.RequestType.HTTP_GET; + this.requestType = WSInterface.RequestType.GET; if (name.startsWith("post")) - this.requestType = WSInterface.RequestType.HTTP_POST; + this.requestType = WSInterface.RequestType.POST; if (name.startsWith("put")) - this.requestType = WSInterface.RequestType.HTTP_PUT; + this.requestType = WSInterface.RequestType.PUT; if (name.startsWith("delete")) - this.requestType = WSInterface.RequestType.HTTP_DELETE; + this.requestType = WSInterface.RequestType.DELETE; + if (name.startsWith("patch")) + this.requestType = WSInterface.RequestType.PATCH; } // Handle endpoint path diff --git a/src/zutil/net/ws/WSParameterDef.java b/src/zutil/net/ws/WSParameterDef.java index a6ae3be..93edee5 100644 --- a/src/zutil/net/ws/WSParameterDef.java +++ b/src/zutil/net/ws/WSParameterDef.java @@ -26,10 +26,10 @@ package zutil.net.ws; /** * This is a web service parameter definition class - * + * * @author Ziver */ -public class WSParameterDef{ +public class WSParameterDef { /** The parent method **/ private WSMethodDef mDef; /** The class type of the parameter **/ @@ -37,13 +37,12 @@ public class WSParameterDef{ /** The web service name of the parameter **/ private String name; /** Developer documentation **/ - private String doc; + private String documentation; /** If this parameter is optional **/ private boolean optional; - /** Is it an header parameter **/ - //boolean header; - protected WSParameterDef( WSMethodDef mDef ){ + + protected WSParameterDef(WSMethodDef mDef){ this.mDef = mDef; this.optional = false; } @@ -63,11 +62,11 @@ public class WSParameterDef{ this.name = name; } - public String getDoc() { - return doc; + public String getDocumentation() { + return documentation; } - protected void setDoc(String doc) { - this.doc = doc; + protected void setDocumentation(String documentation) { + this.documentation = documentation; } public boolean isOptional() { diff --git a/src/zutil/net/ws/WSReturnObject.java b/src/zutil/net/ws/WSReturnObject.java index e6e94a1..69eda22 100644 --- a/src/zutil/net/ws/WSReturnObject.java +++ b/src/zutil/net/ws/WSReturnObject.java @@ -32,15 +32,16 @@ import java.lang.reflect.Field; /** * This class is used as an return Object for a web service. - * If an class implements this interface then it can return - * multiple values through the WSInterface. And the - * implementing class will be transparent. Example: - * + * If a class implements this interface then it implies that multiple + * parameters can be returned through the WSInterface. And the + * implementing class will be transparent to the requester. Example: + * *
  * 	private static class TestObject implements WSReturnObject{
- *		@WSValueName("name")
+ *		@WSParamName("name")
  *		public String name;
- *		@WSValueName("lastname")
+ *		@WSParamName("lastname")
+ *	    @WSDocumentation("The users last name")
  *		public String lastname;
  *
  *		public TestObject(String n, String l){
@@ -49,35 +50,11 @@ import java.lang.reflect.Field;
  *		}
  *	}
  * 
- * + * * @author Ziver * */ -public class WSReturnObject{ - /** - * Method comments for the WSDL. - * These comments are put in the operation part of the WSDL - * - * @author Ziver - */ - @Retention(RetentionPolicy.RUNTIME) - public @interface WSDLDocumentation{ - String value(); - } - - /** - * Annotation that assigns a name to the return value - * to the field. - * - * @author Ziver - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - public @interface WSValueName { - String value(); - boolean optional() default false; - } - +public abstract class WSReturnObject{ public Object getValue(Field field) throws IllegalArgumentException, IllegalAccessException{ return field.get(this); diff --git a/src/zutil/net/ws/WebServiceDef.java b/src/zutil/net/ws/WebServiceDef.java index 767ac2c..9d1baeb 100755 --- a/src/zutil/net/ws/WebServiceDef.java +++ b/src/zutil/net/ws/WebServiceDef.java @@ -36,26 +36,33 @@ import java.util.Set; * @author Ziver */ public class WebServiceDef { - /** A map of methods in this Service **/ - private HashMap methods; + /** This is the WSInterface class **/ + private Class intf; /** Namespace of the service **/ private String namespace; /** Name of the web service **/ private String name; - /** This is the WSInterface class **/ - private Class intf; + /** Human readable description of the service **/ + private String documentation = ""; + /** A map of methods in this Service **/ + private HashMap methods = new HashMap<>(); + public WebServiceDef(Class intf){ this.intf = intf; - methods = new HashMap<>(); name = intf.getSimpleName(); - if (intf.getAnnotation( WSInterface.WSNamespace.class) != null) - this.namespace = intf.getAnnotation(WSInterface.WSNamespace.class).value(); + WSInterface.WSNamespace namespaceAnnotation = intf.getAnnotation(WSInterface.WSNamespace.class); + if (namespaceAnnotation != null) + this.namespace = namespaceAnnotation.value(); + + WSInterface.WSDocumentation documentationAnnotation = intf.getAnnotation(WSInterface.WSDocumentation.class); + if (documentationAnnotation != null) + this.documentation = documentationAnnotation.value(); for(Method m : intf.getDeclaredMethods()){ - // check for public methods + // Check for public methods if ((m.getModifiers() & Modifier.PUBLIC) > 0 && !m.isAnnotationPresent(WSInterface.WSIgnore.class)){ WSMethodDef method = new WSMethodDef(this, m); @@ -78,6 +85,13 @@ public class WebServiceDef { return name; } + /** + * @return a human readable description of the service, or a empty String if no documentation has been provided. + */ + public String getDocumentation(){ + return documentation; + } + /** * @param name is the name of the method * @return if there is a method by the given name diff --git a/src/zutil/net/ws/openapi/OpenAPIHttpPage.java b/src/zutil/net/ws/openapi/OpenAPIHttpPage.java new file mode 100644 index 0000000..83a6a5c --- /dev/null +++ b/src/zutil/net/ws/openapi/OpenAPIHttpPage.java @@ -0,0 +1,90 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ziver Koc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package zutil.net.ws.openapi; + +import zutil.net.http.HttpHeader; +import zutil.net.http.HttpPage; +import zutil.net.http.HttpPrintStream; +import zutil.net.ws.WebServiceDef; +import zutil.net.ws.wsdl.WSDLWriter; + +import java.io.IOException; +import java.util.Map; + +/** + * User: Ziver + */ +public class OpenAPIHttpPage implements HttpPage { + /** + * The WSDL document + **/ + private WSDLWriter wsdl; + + + public OpenAPIHttpPage(WebServiceDef wsDef) { + wsdl = new WSDLWriter(wsDef); + } + + + public void respond(HttpPrintStream out, + HttpHeader headers, + Map session, + Map cookie, + Map request) throws IOException { + + if (request.containsKey("json")) { + out.setHeader(HttpHeader.HEADER_CONTENT_TYPE, "application/json"); + wsdl.write(out); + } else { + // Output human readable interface + + out.println(""); + out.println(""); + out.println(""); + out.println(" OpenAPI Documentation"); + out.println(); + out.println(" "); + out.println(" "); + out.println(); + out.println(" "); + out.println(""); + out.println(""); + out.println("
"); + out.println(""); + out.println(""); + } + } +} diff --git a/src/zutil/net/ws/openapi/OpenAPIWriter.java b/src/zutil/net/ws/openapi/OpenAPIWriter.java new file mode 100644 index 0000000..1302866 --- /dev/null +++ b/src/zutil/net/ws/openapi/OpenAPIWriter.java @@ -0,0 +1,164 @@ +package zutil.net.ws.openapi; + +import zutil.log.LogUtil; +import zutil.net.ws.WSMethodDef; +import zutil.net.ws.WSParameterDef; +import zutil.net.ws.WebServiceDef; +import zutil.parser.DataNode; +import zutil.parser.json.JSONWriter; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +/** + * A OpenAPI specification generator class. + * + * @see OpenAPI Specification + */ +public class OpenAPIWriter { + private static final Logger logger = LogUtil.getLogger(); + + private static final String OPENAPI_VERSION = "3.0.1"; + + /** Current Web service definition **/ + private WebServiceDef ws; + /** Current Web service definition **/ + private List servers = new ArrayList<>(); + /** Cache of generated WSDL **/ + private String cache; + + + public OpenAPIWriter(WebServiceDef ws) { + this.ws = ws; + } + + + public void addServer(String url, String description){ + servers.add(new ServerData(url, description)); + cache = null; + } + + + public void write(Writer out) throws IOException { + out.write(write()); + } + + public void write(PrintStream out) { + out.print(write()); + } + + public void write(OutputStream out) throws IOException { + out.write(write().getBytes()); + } + + public String write() { + if (cache == null) { + DataNode root = new DataNode(DataNode.DataType.Map); + root.set("openapi", OPENAPI_VERSION); + root.set("info", generateInfo()); + root.set("servers", generateServers()); + root.set("paths", generatePaths()); + root.set("components", generateComponents()); + + this.cache = JSONWriter.toString(root); + } + return cache; + } + + private DataNode generateInfo() { + DataNode infoRoot = new DataNode(DataNode.DataType.Map); + infoRoot.set("title", ws.getName()); + infoRoot.set("description", ws.getDocumentation()); + + // Not implemented properties + // "termsOfService": xxx, + // "contact": {"name": xxx,"url": xxx,"email": xxx}, + // "license": {"name": xxx, "url": xxx}, + // "version": xxx + return infoRoot; + } + + private DataNode generateServers() { + DataNode serversRoot = new DataNode(DataNode.DataType.List); + + for (ServerData data : servers) { + DataNode serverNode = new DataNode(DataNode.DataType.Map); + serverNode.set("url", data.url); + serverNode.set("description", data.description); + serversRoot.add(serverNode); + } + + return serversRoot; + } + + private DataNode generatePaths() { + DataNode pathsRoot = new DataNode(DataNode.DataType.Map); + + for (WSMethodDef methodDef : ws.getMethods()) { + DataNode pathNode = new DataNode(DataNode.DataType.Map); + + DataNode typeNode = new DataNode(DataNode.DataType.Map); + typeNode.set("description", methodDef.getDocumentation()); + pathNode.set(methodDef.getRequestType().toString().toLowerCase(), typeNode); + + // -------------------------------------------- + // Inputs + // -------------------------------------------- + + DataNode parameterNode = new DataNode(DataNode.DataType.Map); + for (WSParameterDef parameterDef : methodDef.getInputs()) { + parameterNode.set("name", parameterDef.getName()); + parameterNode.set("description", parameterDef.getDocumentation()); + parameterNode.set("in", "query"); + parameterNode.set("required", parameterDef.isOptional()); + + parameterNode.set("schema", ""); + } + typeNode.set("parameters", parameterNode); + + // -------------------------------------------- + // Outputs + // -------------------------------------------- + + DataNode responseNode = new DataNode(DataNode.DataType.Map); + for (WSParameterDef parameterDef : methodDef.getOutputs()) { + parameterNode.set("name", parameterDef.getName()); + parameterNode.set("description", parameterDef.getDocumentation()); + parameterNode.set("in", "query"); + parameterNode.set("required", parameterDef.isOptional()); + + parameterNode.set("schema", ""); + } + typeNode.set("responses", responseNode); + + } + + return pathsRoot; + } + + private DataNode generateComponents() { + DataNode componentsRoot = new DataNode(DataNode.DataType.Map); + DataNode schemasNode = new DataNode(DataNode.DataType.Map); + componentsRoot.set("schemas", schemasNode); + + // Generate schemas + + return componentsRoot; + } + + + protected static class ServerData { + String url; + String description; + + protected ServerData(String url, String description) { + this.url = url; + this.description = description; + } + } +} diff --git a/src/zutil/net/ws/rest/RESTClientInvocationHandler.java b/src/zutil/net/ws/rest/RESTClientInvocationHandler.java index 42b112f..bff9411 100644 --- a/src/zutil/net/ws/rest/RESTClientInvocationHandler.java +++ b/src/zutil/net/ws/rest/RESTClientInvocationHandler.java @@ -28,23 +28,17 @@ import zutil.io.IOUtil; import zutil.log.LogUtil; import zutil.net.http.HttpClient; import zutil.net.http.HttpHeader; -import zutil.net.http.HttpHeaderParser; import zutil.net.http.HttpURL; -import zutil.net.ws.WSInterface; import zutil.net.ws.WSMethodDef; import zutil.net.ws.WSParameterDef; import zutil.net.ws.WebServiceDef; -import zutil.net.ws.soap.SOAPHttpPage; import zutil.parser.DataNode; import zutil.parser.json.JSONParser; -import javax.naming.OperationNotSupportedException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.net.MalformedURLException; import java.net.URL; import java.util.List; -import java.util.logging.Level; import java.util.logging.Logger; /** @@ -80,10 +74,10 @@ public class RESTClientInvocationHandler implements InvocationHandler { String requestType = "GET"; switch (methodDef.getRequestType()) { - case HTTP_GET: requestType = "GET"; break; - case HTTP_PUT: requestType = "PUT"; break; - case HTTP_POST: requestType = "POST"; break; - case HTTP_DELETE: requestType = "DELETE"; break; + case GET: requestType = "GET"; break; + case PUT: requestType = "PUT"; break; + case POST: requestType = "POST"; break; + case DELETE: requestType = "DELETE"; break; } // Send request diff --git a/src/zutil/net/ws/soap/SOAPHttpPage.java b/src/zutil/net/ws/soap/SOAPHttpPage.java index 797427e..1cbc39d 100755 --- a/src/zutil/net/ws/soap/SOAPHttpPage.java +++ b/src/zutil/net/ws/soap/SOAPHttpPage.java @@ -31,13 +31,13 @@ import org.dom4j.Element; import org.dom4j.io.OutputFormat; import org.dom4j.io.XMLWriter; import org.xml.sax.SAXException; +import zutil.ClassUtil; import zutil.converter.Converter; import zutil.log.LogUtil; import zutil.net.http.HttpHeader; import zutil.net.http.HttpPage; import zutil.net.http.HttpPrintStream; import zutil.net.ws.*; -import zutil.net.ws.WSReturnObject.WSValueName; import zutil.parser.Base64Encoder; import java.io.BufferedReader; @@ -127,10 +127,12 @@ public class SOAPHttpPage implements HttpPage{ // Read http body StringBuilder data = null; String contentType = headers.getHeader("Content-Type"); + if (contentType != null && (contentType.contains("application/soap+xml") || contentType.contains("text/xml") || contentType.contains("text/plain"))) { + int post_data_length = Integer.parseInt(headers.getHeader("Content-Length")); BufferedReader in = new BufferedReader(new InputStreamReader(headers.getInputStream())); data = new StringBuilder(post_data_length); @@ -140,25 +142,24 @@ public class SOAPHttpPage implements HttpPage{ } // Response - out.setHeader("Content-Type", "text/xml"); + out.setHeader(HttpHeader.HEADER_CONTENT_TYPE, "text/xml"); out.flush(); WSInterface obj; if (session_enabled) { - if ( session.containsKey("SOAPInterface")) + if (session.containsKey("SOAPInterface")) obj = (WSInterface)session.get("SOAPInterface"); else { obj = wsDef.newInstance(); session.put("SOAPInterface", obj); } - } - else { + } else { if (ws == null) ws = wsDef.newInstance(); obj = ws; } - Document document = genSOAPResponse( (data!=null ? data.toString() : ""), obj); + Document document = genSOAPResponse((data!=null ? data.toString() : ""), obj); OutputFormat format = OutputFormat.createPrettyPrint(); XMLWriter writer = new XMLWriter( out, format ); @@ -187,11 +188,11 @@ public class SOAPHttpPage implements HttpPage{ public Document genSOAPResponse(String xml) { try { WSInterface obj; - if ( ws == null ) + if (ws == null) ws = wsDef.newInstance(); obj = ws; - return genSOAPResponse(xml, obj ); + return genSOAPResponse(xml, obj); } catch (Exception e) { logger.log(Level.WARNING, "Exception in SOAP generation", e); } @@ -290,12 +291,12 @@ public class SOAPHttpPage implements HttpPage{ // generate response XML if (outputParamDefs.size() > 0) { Element response = responseRoot.addElement(""); - response.addNamespace("m", methodDef.getNamespace() ); - response.setName("m:"+methodDef.getName()+"Response"); + response.addNamespace("m", methodDef.getNamespace() ); + response.setName("m:" + methodDef.getName() + "Response"); if (outputParams instanceof WSReturnObject) { Field[] f = outputParams.getClass().getFields(); - for(int i=0; i c) { - Class cTmp = getClass(c); + /** + * Will generate a SOAP based class name from a given class. + * + * @param c + * @return a String name that can be used by a SOAP call. + */ + public static String getSOAPClassName(Class c) { + Class cTmp = ClassUtil.getArrayClass(c); if (byte[].class.isAssignableFrom(c)) { return "base64Binary"; - } - else if (WSReturnObject.class.isAssignableFrom(cTmp)) { + } else if (WSReturnObject.class.isAssignableFrom(cTmp)) { return c.getSimpleName(); - } - else { + } else { String ret = c.getSimpleName().toLowerCase(); - if (cTmp == Integer.class) ret = ret.replaceAll("integer", "int"); - else if(cTmp == Character.class) ret = ret.replaceAll("character", "char"); + if (cTmp == Integer.class) + ret = ret.replaceAll("integer", "int"); + else if(cTmp == Character.class) + ret = ret.replaceAll("character", "char"); return ret; } } - - protected static Class getClass(Class c) { - if (c!=null && c.isArray()) { - return getClass(c.getComponentType()); - } - return c; - } } diff --git a/src/zutil/net/ws/wsdl/WSDLHttpPage.java b/src/zutil/net/ws/wsdl/WSDLHttpPage.java index 58a9529..fb2bad9 100644 --- a/src/zutil/net/ws/wsdl/WSDLHttpPage.java +++ b/src/zutil/net/ws/wsdl/WSDLHttpPage.java @@ -39,16 +39,19 @@ public class WSDLHttpPage implements HttpPage { /** The WSDL document **/ private WSDLWriter wsdl; - public WSDLHttpPage( WebServiceDef wsDef ){ - wsdl = new WSDLWriter( wsDef ); + + public WSDLHttpPage(WebServiceDef wsDef) { + wsdl = new WSDLWriter(wsDef); } + public void respond(HttpPrintStream out, HttpHeader headers, Map session, Map cookie, - Map request) throws IOException{ - out.setHeader("Content-Type", "text/xml"); - wsdl.write( out ); + Map request) throws IOException { + + out.setHeader(HttpHeader.HEADER_CONTENT_TYPE, "text/xml"); + wsdl.write(out); } } diff --git a/src/zutil/net/ws/wsdl/WSDLService.java b/src/zutil/net/ws/wsdl/WSDLService.java index 2892323..d2f56fe 100644 --- a/src/zutil/net/ws/wsdl/WSDLService.java +++ b/src/zutil/net/ws/wsdl/WSDLService.java @@ -36,10 +36,12 @@ public abstract class WSDLService { /** The URL of this service **/ private String url; + public WSDLService(String url){ this.url = url; } + public String getServiceAddress(){ return url; } diff --git a/src/zutil/net/ws/wsdl/WSDLServiceSOAP.java b/src/zutil/net/ws/wsdl/WSDLServiceSOAP.java index 920d2b7..b749c65 100644 --- a/src/zutil/net/ws/wsdl/WSDLServiceSOAP.java +++ b/src/zutil/net/ws/wsdl/WSDLServiceSOAP.java @@ -36,6 +36,7 @@ public class WSDLServiceSOAP extends WSDLService{ super(url); } + @Override public String getServiceType() { return "soap"; } @@ -57,7 +58,10 @@ public class WSDLServiceSOAP extends WSDLService{ Element soap_operation = operation.addElement("soap:operation"); soap_operation.addAttribute("soapAction", method.getNamespace()); - //*************************** Input + // ------------------------------------------------ + // Input + // ------------------------------------------------ + // definitions -> binding -> operation -> input Element input = operation.addElement("wsdl:input"); // definitions -> binding -> operation -> input -> body @@ -65,7 +69,10 @@ public class WSDLServiceSOAP extends WSDLService{ input_body.addAttribute("use", "literal"); input_body.addAttribute("namespace", method.getNamespace()); - //*************************** output + // ------------------------------------------------ + // Output + // ------------------------------------------------ + if(!method.getOutputs().isEmpty()){ // definitions -> binding -> operation -> output Element output = operation.addElement("wsdl:output"); diff --git a/src/zutil/net/ws/wsdl/WSDLWriter.java b/src/zutil/net/ws/wsdl/WSDLWriter.java index 2811b2c..51fd216 100644 --- a/src/zutil/net/ws/wsdl/WSDLWriter.java +++ b/src/zutil/net/ws/wsdl/WSDLWriter.java @@ -29,52 +29,57 @@ import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.io.OutputFormat; import org.dom4j.io.XMLWriter; +import zutil.ClassUtil; import zutil.io.StringOutputStream; -import zutil.net.ws.WSMethodDef; -import zutil.net.ws.WSParameterDef; -import zutil.net.ws.WSReturnObject; -import zutil.net.ws.WSReturnObject.WSValueName; -import zutil.net.ws.WebServiceDef; +import zutil.log.LogUtil; +import zutil.net.ws.*; +import zutil.net.ws.soap.SOAPHttpPage; import java.io.*; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; -public class WSDLWriter{ - /** Current Web service definition ***/ +public class WSDLWriter { + private static final Logger logger = LogUtil.getLogger(); + + /** Current Web service definition **/ private WebServiceDef ws; + /** A list of services **/ + private ArrayList services = new ArrayList<>(); /** Cache of generated WSDL **/ private String cache; - /** A list of services **/ - private ArrayList services; - public WSDLWriter( WebServiceDef ws ){ - this.services = new ArrayList<>(); + + public WSDLWriter(WebServiceDef ws) { this.ws = ws; } + /** * Add a service to be published with the WSDL */ - public void addService(WSDLService serv){ + public void addService(WSDLService serv) { cache = null; services.add(serv); } - public void write( Writer out ) throws IOException { - out.write(generate()); - } - public void write( PrintStream out ) { - out.print(generate()); - } - public void write( OutputStream out ) throws IOException { - out.write(generate().getBytes() ); + public void write(Writer out) throws IOException { + out.write(write()); } + public void write(PrintStream out) { + out.print(write()); + } - private String generate(){ - if(cache == null){ + public void write(OutputStream out) throws IOException { + out.write(write().getBytes()); + } + + public String write() { + if (cache == null) { try { OutputFormat outformat = OutputFormat.createPrettyPrint(); StringOutputStream out = new StringOutputStream(); @@ -87,13 +92,14 @@ public class WSDLWriter{ this.cache = out.toString(); out.close(); } catch (IOException e) { - e.printStackTrace(); + logger.log(Level.SEVERE, "Unable to generate WSDL specification.", e); } } return cache; } - private Document generateDefinition(){ + + private Document generateDefinition() { Document wsdl = DocumentHelper.createDocument(); Element definitions = wsdl.addElement("wsdl:definitions"); definitions.addNamespace("wsdl", "http://schemas.xmlsoap.org/wsdl/"); @@ -101,7 +107,7 @@ public class WSDLWriter{ definitions.addNamespace("http", "http://schemas.xmlsoap.org/wsdl/http/"); definitions.addNamespace("xsd", "http://www.w3.org/2001/XMLSchema"); definitions.addNamespace("soap-enc", "http://schemas.xmlsoap.org/soap/encoding/"); - definitions.addNamespace("tns", ws.getNamespace()+"?type"); + definitions.addNamespace("tns", ws.getNamespace() + "?type"); definitions.addAttribute("targetNamespace", ws.getNamespace()); generateType(definitions); @@ -113,12 +119,13 @@ public class WSDLWriter{ return wsdl; } - private void generateMessages(Element definitions){ - for( WSMethodDef method : ws.getMethods() ){ + private void generateMessages(Element definitions) { + for (WSMethodDef method : ws.getMethods()) { generateMessage(definitions, method); } // Default message used for functions without input parameters + // definitions -> message: empty Element empty = definitions.addElement("wsdl:message"); empty.addAttribute("name", "empty"); @@ -128,6 +135,7 @@ public class WSDLWriter{ empty_part.addAttribute("type", "td:empty"); // Exception message + // definitions -> message: exception Element exception = definitions.addElement("wsdl:message"); exception.addAttribute("name", "exception"); @@ -137,87 +145,98 @@ public class WSDLWriter{ exc_part.addAttribute("type", "td:string"); } - private void generateMessage(Element parent, WSMethodDef method){ - //*************************** Input - if(!method.getInputs().isEmpty()){ + private void generateMessage(Element parent, WSMethodDef method) { + // ------------------------------------------------ + // Input + // ------------------------------------------------ + + if (!method.getInputs().isEmpty()) { // definitions -> message Element input = parent.addElement("wsdl:message"); - input.addAttribute("name", method.getName()+"Request"); + input.addAttribute("name", method.getName() + "Request"); // Parameters - for( WSParameterDef param : method.getInputs() ){ + for (WSParameterDef param : method.getInputs()) { // definitions -> message -> part Element part = input.addElement("wsdl:part"); part.addAttribute("name", param.getName()); - part.addAttribute("type", "xsd:"+getClassName( param.getParamClass())); + part.addAttribute("type", "xsd:" + SOAPHttpPage.getSOAPClassName(param.getParamClass())); - if( param.isOptional() ) + if (param.isOptional()) part.addAttribute("minOccurs", "0"); } } - //*************************** Output - if(!method.getOutputs().isEmpty()){ + + // ------------------------------------------------ + // Output + // ------------------------------------------------ + + if (!method.getOutputs().isEmpty()) { // definitions -> message Element output = parent.addElement("wsdl:message"); - output.addAttribute("name", method.getName()+"Response"); + output.addAttribute("name", method.getName() + "Response"); // Parameters - for( WSParameterDef param : method.getOutputs() ){ + for (WSParameterDef param : method.getOutputs()) { // definitions -> message -> part Element part = output.addElement("wsdl:part"); part.addAttribute("name", param.getName()); Class paramClass = param.getParamClass(); - Class valueClass = getClass( paramClass ); + Class valueClass = ClassUtil.getArrayClass(paramClass); // is an binary array - if(byte[].class.isAssignableFrom( paramClass )){ + if (byte[].class.isAssignableFrom(paramClass)) { part.addAttribute("type", "xsd:base64Binary"); } // is an array? - else if( paramClass.isArray()){ - part.addAttribute("type", "td:" +getArrayClassName(paramClass)); - } - else if( WSReturnObject.class.isAssignableFrom(valueClass) ){ + else if (paramClass.isArray()) { + part.addAttribute("type", "td:" + getArrayClassName(paramClass)); + } else if (WSReturnObject.class.isAssignableFrom(valueClass)) { // its an SOAPObject - part.addAttribute("type", "td:"+getClassName( paramClass )); - } - else{// its an Object - part.addAttribute("type", "xsd:"+getClassName( paramClass )); + part.addAttribute("type", "td:" + SOAPHttpPage.getSOAPClassName(paramClass)); + } else {// its an Object + part.addAttribute("type", "xsd:" + SOAPHttpPage.getSOAPClassName(paramClass)); } } } } - private void generatePortType(Element definitions){ + private void generatePortType(Element definitions) { // definitions -> portType Element portType = definitions.addElement("wsdl:portType"); - portType.addAttribute("name", ws.getName()+"PortType"); + portType.addAttribute("name", ws.getName() + "PortType"); - for( WSMethodDef method : ws.getMethods() ){ + for (WSMethodDef method : ws.getMethods()) { // definitions -> portType -> operation Element operation = portType.addElement("wsdl:operation"); operation.addAttribute("name", method.getName()); // Documentation - if(method.getDocumentation() != null){ + + if (method.getDocumentation() != null) { Element doc = operation.addElement("wsdl:documentation"); doc.setText(method.getDocumentation()); } - //*************************** Input - if( method.getInputs().size() > 0 ){ + // Input + + if (method.getInputs().size() > 0) { // definitions -> message Element input = operation.addElement("wsdl:input"); - input.addAttribute("message", "tns:"+method.getName()+"Request"); + input.addAttribute("message", "tns:" + method.getName() + "Request"); } - //*************************** Output - if( method.getOutputs().size() > 0 ){ + + // Output + + if (method.getOutputs().size() > 0) { // definitions -> message Element output = operation.addElement("wsdl:output"); - output.addAttribute("message", "tns:"+method.getName()+"Response"); + output.addAttribute("message", "tns:" + method.getName() + "Response"); } - //*************************** Fault - if( method.getOutputs().size() > 0 ){ + + // Fault + + if (method.getOutputs().size() > 0) { // definitions -> message Element fault = operation.addElement("wsdl:fault"); fault.addAttribute("message", "tns:exception"); @@ -226,35 +245,35 @@ public class WSDLWriter{ } - private void generateBinding(Element definitions){ + private void generateBinding(Element definitions) { // definitions -> binding Element binding = definitions.addElement("wsdl:binding"); - binding.addAttribute("name", ws.getName()+"Binding"); - binding.addAttribute("type", "tns:"+ws.getName()+"PortType"); + binding.addAttribute("name", ws.getName() + "Binding"); + binding.addAttribute("type", "tns:" + ws.getName() + "PortType"); - for(WSDLService serv : services){ + for (WSDLService serv : services) { serv.generateBinding(binding); - for(WSMethodDef method : ws.getMethods()){ + for (WSMethodDef method : ws.getMethods()) { serv.generateOperation(binding, method); } } } - private void generateService(Element parent){ + private void generateService(Element parent) { // definitions -> service Element root = parent.addElement("wsdl:service"); - root.addAttribute("name", ws.getName()+"Service"); + root.addAttribute("name", ws.getName() + "Service"); // definitions -> service -> port Element port = root.addElement("wsdl:port"); - port.addAttribute("name", ws.getName()+"Port"); - port.addAttribute("binding", "tns:"+ws.getName()+"Binding"); + port.addAttribute("name", ws.getName() + "Port"); + port.addAttribute("binding", "tns:" + ws.getName() + "Binding"); - for(WSDLService serv : services){ + for (WSDLService serv : services) { // definitions -> service-> port -> address - Element address = port.addElement(serv.getServiceType()+":address"); + Element address = port.addElement(serv.getServiceType() + ":address"); address.addAttribute("location", serv.getServiceAddress()); } } @@ -266,19 +285,19 @@ public class WSDLWriter{ * -wsdl:type * */ - private void generateType(Element definitions){ + private void generateType(Element definitions) { ArrayList> types = new ArrayList<>(); // Find types - for( WSMethodDef method : ws.getMethods() ){ - if(!method.getOutputs().isEmpty()){ - for( WSParameterDef param : method.getOutputs() ){ + for (WSMethodDef method : ws.getMethods()) { + if (!method.getOutputs().isEmpty()) { + for (WSParameterDef param : method.getOutputs()) { Class paramClass = param.getParamClass(); - Class valueClass = getClass(paramClass); + Class valueClass = ClassUtil.getArrayClass(paramClass); // is an array? or special class - if( paramClass.isArray() || WSReturnObject.class.isAssignableFrom(valueClass)){ + if (paramClass.isArray() || WSReturnObject.class.isAssignableFrom(valueClass)) { // add to type generation list - if(!types.contains( paramClass )) - types.add( paramClass ); + if (!types.contains(paramClass)) + types.add(paramClass); } } } @@ -287,18 +306,17 @@ public class WSDLWriter{ // definitions -> types Element typeE = definitions.addElement("wsdl:types"); Element schema = typeE.addElement("xsd:schema"); - schema.addAttribute("targetNamespace", ws.getNamespace()+"?type"); + schema.addAttribute("targetNamespace", ws.getNamespace() + "?type"); // empty type Element empty = schema.addElement("xsd:complexType"); empty.addAttribute("name", "empty"); empty.addElement("xsd:sequence"); - for(int n=0; n c = types.get(n); + for (Class c : types) { // Generate Array type - if(c.isArray()){ - Class ctmp = getClass(c); + if (c.isArray()) { + Class ctmp = ClassUtil.getArrayClass(c); Element type = schema.addElement("xsd:complexType"); type.addAttribute("name", getArrayClassName(c)); @@ -310,83 +328,53 @@ public class WSDLWriter{ element.addAttribute("maxOccurs", "unbounded"); element.addAttribute("name", "element"); element.addAttribute("nillable", "true"); - if( WSReturnObject.class.isAssignableFrom(ctmp) ) - element.addAttribute("type", "tns:"+getClassName(c).replace("[]", "")); + if (WSReturnObject.class.isAssignableFrom(ctmp)) + element.addAttribute("type", "tns:" + SOAPHttpPage.getSOAPClassName(c).replace("[]", "")); else - element.addAttribute("type", "xsd:"+getClassName(c).replace("[]", "")); + element.addAttribute("type", "xsd:" + SOAPHttpPage.getSOAPClassName(c).replace("[]", "")); - if(!types.contains(ctmp)) + if (!types.contains(ctmp)) types.add(ctmp); } // Generate SOAPObject type - else if(WSReturnObject.class.isAssignableFrom(c)){ + else if (WSReturnObject.class.isAssignableFrom(c)) { Element type = schema.addElement("xsd:complexType"); - type.addAttribute("name", getClassName(c)); + type.addAttribute("name", SOAPHttpPage.getSOAPClassName(c)); Element sequence = type.addElement("xsd:sequence"); Field[] fields = c.getFields(); - for(int i=0; i cTmp = getClass(fields[i].getType()); - if( WSReturnObject.class.isAssignableFrom(cTmp) ){ - element.addAttribute("type", "tns:"+getClassName(cTmp)); - if(!types.contains(cTmp)) + Class cTmp = ClassUtil.getArrayClass(fields[i].getType()); + if (WSReturnObject.class.isAssignableFrom(cTmp)) { + element.addAttribute("type", "tns:" + SOAPHttpPage.getSOAPClassName(cTmp)); + if (!types.contains(cTmp)) types.add(cTmp); + } else { + element.addAttribute("type", "xsd:" + SOAPHttpPage.getSOAPClassName(fields[i].getType())); } - else{ - element.addAttribute("type", "xsd:"+getClassName(fields[i].getType())); - } + // Is the Field optional - if(tmp != null && tmp.optional()) + if (tmp != null && tmp.optional()) element.addAttribute("minOccurs", "0"); } } } } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // TODO: FIX THESE ARE DUPLICATES FROM SOAPHttpPage - /////////////////////////////////////////////////////////////////////////////////////////////// - - private Class getClass(Class c){ - if(c!=null && c.isArray()){ - return getClass(c.getComponentType()); - } - return c; + private String getArrayClassName(Class c) { + return "ArrayOf" + SOAPHttpPage.getSOAPClassName(c).replaceAll("[\\[\\]]", ""); } - - private String getArrayClassName(Class c){ - return "ArrayOf"+getClassName(c).replaceAll("[\\[\\]]", ""); - } - - private String getClassName(Class c){ - Class cTmp = getClass(c); - if( byte[].class.isAssignableFrom(c) ){ - return "base64Binary"; - } - else if( WSReturnObject.class.isAssignableFrom(cTmp) ){ - return c.getSimpleName(); - } - else{ - String ret = c.getSimpleName().toLowerCase(); - - if( cTmp == Integer.class ) ret = ret.replaceAll("integer", "int"); - else if( cTmp == Character.class ) ret = ret.replaceAll("character", "char"); - - return ret; - } - } - - public void close() {} - }