Moved from javasisst to Reflection Proxy

This commit is contained in:
Ziver Koc 2015-12-15 23:00:58 +01:00
parent ad970e3317
commit 431d238c19
10 changed files with 288 additions and 229 deletions

BIN
Zutil.jar

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,73 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2015 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;
import zutil.log.LogUtil;
import zutil.net.ws.soap.SOAPClientInvocationHandler;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This is an factory that generates clients for an unspecified web services
*
* @author Ziver
*/
public class WSClientFactory {
private static Logger logger = LogUtil.getLogger();
/**
* Generates a Client Object for the web service.
*
* @param <T> is the class of the web service definition
* @param intf is the class of the web service definition
* @return a client Object
*/
public static <T> T createClient(Class<T> intf, InvocationHandler handler){
if( !WSInterface.class.isAssignableFrom( intf )){
throw new ClassCastException("The Web Service class is not a subclass of WSInterface!");
}
try {
Class proxyClass = Proxy.getProxyClass(
WSClientFactory.class.getClassLoader(),
new Class[]{intf});
Constructor<T> constructor = proxyClass.getConstructor(
new Class[]{InvocationHandler.class});
T obj = constructor.newInstance(
new Object[]{handler});
return obj;
} catch (Exception e){
logger.log(Level.SEVERE, null, e);
}
return null;
}
}

35
src/zutil/net/ws/rest/RestHttpPage.java Normal file → Executable file
View file

@ -25,14 +25,15 @@
package zutil.net.ws.rest;
import zutil.converters.Converter;
import zutil.io.StringOutputStream;
import zutil.net.http.HttpHeaderParser;
import zutil.net.http.HttpPage;
import zutil.net.http.HttpPrintStream;
import zutil.net.ws.WSInterface;
import zutil.net.ws.WSMethodDef;
import zutil.net.ws.WSParameterDef;
import zutil.net.ws.WebServiceDef;
import zutil.net.ws.*;
import zutil.parser.json.JSONObjectOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Map;
/**
@ -57,13 +58,33 @@ public class RestHttpPage implements HttpPage {
HttpHeaderParser client_info,
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request) {
execute(request);
Map<String, String> request) throws IOException {
try {
out.println(
execute(client_info.getRequestURL(), request));
} catch (Throwable throwable) {
throw new IOException(throwable);
}
}
private void execute(Map<String, String> input){
private String execute(String targetMethod, Map<String, String> input) throws Throwable {
if( wsDef.hasMethod(targetMethod) ){
// Parse request
WSMethodDef m = wsDef.getMethod(targetMethod);
Object[] params = prepareInputParams(m, input);
// Invoke
Object ret = m.invoke(ws, params);
// Generate Response
StringOutputStream dummyOut = new StringOutputStream();
JSONObjectOutputStream out = new JSONObjectOutputStream(dummyOut);
out.writeObject(ret);
out.close();
return dummyOut.toString();
}
return "{error: \"Unknown target: "+targetMethod+"\"}";
}
private Object[] prepareInputParams(WSMethodDef method, Map<String, String> input){

View file

@ -1,47 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2015 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.soap;
import java.util.HashMap;
/**
* This is an abstract client that will do generic requests to a
* SOAP Web service
*
* @author Ziver
*/
public class SOAPAbstractClient {
/** Web address of the web service */
protected String url;
/**
* Makes a request to the target web service
* @return
*/
public static Object request(String methodName, HashMap<String,Object> input){
return null;
}
}

160
src/zutil/net/ws/soap/SOAPClientFactory.java Normal file → Executable file
View file

