/*
* JBoss, Home of Professional Open Source
* Copyright 2008-11, Red Hat Middleware LLC, and others contributors as indicated
* by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.savara.java.generator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import org.savara.common.model.annotation.Annotation;
import org.savara.common.model.annotation.AnnotationDefinitions;
import org.savara.common.resources.ResourceLocator;
import org.savara.java.generator.util.JavaGeneratorUtil;
import org.savara.protocol.model.util.ChoiceUtil;
import org.scribble.protocol.model.Activity;
import org.scribble.protocol.model.Block;
import org.scribble.protocol.model.Choice;
import org.scribble.protocol.model.Interaction;
import org.scribble.protocol.model.MessageSignature;
import org.scribble.protocol.model.ModelObject;
import org.scribble.protocol.model.ProtocolModel;
import org.scribble.protocol.model.Role;
import org.scribble.protocol.model.TypeImport;
import org.scribble.protocol.model.TypeReference;
import org.scribble.protocol.util.InteractionUtil;
import org.scribble.protocol.util.TypesUtil;
/**
* This class provides capabilities to generate stateless behaviour
* as Java code.
*
*/
public class JavaBehaviourGenerator {
private static final Logger logger=Logger.getLogger(JavaBehaviourGenerator.class.getName());
private ProtocolModel _behaviour=null;
private ResourceLocator _locator=null;
private java.util.List<String> _importedTypes=new java.util.Vector<String>();
/**
* This is the constructor for the Java benaviour generator.
*
* @param behaviour The stateless endpoint behaviour
* @param locator The optional resource locator
*/
public JavaBehaviourGenerator(ProtocolModel behaviour,
ResourceLocator locator) {
_behaviour = behaviour;
_locator = locator;
}
/**
* This method derives the Java type to use, and if appropriate
* includes the type in the import list, and then returns
* just the local type.
*
* @param type The fully qualified Java type
* @return The Java type, local if imported, or fully qualified
*/
protected String getImportedType(String type) {
String ret=type;
// Check if in import list
if (ret.indexOf('.') != -1 && !_importedTypes.contains(ret)) {
_importedTypes.add(ret);
}
return(getLocalJavaType(ret));
}
/**
* This method derives the Java type to use, and if appropriate
* includes the type in the import list, and then returns
* just the local type.
*
* @param interaction The interaction
* @param locator The optional resource locator
* @return The Java type, local if imported, or fully qualified
*/
protected String getImportedType(Interaction interaction, ResourceLocator locator) {
String ret=getJavaType(interaction, locator);
// Check if in import list
if (ret.indexOf('.') != -1 && !_importedTypes.contains(ret)) {
_importedTypes.add(ret);
}
return(getLocalJavaType(ret));
}
protected static String getJavaType(Interaction interaction, ResourceLocator locator) {
String ret=null;
TypeReference tref=interaction.getMessageSignature().getTypeReferences().get(0);
TypeImport ti=TypesUtil.getTypeImport(tref);
if (ti != null && ti.getDataType() != null) {
QName type=QName.valueOf(ti.getDataType().getDetails());
ret = JavaGeneratorUtil.getJavaPackage(type.getNamespaceURI());
if (org.savara.protocol.model.util.InteractionUtil.isFaultResponse(interaction)) {
ret = JavaGeneratorUtil.getJavaClassName(
org.savara.protocol.model.util.InteractionUtil.getFaultName(interaction))+"Fault";
if (!org.savara.protocol.model.util.InteractionUtil.isSend(interaction)) {
Annotation ann=AnnotationDefinitions.getAnnotationWithProperty(
interaction.getEnclosingProtocol().getAnnotations(),
AnnotationDefinitions.INTERFACE, AnnotationDefinitions.ROLE_PROPERTY,
interaction.getFromRole().getName());
if (ann != null) {
String ns=(String)ann.getProperties().get(AnnotationDefinitions.NAMESPACE_PROPERTY);
ret = JavaGeneratorUtil.getJavaPackage(ns)+"."+ret;
}
}
} else {
ret += "."+JavaGeneratorUtil.getJavaClassName(type.getLocalPart());
/* Since providing binding file to XJC, it now uses Element java classes
* (related to SAVARA-379)
*
// Check if XSD element - and if so find the XSD type
if (locator != null && ti.getParent() instanceof TypeImportList &&
((TypeImportList)ti.getParent()).getLocation() != null) {
String location=((TypeImportList)ti.getParent()).getLocation();
ret = JavaGeneratorUtil.getElementJavaType(type, location, locator);
}
*/
}
}
return(ret);
}
protected static String getLocalJavaType(String qualifiedType) {
String ret=qualifiedType;
int ind=qualifiedType.lastIndexOf('.');
if (ind != -1) {
ret = qualifiedType.substring(ind+1);
}
return(ret);
}
public static String getVariableName(String name) {
return(Character.toLowerCase(name.charAt(0))+name.substring(1));
}
public String getOperationBody(String opName, String returnType,
String parameterType, String parameterName,
java.util.Map<Role,String> roleMapping) {
String ret=null;
Block block=getStatelessBehaviourForOperation(opName);
if (block != null) {
if (logger.isLoggable(Level.FINER)) {
logger.finer("Behaviour for operation="+opName+" is: "+block);
}
StringBuffer code=new StringBuffer();
int indent=2;
java.util.Map<String,String> typeVarMap=new java.util.HashMap<String,String>();
typeVarMap.put(getImportedType(parameterType), parameterName);
if (!returnType.equals("void")) {
newline(code, indent);
String localType=getImportedType(returnType);
code.append(localType+" ret=null;\r\n");
typeVarMap.put(localType, "ret");
}
processBlock(block, code, indent, roleMapping, typeVarMap, _locator);
if (!returnType.equals("void")) {
newline(code, indent);
code.append("return (ret);");
}
ret = code.toString();
}
return(ret);
}
/**
* This method returns the import statements derived from
* processing the operation bodies.
*
* @return The import statements
*/
public String getImportStatements() {
StringBuffer code=new StringBuffer();
for (String type : _importedTypes) {
code.append("import "+type+";\r\n");
}
return(code.toString());
}
protected Block getStatelessBehaviourForOperation(String opName) {
Block ret=null;
if (_behaviour.getProtocol().getBlock().size() == 1 &&
_behaviour.getProtocol().getBlock().get(0) instanceof Choice) {
for (Block b : ((Choice)_behaviour.getProtocol().getBlock().get(0)).getPaths()) {
// Get first interaction, and check whether it is for
// the supplied operation name
java.util.List<ModelObject> interactions=InteractionUtil.getInitialInteractions(b);
if (interactions != null) {
if (interactions.size() == 1) {
MessageSignature msig=InteractionUtil.getMessageSignature(interactions.get(0));
if (msig.getOperation() != null && msig.getOperation().equals(opName)) {
ret = b;
break;
}
} else {
logger.severe("Stateless behaviour shouldn't have more " +
"than one initial interaction for path: "+b);
}
}
}
} else {
// Assume only a single path
// Get first interaction, and check whether it is for
// the supplied operation name
java.util.List<ModelObject> interactions=
InteractionUtil.getInitialInteractions(_behaviour.getProtocol().getBlock());
if (interactions != null) {
if (interactions.size() == 1) {
MessageSignature msig=InteractionUtil.getMessageSignature(interactions.get(0));
if (msig.getOperation() != null && msig.getOperation().equals(opName)) {
ret = _behaviour.getProtocol().getBlock();
}
} else {
logger.severe("Stateless behaviour shouldn't have more " +
"than one initial interaction for path: "+_behaviour.getProtocol().getBlock());
}
}
}
return(ret);
}
protected void processBlock(Block block, StringBuffer code, int indent,
java.util.Map<Role,String> roleMap, java.util.Map<String,String> typeVarMap,
ResourceLocator locator) {
for (int i=0; i < block.getContents().size(); i++) {
Activity act=block.getContents().get(i);
if (act instanceof Interaction) {
Interaction interaction=(Interaction)act;
if (org.savara.protocol.model.util.InteractionUtil.isRequest(interaction)) {
if (org.savara.protocol.model.util.InteractionUtil.isSend(interaction)) {
if (i+1 < block.getContents().size()) {
Activity next=block.getContents().get(i+1);
// Check if followed by an interaction representing a response to this
// request
boolean processed=false;
if (next instanceof Interaction &&
org.savara.protocol.model.util.InteractionUtil.isResponseForRequest(
(Interaction)next, interaction)) {
processInvoke(interaction, (Interaction)next,
code, indent, roleMap, typeVarMap, locator);
i++; // Skip response
processed = true;
// Check if followed by a choice representing normal and fault responses
} else if (next instanceof Choice &&
org.savara.protocol.model.util.InteractionUtil.isResponseAndFaultHandler(
(Choice)next, interaction)) {
processInvoke(interaction, (Choice)next,
code, indent, roleMap, typeVarMap, locator);
i++; // Skip normal/response fault handler
processed = true;
}
if (!processed) {
// Handle one-way requests
processInvoke(interaction, (Interaction)null,
code, indent, roleMap, typeVarMap, locator);
}
}
} else {
processReceive(interaction,
code, indent, roleMap, typeVarMap, locator);
}
} else if (org.savara.protocol.model.util.InteractionUtil.isResponse(interaction) &&
org.savara.protocol.model.util.InteractionUtil.isSend(interaction)) {
processResponse(interaction, code, indent, roleMap, typeVarMap, locator);
}
} else if (act instanceof Choice && ChoiceUtil.isDecisionMaker((Choice)act)) {
for (int j=0; j < ((Choice)act).getPaths().size(); j++) {
Block path=((Choice)act).getPaths().get(j);
// Save type/var map
java.util.Map<String,String> savedTypeVarMap=new java.util.HashMap<String, String>(typeVarMap);
newline(code, indent);
if (j == 0) {
code.append("if (false) { // TODO: Set expression");
} else if (j != ((Choice)act).getPaths().size()-1) {
code.append("} else if (false) { // TODO: Set expression");
} else {
code.append("} else {");
}
processBlock(path, code, indent+1, roleMap, typeVarMap, locator);
// Restore type/var map
typeVarMap = savedTypeVarMap;
}
newline(code, indent);
code.append("}\r\n");
}
}
}
protected void newline(StringBuffer code, int indent) {
code.append("\r\n");
for (int i=0; i < indent; i++) {
code.append(" ");
}
}
protected void processResponse(Interaction resp,
StringBuffer code, int indent, java.util.Map<Role,String> roleMap,
java.util.Map<String,String> typeVarMap,
ResourceLocator locator) {
// Check if fault response
if (org.savara.protocol.model.util.InteractionUtil.isFaultResponse(resp)) {
String faultType=getImportedType(resp, locator);
newline(code, indent);
code.append("throw new "+faultType+"();");
} else {
String type=getImportedType(resp, locator);
String name=typeVarMap.get(type);
newline(code, indent);
code.append("// TODO: Add code here to return response");
newline(code, indent);
code.append("// "+name+" = ....;");
}
}
protected void processReceive(Interaction req,
StringBuffer code, int indent, java.util.Map<Role,String> roleMap,
java.util.Map<String,String> typeVarMap,
ResourceLocator locator) {
String type=getImportedType(req, locator);
String name=typeVarMap.get(type);
newline(code, indent);
code.append("// TODO: Add code here to handle request (in variable '"+name+"')\r\n");
}
protected void processInvoke(Interaction req, Interaction resp,
StringBuffer code, int indent, java.util.Map<Role,String> roleMap,
java.util.Map<String,String> typeVarMap,
ResourceLocator locator) {
// Get variable for role
String roleVar=roleMap.get(req.getToRoles().get(0));
// Identify request Java type, and determine if a new variable is required
String reqType=getImportedType(req, locator);
String reqVarName=typeVarMap.get(reqType);
if (reqVarName == null) {
newline(code, indent);
// Declare new request variable
reqVarName = getVariableName(req.getMessageSignature().getOperation())+"Req";
typeVarMap.put(reqType, reqVarName);
code.append("// TODO: Add code here to initialize request");
newline(code, indent);
code.append(reqType+" "+reqVarName+"=null;\r\n");
}
newline(code, indent);
if (resp != null) {
String typeStr=getImportedType(resp, locator);
String varName=typeVarMap.get(typeStr);
if (varName == null) {
varName = getVariableName(req.getMessageSignature().getOperation())+"Result";
typeVarMap.put(typeStr, varName);
code.append(typeStr+" ");
}
code.append(varName+" = ");
}
code.append(roleVar+"."+req.getMessageSignature().getOperation()+"("+reqVarName+");\r\n");
}
protected void processInvoke(Interaction req, Choice respAndFaults,
StringBuffer code, int indent, java.util.Map<Role,String> roleMap,
java.util.Map<String,String> typeVarMap,
ResourceLocator locator) {
// Sort choice paths into normal and fault responses
Block normalPath=null;
Interaction normalInteraction=null;
java.util.List<Block> faultPaths=new java.util.Vector<Block>();
java.util.List<Interaction> faultInteractions=new java.util.Vector<Interaction>();
for (Block b : respAndFaults.getPaths()) {
java.util.List<ModelObject> ints=InteractionUtil.getInitialInteractions(b);
if (ints.size() != 1) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Response fault handler path should only have single initial interaction: "+b);
}
} else {
if (org.savara.protocol.model.util.InteractionUtil.isFaultResponse((Interaction)ints.get(0))) {
faultPaths.add(b);
faultInteractions.add((Interaction)ints.get(0));
} else if (normalPath == null) {
normalPath = b;
normalInteraction = (Interaction)ints.get(0);
} else {
logger.severe("More than one normal path exists in the choice: "+respAndFaults);
}
}
}
newline(code, indent);
code.append("try {");
indent++;
// Save type/var map
java.util.Map<String,String> savedTypeVarMap=new java.util.HashMap<String, String>(typeVarMap);
// Identify request Java type, and determine if a new variable is required
String reqType=getImportedType(req, locator);
String reqVarName=typeVarMap.get(reqType);
if (reqVarName == null) {
newline(code, indent);
// Declare new request variable
reqVarName = getVariableName(req.getMessageSignature().getOperation())+"Req";
typeVarMap.put(reqType, reqVarName);
code.append("// TODO: Add code here to initialize request");
newline(code, indent);
code.append(reqType+" "+reqVarName+"=null;\r\n");
}
newline(code, indent);
// Get normal response and create var if does not exist
if (normalPath != null && normalInteraction != null &&
normalInteraction.getMessageSignature().getTypeReferences().size() == 1) {
String typeStr=getImportedType(normalInteraction, locator);
String varName=typeVarMap.get(typeStr);
if (varName == null) {
varName = getVariableName(req.getMessageSignature().getOperation())+"Result";
typeVarMap.put(typeStr, varName);
code.append(typeStr+" ");
}
code.append(varName+" = ");
}
// Get variable for role
String roleVar=roleMap.get(req.getToRoles().get(0));
code.append(roleVar+"."+req.getMessageSignature().getOperation()+"("+reqVarName+");\r\n");
// Process remainder of normal block
if (normalPath != null) {
processBlock(normalPath, code, indent, roleMap, typeVarMap, locator);
}
indent--;
// Restore type/var map
typeVarMap = savedTypeVarMap;
// For each of the fault blocks - create a catch block for fault
for (int i=0; i < faultPaths.size(); i++) {
Block faultPath=faultPaths.get(i);
Interaction faultInteraction=faultInteractions.get(i);
// Save type/var map
savedTypeVarMap=new java.util.HashMap<String, String>(typeVarMap);
String faultType=getImportedType(faultInteraction, locator);
String faultName=getVariableName(org.savara.protocol.model.util.InteractionUtil.getFaultName(faultInteraction));
newline(code, indent);
code.append("} catch ("+faultType+" "+faultName+") {");
typeVarMap.put(faultType, faultName);
// Process fault block
processBlock(faultPath, code, indent+1, roleMap, typeVarMap, locator);
// Restore type/var map
typeVarMap = savedTypeVarMap;
}
newline(code, indent);
code.append("}\r\n");
}
}