/*
* gvNIX is an open source tool for rapid application development (RAD).
* Copyright (C) 2010 Generalitat Valenciana
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gvnix.service.roo.addon.addon;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.gvnix.service.roo.addon.addon.converters.JavaTypeList;
import org.gvnix.service.roo.addon.addon.ws.export.WSExportOperations;
import org.gvnix.service.roo.addon.addon.ws.export.WSExportWsdlOperations;
import org.gvnix.service.roo.addon.addon.ws.importt.WSImportOperations;
import org.gvnix.support.OperationUtils;
import org.gvnix.support.WebProjectUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.shell.CliAvailabilityIndicator;
import org.springframework.roo.shell.CliCommand;
import org.springframework.roo.shell.CliOption;
import org.springframework.roo.shell.CommandMarker;
import org.springframework.roo.support.logging.HandlerUtils;
/**
* Addon for Handle Service Layer
*
* @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made for <a
* href="http://www.dgti.gva.es">General Directorate for Information
* Technologies (DGTI)</a>
*/
@Component
@Service
public class ServiceCommands implements CommandMarker {
@Reference
private ServiceOperations serviceOperations;
@Reference
private WSExportOperations wSExportOperations;
@Reference
private WSImportOperations wSImportOperations;
@Reference
private WSExportWsdlOperations wSExportWsdlOperations;
@Reference
private MetadataService metadataService;
@Reference
private ProjectOperations projectOperations;
@Reference
private FileManager fileManager;
private static final Logger LOGGER = HandlerUtils
.getLogger(ServiceCommands.class);
// ------------ OSGi component attributes ----------------
private BundleContext context;
private OperationUtils operationUtils;
private WebProjectUtils webProjectUtils;
protected void activate(ComponentContext cContext) {
context = cContext.getBundleContext();
}
@CliAvailabilityIndicator({ "remote service class",
"remote service operation" })
public boolean isCreateServiceClassAvailable() {
return getOperationUtils().isProjectAvailable(metadataService,
projectOperations);
}
@CliCommand(value = "remote service class",
help = "Creates a new Service class in SRC_MAIN_JAVA.")
public String createServiceClass(@CliOption(key = "class",
mandatory = true,
help = "Name of the service class to create") JavaType serviceClass) {
serviceOperations.createServiceClass(serviceClass);
return "New class can be used adding a property of this type with @Autowired annotation in the class that use it.";
}
@CliCommand(value = "remote service operation",
help = "Adds a new method to existing Service")
public void addServiceOperation(
@CliOption(key = { "", "name" },
mandatory = true,
help = "The name of the operation to add") JavaSymbolName operationName,
@CliOption(key = "service",
mandatory = true,
optionContext = "update,project",
help = "The name of the service to receive this field") JavaType className,
@CliOption(key = "return",
mandatory = false,
unspecifiedDefaultValue = "__NULL__",
optionContext = "java-all,project",
help = "The Java type this operation returns") JavaType returnType,
@CliOption(key = "paramNames",
mandatory = false,
help = "The parameters of the operation. They must be introduced separated by commas without blank spaces.") String paramNames,
@CliOption(key = "paramTypes",
mandatory = false,
optionContext = "java",
help = "The Java types of the given parameters. They must be introduced separated by commas without blank spaces.") JavaTypeList paramTypesList,
@CliOption(key = "exceptions",
mandatory = false,
optionContext = "exceptions",
help = "The Exceptions defined for the operation. They must be introduced separated by commas without blank spaces.") JavaTypeList exceptionTypes) {
String[] paramNameArray;
List<String> paramNameList = new ArrayList<String>();
List<JavaType> paramTypes = new ArrayList<JavaType>();
List<JavaType> exceptionList = new ArrayList<JavaType>();
boolean existsParamTypes = paramTypesList != null;
if (existsParamTypes && paramTypesList.getJavaTypes().size() > 0) {
Validate.isTrue(StringUtils.isNotBlank(paramNames),
"You must provide parameter names to create the method.");
}
else if (StringUtils.isNotBlank(paramNames)) {
Validate.isTrue(existsParamTypes
&& paramTypesList.getJavaTypes().size() > 0,
"You must provide parameter Types to create the method.");
}
if (StringUtils.isNotBlank(paramNames)
&& paramTypesList.getJavaTypes().size() > 0) {
paramNameArray = StringUtils.split(paramNames, ",");
Validate.isTrue(
paramTypesList.getJavaTypes().size() == paramNameArray.length,
"The method parameter types must have the same number of parameter names to create the method.");
for (int i = 0; i <= paramNameArray.length - 1; i++) {
paramNameList.add(paramNameArray[i]);
}
paramTypes = paramTypesList.getJavaTypes();
}
// Exceptions.
if (exceptionTypes != null && !exceptionTypes.getJavaTypes().isEmpty()) {
exceptionList = exceptionTypes.getJavaTypes();
}
serviceOperations.addServiceOperation(operationName, returnType,
className, paramTypes, paramNameList, exceptionList);
}
@CliAvailabilityIndicator("remote service define ws")
public boolean isServiceExportAvailable() {
return getOperationUtils().isProjectAvailable(metadataService,
projectOperations)
&& getWebProjectUtils().isWebProject(metadataService,
fileManager, projectOperations);
}
@CliCommand(value = "remote service define ws",
help = "Defines a service endpoint interface (SEI) that will be mapped to a PortType in service contract. If target class doesn't exist the add-on will create it.")
public String serviceExport(
@CliOption(key = "class",
mandatory = true,
help = "Name of the service class to export or create") JavaType serviceClass,
@CliOption(key = "serviceName",
mandatory = false,
help = "Name to publish the Web Service.") String serviceName,
@CliOption(key = "portTypeName",
mandatory = false,
help = "Name to define the portType.") String portTypeName,
@CliOption(key = "addressName",
mandatory = false,
help = "Address to publish the Web Service in server. Default class name value.") String addressName,
@CliOption(key = "targetNamespace",
mandatory = false,
help = "Namespace name for the service. \ni.e.: 'http://services.project.service.test.gvnix.org/'. It must have URI format.") String targetNamespace) {
wSExportOperations.exportService(serviceClass, serviceName,
portTypeName, targetNamespace, addressName);
StringBuilder sb = new StringBuilder();
sb.append("* New service has been defined without operations, use 'service export operation' command to add it.");
sb.append("\n");
sb.append("* New service can be shown adding '/services/' suffix to your base application URL.");
sb.append("\n");
return sb.toString();
}
@CliAvailabilityIndicator("remote service export operation")
public boolean isServiceExportOperationAvailable() {
return getOperationUtils().isProjectAvailable(metadataService,
projectOperations)
&& getWebProjectUtils().isWebProject(metadataService,
fileManager, projectOperations);
}
@CliAvailabilityIndicator("remote service list operation")
public boolean isServiceListOperationAvailable() {
return getOperationUtils().isProjectAvailable(metadataService,
projectOperations)
&& getWebProjectUtils().isWebProject(metadataService,
fileManager, projectOperations);
}
/**
* Command to export a method as a web service operation.
* <p>
* Parameters:
* </p>
* <ul>
* <li>``--class`` (mandatory) Class to export a method.</li>
* <li>``--method``(mandatory) Method to export.</li>
* <li>``--operationName`` Name of the method to be showed as a Web Service
* operation.</li>
* <li>``--resultName`` Method result name.</li>
* <li>``--resultNamespace`` Namespace of the result type.</li>
* <li>``--responseWrapperName`` Name to define the Response Wrapper Object.
* </li>
* <li>``--responseWrapperNamespace``: Namespace of the Response Wrapper
* Object.</li>
* <li>``--requestWrapperName``: Name to define the Request Wrapper Object.</li>
* <li>``--requestWrapperNamespace``: Namespace of the Request Wrapper
* Object.</li>
* </ul>
*/
@CliCommand(value = "remote service export operation",
help = "Publish a class method as web service operation in a PortType.")
public void serviceExportOperation(
@CliOption(key = "class",
mandatory = true,
help = "Name of the service class to export a method.") JavaType serviceClass,
@CliOption(key = "method",
mandatory = true,
help = "Method to export as Web Service Operation.") JavaSymbolName methodName,
@CliOption(key = "operationName",
mandatory = false,
help = "Name of the method to be showed as a Web Service operation.") String operationName,
@CliOption(key = "resultName",
mandatory = false,
help = "Method result name.") String resultName,
@CliOption(key = "resultNamespace",
mandatory = false,
help = "NNamespace of the result type. \ni.e.: 'http://services.project.service.test.gvnix.org/'. It must have URI format.") String resultNamespace,
@CliOption(key = "responseWrapperName",
mandatory = false,
help = "Name to define the Response Wrapper Object.") String responseWrapperName,
@CliOption(key = "responseWrapperNamespace",
mandatory = false,
help = "Namespace of the Response Wrapper Object. \ni.e.: 'http://services.project.service.test.gvnix.org/'. It must have URI format.") String responseWrapperNamespace,
@CliOption(key = "requestWrapperName",
mandatory = false,
help = "Name to define the Request Wrapper Object.") String requestWrapperName,
@CliOption(key = "requestWrapperNamespace",
mandatory = false,
help = "Namespace of the Request Wrapper Object. \ni.e.: 'http://services.project.service.test.gvnix.org/'. It must have URI format.") String requestWrapperNamespace) {
if (StringUtils.isNotBlank(resultNamespace)) {
Validate.isTrue(StringUtils.startsWithIgnoreCase(resultNamespace,
"http://"),
"Name space for WebResult is not correctly defined. It must have URI format.");
}
if (StringUtils.isNotBlank(requestWrapperNamespace)) {
Validate.isTrue(
StringUtils.startsWithIgnoreCase(requestWrapperNamespace,
"http://"),
"Name space for RequestWrapper is not correctly defined. It must have URI format.");
}
if (StringUtils.isNotBlank(responseWrapperNamespace)) {
Validate.isTrue(
StringUtils.startsWithIgnoreCase(responseWrapperNamespace,
"http://"),
"Name space for ResponsetWrapper is not correctly defined. It must have URI format.");
}
wSExportOperations.exportOperation(serviceClass, methodName,
operationName, resultName, resultNamespace,
responseWrapperName, responseWrapperNamespace,
requestWrapperName, requestWrapperNamespace);
}
@CliCommand(value = "remote service list operation",
help = "Shows available methods to export as web service operation in selected class.")
public String serviceExportOperationList(
@CliOption(key = "class",
mandatory = true,
help = "Name of the service class to list methods available to export as web service operations.") JavaType serviceClass) {
return wSExportOperations
.getAvailableServiceOperationsToExport(serviceClass);
}
@CliAvailabilityIndicator("remote service import ws")
public boolean isServiceImportAvailable() {
return getOperationUtils().isProjectAvailable(metadataService,
projectOperations);
}
@CliCommand(value = "remote service import ws",
help = "Imports a Web Service to Service class. If the class doesn't exists the Addon will create it.")
public String serviceImport(
@CliOption(key = "class",
mandatory = true,
help = "Name of the service class to import or create") JavaType serviceClass,
@CliOption(key = "wsdl",
mandatory = true,
help = "Local or remote location (URL) of the web service contract") String url) {
wSImportOperations.addImportAnnotation(serviceClass, url);
StringBuilder sb = new StringBuilder();
sb.append("* New service can be used adding a property of this type with @Autowired annotation in the class that use it.");
sb.append("\n");
sb.append("* If new service has security requirements, use 'remote service security ws' command.");
sb.append("\n");
return sb.toString();
}
@CliAvailabilityIndicator("remote service export ws")
public boolean isServiceExportWsdl() {
return getOperationUtils().isProjectAvailable(metadataService,
projectOperations)
&& getWebProjectUtils().isWebProject(metadataService,
fileManager, projectOperations);
}
@CliCommand(value = "remote service export ws",
help = "Exports a Web Service from WSDL to java code with gvNIX annotations to generate this Web Service in project with dummy methods.")
public String serviceExportWsdl(
@CliOption(key = "wsdl",
mandatory = true,
help = "Local or remote location (URL) of the web service contract") String url) {
List<JavaType> serviceClasses = wSExportWsdlOperations.exportWsdl(url);
StringBuilder sb = new StringBuilder();
if (serviceClasses == null || serviceClasses.size() == 0) {
return null;
}
else if (serviceClasses.size() == 1) {
sb.append(MessageFormat
.format("* New service has been created at {0}, edit it to add you business logic.",
new Object[] { serviceClasses.get(0)
.getFullyQualifiedTypeName() }));
}
else {
sb.append("* New service classes has been created, edit them to add you business logic:");
for (JavaType serviceClass : serviceClasses) {
sb.append(" - ".concat(serviceClass
.getFullyQualifiedTypeName()));
}
}
sb.append("\n");
sb.append("* New service can be shown adding '/services/' suffix to your base application URL.");
sb.append("\n");
return sb.toString();
}
@CliAvailabilityIndicator("remote service ws list")
public boolean isServiceWsListAvalilable() {
return getOperationUtils().isProjectAvailable(metadataService,
projectOperations);
}
@CliAvailabilityIndicator("remote service security ws")
public boolean isServiceSecurityWs() {
return getOperationUtils().isProjectAvailable(metadataService,
projectOperations);
}
@CliCommand(value = "remote service security ws",
help = "Adds Signature to a imported Web Service request")
public void serviceSecurityWs(
@CliOption(key = "class",
mandatory = true,
help = "Name of the imported service class") JavaType importedServiceClass,
@CliOption(key = "certificate",
mandatory = true,
help = "pkcs12 file to use for sing the request") File certificate,
@CliOption(key = "password",
mandatory = true,
help = "pkcs12 file password to use for sing the request") String password,
@CliOption(key = "alias",
mandatory = true,
help = "alias of pkcs12 file to use for sing the request") String alias) {
// TODO use converters to auto-complete imported service class parameter
// (converter can use WSImportOperation.getServiceList() for
// auto-complete)
wSImportOperations.addSignatureAnnotation(importedServiceClass,
certificate, password, alias);
}
@CliCommand(value = "remote service ws list",
help = "Shows a class list with imported and/or exported services")
public String serviceWsList() {
// Gets imported services
List<String> imported = wSImportOperations.getServiceList();
// Gets exported services
List<String> exported = wSExportOperations.getServiceList();
// Format result
return formatWsList(imported, exported);
}
/**
* <p>
* Format web service list
* </p>
* <p>
* Pattern:<br/>
*
* <pre>
* Services exported imported
* ------------------- --------- -----------
* {className} X X
* {className2} X
* {className3} X
* </pre>
*
* </p>
*
* @param importedServices
* @param exportedServices
* @return
*/
private String formatWsList(List<String> importedServices,
List<String> exportedServices) {
if ((importedServices == null || importedServices.isEmpty())
&& (exportedServices == null || exportedServices.isEmpty())) {
return "No Web Services services found in application";
}
// Variable for max service name length
int maxLength = 0;
// Generate a shorted set
Set<String> services = new TreeSet<String>();
if (importedServices != null) {
for (String service : importedServices) {
services.add(service);
if (service.length() > maxLength) {
maxLength = service.length();
}
}
}
if (exportedServices != null) {
for (String service : exportedServices) {
services.add(service);
if (service.length() > maxLength) {
maxLength = service.length();
}
}
}
// Generate out
StringWriter writer = new StringWriter();
PrintWriter printer = new PrintWriter(writer);
// Add header
printer.print(fitStringTo("Services", maxLength, ' '));
printer.print(" ");
printer.print("exported");
printer.print(" ");
printer.println("imported");
// Add header separator
printer.print(fitStringTo("", maxLength + 1, '-'));
printer.print(' ');
printer.print(fitStringTo("", "exported".length() + 2, '-'));
printer.print(' ');
printer.print(fitStringTo("", "imported".length() + 2, '-'));
printer.println();
for (String service : services) {
printer.print(fitStringTo(service, maxLength, ' '));
printer.print(" ");
if (exportedServices.contains(service)) {
printer.print(fitStringTo("", 3, ' '));
printer.print('X');
printer.print(fitStringTo("", 4, ' '));
}
else {
printer.print(fitStringTo("", 8, ' '));
}
printer.print(" ");
if (importedServices.contains(service)) {
printer.print(fitStringTo("", 3, ' '));
printer.print('X');
printer.print(fitStringTo("", 4, ' '));
}
else {
printer.print(fitStringTo("", 8, ' '));
}
printer.println();
}
return writer.toString();
}
/**
* Add <code>character</code> to a <code>string</code> until
* <code>length</code>
*
* @param string
* @param length
* @param character
* @return
*/
private String fitStringTo(String string, int length, char character) {
StringBuilder sb = new StringBuilder(string);
while (sb.length() < length) {
sb.append(character);
}
return sb.toString();
}
public WebProjectUtils getWebProjectUtils() {
if (webProjectUtils == null) {
// Get all Services implement WebProjectUtils interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
WebProjectUtils.class.getName(), null);
for (ServiceReference<?> ref : references) {
webProjectUtils = (WebProjectUtils) this.context
.getService(ref);
return webProjectUtils;
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load WebProjectUtils on ServiceCommands.");
return null;
}
}
else {
return webProjectUtils;
}
}
public OperationUtils getOperationUtils() {
if (operationUtils == null) {
// Get all Services implement OperationUtils interface
try {
ServiceReference<?>[] references = this.context
.getAllServiceReferences(
OperationUtils.class.getName(), null);
for (ServiceReference<?> ref : references) {
operationUtils = (OperationUtils) this.context
.getService(ref);
return operationUtils;
}
return null;
}
catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load OperationUtils on ServiceCommands.");
return null;
}
}
else {
return operationUtils;
}
}
}