@ -24,144 +24,48 @@
package zutil.net.ws.soap;
import javassist.*;
import zutil.log.LogUtil;
import zutil.net.ws.WSClientFactory;
import zutil.net.ws.WSInterface;
import zutil.net.ws.WSMethodDef;
import zutil.net.ws.WSParameterDef;
import zutil.net.ws.WebServiceDef;
import java.util.HashMap;
import java.util.List;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This is an factory that generates clients for web services
*
*
* @author Ziver
*/
public class SOAPClientFactory {
private static Logger logger = LogUtil.getLogger();
/**
* Generates a Client Object for the web service.
*
* @param <T> is the class of the web service definition
* @param intf is the class of the web service definition
* @return a client Object
*/
@SuppressWarnings("unchecked")
public static <T> T getClient(Class<T> intf) throws InstantiationException, IllegalAccessException, CannotCompileException, NotFoundException{
if( !WSInterface.class.isAssignableFrom( intf )){
throw new ClassCastException("The Web Service class is not a subclass of WSInterface!");
}
return getClient( intf, new WebServiceDef((Class<? extends WSInterface>)intf) );
}
/**
* Generates a Client Object for the web service.
*
* @param <T> is the class of the web service definition
* @param intf is the class of the web service definition
* @param wsDef is the web service definition of the intf parameter
* @return a client Object
*/
public static <T> T getClient(Class<T> intf, WebServiceDef wsDef) throws InstantiationException, IllegalAccessException, CannotCompileException, NotFoundException{
if( !WSInterface.class.isAssignableFrom( intf )){
throw new ClassCastException("The Web Service class is not a subclass of WSInterface!");
}
// Generate the class
ClassPool pool = ClassPool.getDefault();
CtClass intfClass = pool.get( intf.getName() );
CtClass cc = pool.makeClass(intf.getName()+"Impl_"+ (int)(Math.random()*10000));
// Is intf an interface
if( intf.isInterface() ){
cc.addInterface( intfClass );
}
// or a class
else{
cc.setSuperclass( intfClass );
}
// Add the logger class
CtField logger = CtField.make(
"private static "+Logger.class.getName()+" logger = "+LogUtil.class.getName()+".getLogger();",
cc);
cc.addField(logger);
// Generate the methods
for(WSMethodDef methodDef : wsDef.getMethods()){
// Create method
CtMethod method = CtNewMethod.make(
getOutputClass(methodDef.getOutputs()), // Return type
methodDef.getName(), // Method name
getParameterClasses(methodDef.getInputs()), // Parameters
new CtClass[]{pool.get( SOAPException.class.getName() )}, // Exceptions
generateCodeBody(methodDef), // Code Body
cc); // Class
cc.addMethod(method);
}
// Initiate the class
@SuppressWarnings("unchecked")
Class<T> c = cc.toClass();
T obj = c.newInstance();
return obj;
}
/**
* Generates a generic method code body that calls the SOAPAbstractClient class
*/
private static String generateCodeBody(WSMethodDef m) {
logger.finer("Generating method "+m.getName()+"(...)");
StringBuilder body = new StringBuilder("{\n");
// Logging
body.append( "logger.fine(\"Executing method: "+m.getName()+"(...)\");\n" );
// Generate parameter list
body.append( HashMap.class.getName()+"<String,Object> params = new "+HashMap.class.getName()+"<String,Object>();\n" );
for(WSParameterDef param : m.getInputs()){
body.append( "params.put(\""+param.getName()+"\", "+param.getName()+");\n");
}
// Call SOAPAbstractClient class
if(m.getOutputCount() > 0) // non void function
body.append( "return " );
body.append( SOAPAbstractClient.class.getName()+".request(\""+m.getName()+"\", params);\n" );
body.append("}");
logger.finest("###################### BODY #########################");
logger.finest(body.toString());
logger.finest("#######################################################");
return body.toString();
}
private static Logger logger = LogUtil.getLogger();
/**
* Generates a Client Object for the web service.
*
* @param <T> is the class of the web service definition
* @param intf is the class of the web service definition
* @return a client Object
*/
@SuppressWarnings("unchecked")
public static <T> T createClient(Class<T> intf){
return createClient( intf, new WebServiceDef((Class<? extends WSInterface>)intf) );
}
/**
* Generates a Client Object for the web service.
*
* @param <T> is the class of the web service definition
* @param intf is the class of the web service definition
* @param wsDef is the web service definition of the intf parameter
* @return a client Object
*/
public static <T> T createClient(Class<T> intf, WebServiceDef wsDef){
T obj = WSClientFactory.createClient(intf, new SOAPClientInvocationHandler());
return obj;
}
private static CtClass getParameterClass(WSParameterDef param) throws NotFoundException{
return ClassPool.getDefault().get( param.getClass().getName() );
}
private static CtClass[] getParameterClasses(List<WSParameterDef> params) throws NotFoundException{
CtClass[] c = new CtClass[params.size()];
int i = 0;
for(WSParameterDef param : params){
c[i++] = getParameterClass(param);
}
return c;
}
private static CtClass getOutputClass(List<WSParameterDef> params) throws NotFoundException{
if(params.isEmpty()){
return CtClass.voidType;
}
else if(params.size() == 1){
return ClassPool.getDefault().get( params.get(0).getClass().getName() );
}
throw new IllegalArgumentException("Unknown return type");
}
}

View file

@ -0,0 +1,105 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2015 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.soap;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.xml.sax.SAXException;
import zutil.log.LogUtil;
import zutil.net.http.HttpClient;
import zutil.net.http.HttpHeaderParser;
import zutil.net.ws.WSInterface;
import zutil.net.ws.WSMethodDef;
import zutil.net.ws.WSParameterDef;
import zutil.net.ws.WebServiceDef;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This is an abstract client that will do generic requests to a
* SOAP Web service
*
* @author Ziver
*/
public class SOAPClientInvocationHandler implements InvocationHandler {
private static Logger logger = LogUtil.getLogger();
private WebServiceDef wsDef;
/** Web address of the web service */
protected String url;
/**
* Makes a request to the target web service
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
HttpClient request = HttpClient.POST();
HttpHeaderParser response = request.send();
return null;
}
private Document genSOAPRequest(WSInterface obj, String targetMethod, Object[] args){
Document document = DocumentHelper.createDocument();
Element envelope = document.addElement("soap:Envelope");
WSMethodDef methodDef = wsDef.getMethod( targetMethod );
try {
envelope.addNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
envelope.addNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
envelope.addNamespace("xsd", "http://www.w3.org/2001/XMLSchema");
Element body = envelope.addElement( "soap:Body" );
Element method = body.addElement("");
method.addNamespace("m", methodDef.getNamespace() );
method.setName("m:"+methodDef.getName()+"Request");
for(int i=0; i<methodDef.getOutputCount() ;i++){
WSParameterDef param = methodDef.getOutput( i );
SOAPHttpPage.generateSOAPXMLForObj(method, args[i] , param.getName());
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception in SOAP generation", e);
}
return document;
}
private Object parseSOAPResponse(String xml){
try {
Element response = SOAPHttpPage.getXMLRoot(xml);
// TODO:
} catch (DocumentException e) {
logger.log(Level.SEVERE, "Unable to parse SOAP response", e);
}
return null;
}
}

64
src/zutil/net/ws/soap/SOAPHttpPage.java Normal file → Executable file
View file

@ -228,7 +228,7 @@ public class SOAPHttpPage implements HttpPage{
* @param xml is the string XML
* @return the XML root Element
*/
private Element getXMLRoot(String xml) throws Exception {
protected static Element getXMLRoot(String xml) throws DocumentException {
if(xml != null && !xml.isEmpty()){
Document document = DocumentHelper.parseText(xml);
return document.getRootElement();
@ -275,73 +275,77 @@ public class SOAPHttpPage implements HttpPage{
Field[] f = ret.getClass().getFields();
for(int i=0; i<m.getOutputCount() ;i++){
WSParameterDef param = m.getOutput( i );
generateReturnXML(response,((WSReturnObject)ret).getValue(f[i]) , param.getName());
generateSOAPXMLForObj(response,((WSReturnObject)ret).getValue(f[i]) , param.getName());
}
}
else{
generateReturnXML(response, ret, m.getOutput(0).getName());
generateSOAPXMLForObj(response, ret, m.getOutput(0).getName());
}
}
}
else{
throw new Exception("No such method: "+e.getQName().getName()+"!");
throw new NoSuchMethodException("Unable to find method: "+e.getQName().getName()+"!");
}
}
}
/**
* Generates a return XML Element. This function can
* Generates a XML Element for a given Object. This method can
* handle return values as XML Elements, WSReturnObject and
* Java basic data types.
*
* @param root is the parent Element
* @param retObj is the object that is the return value
* @param ename is the name of the parent Element
* @param obj is the object that is the return value
* @param elementName is the name of the parent Element
*/
private void generateReturnXML(Element root, Object retObj, String ename) throws IllegalArgumentException, IllegalAccessException{
if(retObj == null) return;
if(byte[].class.isAssignableFrom(retObj.getClass())){
Element valueE = root.addElement( ename );
valueE.addAttribute("type", "xsd:"+ getSOAPClassName(retObj.getClass()));
String tmp = new sun.misc.BASE64Encoder().encode((byte[])retObj);
protected static void generateSOAPXMLForObj(Element root, Object obj, String elementName) throws IllegalArgumentException, IllegalAccessException{
if(obj == null) return;
if(byte[].class.isAssignableFrom(obj.getClass())){
Element valueE = root.addElement( elementName );
valueE.addAttribute("type", "xsd:"+ getSOAPClassName(obj.getClass()));
String tmp = new sun.misc.BASE64Encoder().encode((byte[])obj);
tmp = tmp.replaceAll("\\s", "");
valueE.setText(tmp);
}
// return an array
else if(retObj.getClass().isArray()){
Element array = root.addElement( (ename.equals("element") ? "Array" : ename) );
String arrayType = "xsd:"+ getSOAPClassName(retObj.getClass());
arrayType = arrayType.replaceFirst("\\[\\]", "["+Array.getLength(retObj)+"]");
else if(obj.getClass().isArray()){
Element array = root.addElement( (elementName.equals("element") ? "Array" : elementName) );
String arrayType = "xsd:"+ getSOAPClassName(obj.getClass());
arrayType = arrayType.replaceFirst("\\[\\]", "["+Array.getLength(obj)+"]");
array.addAttribute("type", "soap:Array");
array.addAttribute("soap:arrayType", arrayType);
for(int i=0; i<Array.getLength(retObj) ;i++){
generateReturnXML(array, Array.get(retObj, i), "element");
for(int i=0; i<Array.getLength(obj) ;i++){
generateSOAPXMLForObj(array, Array.get(obj, i), "element");
}
}
else{
Element objectE = root.addElement( ename );
if(retObj instanceof Element)
objectE.add( (Element)retObj );
else if(retObj instanceof WSReturnObject){
Field[] fields = retObj.getClass().getFields();
Element objectE = root.addElement( elementName );
if(obj instanceof Element)
objectE.add( (Element)obj );
else if(obj instanceof WSReturnObject){
Field[] fields = obj.getClass().getFields();
for(int i=0; i<fields.length ;i++){
WSValueName tmp = fields[i].getAnnotation( WSValueName.class );
String name;
if(tmp != null) name = tmp.value();
else name = "field"+i;
generateReturnXML(objectE, fields[i].get(retObj), name);
generateSOAPXMLForObj(objectE, fields[i].get(obj), name);
}
}
else {
objectE.addAttribute("type", "xsd:"+ getSOAPClassName(retObj.getClass()));
objectE.addText( ""+retObj );
objectE.addAttribute("type", "xsd:"+ getSOAPClassName(obj.getClass()));
objectE.addText( ""+obj );
}
}
}
public static String getSOAPClassName(Class<?> c){
protected static String getSOAPClassName(Class<?> c){
Class<?> cTmp = getClass(c);
if(byte[].class.isAssignableFrom(c)){
return "base64Binary";
@ -359,7 +363,7 @@ public class SOAPHttpPage implements HttpPage{
}
}
public static Class<?> getClass(Class<?> c){
protected static Class<?> getClass(Class<?> c){
if(c!=null && c.isArray()){
return getClass(c.getComponentType());
}

6
test/zutil/test/SOAPClientTest.java Normal file → Executable file
View file

@ -23,8 +23,6 @@
*/
package zutil.test;
import javassist.CannotCompileException;
import javassist.NotFoundException;
import zutil.log.CompactLogFormatter;
import zutil.log.LogUtil;
import zutil.net.ws.WSInterface;
@ -34,11 +32,11 @@ import java.util.logging.Level;
public class SOAPClientTest {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, CannotCompileException, NotFoundException{
public static void main(String[] args) throws InstantiationException, IllegalAccessException{
LogUtil.setGlobalLevel(Level.ALL);
LogUtil.setFormatter("", new CompactLogFormatter());
TestClient intf = SOAPClientFactory.getClient(TestClient.class);
TestClient intf = SOAPClientFactory.createClient(TestClient.class);
intf.m();
intf.c();
}

27
test/zutil/test/SOAPTest.java Normal file → Executable file
View file

@ -26,6 +26,7 @@ package zutil.test;
import org.dom4j.Document;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import zutil.net.nio.message.type.SystemMessage;
import zutil.net.ws.WSInterface;
import zutil.net.ws.WSInterface.WSNamespace;
import zutil.net.ws.WSReturnObject;
@ -43,30 +44,30 @@ public class SOAPTest {
public SOAPTest(){
WebServiceDef wsDef = new WebServiceDef( MainSOAPClass.class );
SOAPHttpPage soap = new SOAPHttpPage( wsDef );
System.out.println( "****************** WSDL *********************" );
WSDLWriter wsdl = new WSDLWriter( wsDef );
wsdl.write(System.out);
System.out.println( "****************** new *********************" );
WSDLWriter wsdl2 = new WSDLWriter( wsDef );
wsdl2.write(System.out);
// Response
try {
System.out.println( "****************** LOG *********************" );
Document document = soap.genSOAPResponse(
"<?xml version=\"1.0\"?>" +
System.out.println( "\n****************** REQUEST *********************" );
String request = "<?xml version=\"1.0\"?>\n" +
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" +
" <soap:Body xmlns:m=\"http://www.example.org/stock\">\n" +
" <m:stringArrayMethod>\n" +
" <m:StringName>IBM</m:StringName>\n" +
" </m:stringArrayMethod>\n" +
" <m:simpleReturnClassMethod>\n" +
" <m:byte>IBM</m:byte>\n" +
" </m:simpleReturnClassMethod>\n" +
" </soap:Body>\n" +
"</soap:Envelope>");
System.out.println( "****************** RESPONSE *********************" );
"</soap:Envelope>";
System.out.println(request);
System.out.println( "\n****************** EXECUTION *********************" );
Document document = soap.genSOAPResponse(request);
System.out.println( "\n****************** RESPONSE *********************" );
OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter writer = new XMLWriter( System.out, format );
@ -112,7 +113,7 @@ public class SOAPTest {
public void exceptionMethod(
@WSParamName(value="otherParam1", optional=true) int param1,
@WSParamName(value="otherParam2", optional=true) int param2) throws Exception{
System.out.println("Executing method: exceptionMethod()");
System.out.println("Executing method: exceptionMethod(int param1="+param1+", int param2="+param2+",)");
throw new Exception("This is an Exception");
}
@ -120,7 +121,7 @@ public class SOAPTest {
@WSParamDocumentation("Documentation of stringArrayMethod()")
public String[][] stringArrayMethod (
@WSParamName("StringName") String str) throws Exception{
System.out.println("Executing method: stringArrayMethod()");
System.out.println("Executing method: stringArrayMethod(String str='"+str+"')");
return new String[][]{{"test","test2"},{"test3","test4"}};
}
@ -128,7 +129,7 @@ public class SOAPTest {
@WSParamDocumentation("Documentation of specialReturnMethod()")
public SpecialReturnClass[] specialReturnMethod (
@WSParamName("StringName2") String str) throws Exception{
System.out.println("Executing method: specialReturnMethod()");
System.out.println("Executing method: specialReturnMethod(String str='"+str+"')");
return new SpecialReturnClass[]{new SpecialReturnClass(), new SpecialReturnClass()};
}