/*
* 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.ws.export;
import java.io.File;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
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.AnnotationsService;
import org.gvnix.service.roo.addon.addon.JavaParserService;
import org.gvnix.service.roo.addon.addon.ws.WSConfigService;
import org.gvnix.service.roo.addon.annotations.GvNIXWebFault;
import org.gvnix.service.roo.addon.annotations.GvNIXWebService;
import org.gvnix.service.roo.addon.annotations.GvNIXXmlElement;
import org.springframework.roo.classpath.PhysicalTypeDetails;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.MethodMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.ArrayAttributeValue;
import org.springframework.roo.classpath.details.annotations.BooleanAttributeValue;
import org.springframework.roo.classpath.details.annotations.StringAttributeValue;
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.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.ProjectOperations;
import org.apache.commons.lang3.StringUtils;
/**
* @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 WSExportValidationServiceImpl implements WSExportValidationService {
@Reference
private ProjectOperations projectOperations;
@Reference
private FileManager fileManager;
@Reference
private MetadataService metadataService;
@Reference
private WSConfigService wSConfigService;
@Reference
private AnnotationsService annotationsService;
@Reference
private TypeLocationService typeLocationService;
@Reference
private JavaParserService javaParserService;
/**
* {@inheritDoc}
*/
public void addGvNixWebFaultToExceptions(MethodMetadata method,
String targetNamespace) {
// Method is required
Validate.isTrue(method != null,
"The method doesn't exists in the class");
// Get all throws types
List<JavaType> types = method.getThrowsTypes();
for (JavaType type : types) {
addGvNixWebFaultToException(targetNamespace, type);
}
}
/**
* Add GvNIXWebFault to exception and extends java types in project.
*
* @param method Add annotation to java type exception in project
* @param targetNamespace Target namespace to add as annotation attribute
*/
protected void addGvNixWebFaultToException(String targetNamespace,
JavaType type) {
// Exception type exists in the project sources
if (fileManager.exists(getJavaTypeIdentifier(type))) {
// Add GvNIXWebFault annotation to type
addGvNixWebFaultAnnotation(targetNamespace, type);
// Add gvNIX web fault to parent types in project too
for (JavaType extend : getTypeDetails(type).getExtendsTypes()) {
addGvNixWebFaultToException(targetNamespace, extend);
}
}
}
/**
* Add GvNIXWebFault with attributes to exception java type in project.
* <ul>
* <li>name: from uncapitalized java type simple type name</li>
* <li>targetNamespace: from input parameter</li>
* <li>faultBean: from java type fully qualified type name</li>
* </ul>
*
* @param targetNamespace Target namespace to add as annotation attribute
* @param type Type to add annotation
*/
protected void addGvNixWebFaultAnnotation(String targetNamespace,
JavaType type) {
// Annotation attributes: name, target namespace and fault bean
List<AnnotationAttributeValue<?>> attrs = new ArrayList<AnnotationAttributeValue<?>>();
attrs.add(new StringAttributeValue(new JavaSymbolName("name"),
StringUtils.uncapitalize(type.getSimpleTypeName())));
attrs.add(new StringAttributeValue(
new JavaSymbolName("targetNamespace"), targetNamespace));
attrs.add(new StringAttributeValue(new JavaSymbolName("faultBean"),
type.getFullyQualifiedTypeName()));
// Add gvNIX web fault annotation to exception
annotationsService.addJavaTypeAnnotation(type,
GvNIXWebFault.class.getName(), attrs, false);
}
/**
* {@inheritDoc}
*/
public void addGvNixXmlElementToTypes(MethodMetadata method) {
Validate.isTrue(method != null,
"The method doesn't exists in the class");
List<JavaType> types = new ArrayList<JavaType>();
getProjectTypes(method, types);
for (JavaType type : types) {
ClassOrInterfaceTypeDetails typeDetails = getTypeDetails(type);
// Add gvNIX XML Element annotation
addGvNixXmlElementAnnotation(type, typeDetails.getName());
}
// // Add gvNIX xml element annotation to method return type in project
// addGvNixXmlElementToType(method.getReturnType());
//
// // Add gvNIX xml element annotation to parameters types in project
// for (AnnotatedJavaType parameterType : method.getParameterTypes()) {
//
// addGvNixXmlElementToType(parameterType.getJavaType());
// }
}
/**
* Add to list a method return and params types.
* <p>
* Return and param types and their params only added to list if exists in
* project source.
* </p>
*
* @param method Method to check
* @param types Types list to add types
*/
protected void getProjectTypes(MethodMetadata method, List<JavaType> types) {
// Get project types from return type, return extends and return params
getProjectTypes(method.getReturnType(), types);
// Get project types from param type, param extends and param params
for (AnnotatedJavaType parameterType : method.getParameterTypes()) {
getProjectTypes(parameterType.getJavaType(), types);
}
}
/**
* Add to list a type and their params and extends types.
* <p>
* Type and extend types only added to list if exists in project source.
* </p>
*
* @param type Type to check
* @param types Types list to add types
*/
protected void getProjectTypes(JavaType type, List<JavaType> types) {
// If type already included do nothing (avoid infinite loop)
if (types.contains(type)) {
return;
}
// If type exists in project source
if (fileManager.exists(getJavaTypeIdentifier(type))) {
// Add type to list
types.add(type);
// Get type project extends types
for (JavaType extendsType : getTypeDetails(type).getExtendsTypes()) {
getProjectTypes(extendsType, types);
}
}
// Get type project parameters types, if exists
for (JavaType paramType : type.getParameters()) {
getProjectTypes(paramType, types);
}
}
/**
* Add GvNIXXmlElement to java type in project.
* <p>
* GvNIXXmlElement annotation is added to parent extend types in project and
* type parameters in project too.
* </p>
*
* @param javaType Java type (can't be null)
* @return Is it allowed ?
*/
protected void addGvNixXmlElementToType(JavaType javaType) {
// javaType is required
Validate.isTrue(javaType != null, "JavaType type can't be 'null'.");
// Java type exists in the project sources
if (fileManager.exists(getJavaTypeIdentifier(javaType))) {
ClassOrInterfaceTypeDetails typeDetails = getTypeDetails(javaType);
// Add gvNIX XML Element annotation
addGvNixXmlElementAnnotation(javaType, typeDetails.getName());
// Add gvNIX XML Element to parent type (b.e. Owner->AbstractPerson)
for (JavaType extend : typeDetails.getExtendsTypes()) {
addGvNixXmlElementToType(extend);
}
}
// Check parameters types (b.e. List<Owner>)
for (JavaType paramType : javaType.getParameters()) {
addGvNixXmlElementToType(paramType);
}
}
/**
* Add @GvNIXXmlElement annotation with attributes to java type.
* <ul>
* <li>name attribute value from java type simple name</li>
* <li>namespace attribute value from java type package</li>
* <li>elementList attribute from all not transient fields (Java and AJs)</li>
* <li>exported attribute is always false</li>
* <li>xmlTypeName from java simple type, if not empty</li>
* </ul>
*
* @param javaType To get attributes for gvNIX annotation
* @param typeName Type name to add annotation
*/
protected void addGvNixXmlElementAnnotation(JavaType javaType,
JavaType typeName) {
List<AnnotationAttributeValue<?>> attrs = new ArrayList<AnnotationAttributeValue<?>>();
// name attribute value from java type simple name
StringAttributeValue name = new StringAttributeValue(
new JavaSymbolName("name"), StringUtils.uncapitalize(javaType
.getSimpleTypeName()));
attrs.add(name);
// namespace attribute value from java type package
StringAttributeValue namespace = new StringAttributeValue(
new JavaSymbolName("namespace"),
wSConfigService.convertPackageToTargetNamespace(javaType
.getPackage().toString()));
attrs.add(namespace);
// Create attribute list with all (Java & AJs) no transient fields
List<FieldMetadata> fields = javaParserService.getFieldsInAll(typeName);
List<StringAttributeValue> values = new ArrayList<StringAttributeValue>();
for (FieldMetadata field : fields) {
// Transient fields can't have JAXB annotations (b.e. entityManager)
if (field.getModifier() != Modifier.TRANSIENT) {
// Create an attribute list with fields
StringAttributeValue value = new StringAttributeValue(
new JavaSymbolName("ignored"), field.getFieldName()
.getSymbolName());
if (!values.contains(value)) {
values.add(value);
}
}
}
ArrayAttributeValue<StringAttributeValue> elements = new ArrayAttributeValue<StringAttributeValue>(
new JavaSymbolName("elementList"), values);
attrs.add(elements);
// xmlTypeName from java type simple type, if not empty
if (elements != null && !elements.getValue().isEmpty()) {
StringAttributeValue xmlTypeName = new StringAttributeValue(
new JavaSymbolName("xmlTypeName"),
javaType.getSimpleTypeName());
attrs.add(xmlTypeName);
}
else {
StringAttributeValue xmlTypeName = new StringAttributeValue(
new JavaSymbolName("xmlTypeName"), "");
attrs.add(xmlTypeName);
}
// exported attribute is always false (when code first)
BooleanAttributeValue exported = new BooleanAttributeValue(
new JavaSymbolName("exported"), false);
attrs.add(exported);
annotationsService.addJavaTypeAnnotation(typeName,
GvNIXXmlElement.class.getName(), attrs, false);
}
/**
* Get mutable class or interface type details from java type.
*
* @param javaType Java type
* @return Mutable class or interface type
*/
protected ClassOrInterfaceTypeDetails getTypeDetails(JavaType javaType) {
// Get mutable class or interface type details from java type
String id = PhysicalTypeIdentifier.createIdentifier(javaType,
LogicalPath.getInstance(Path.SRC_MAIN_JAVA, ""));
PhysicalTypeMetadata ptm = (PhysicalTypeMetadata) metadataService
.get(id);
Validate.notNull(ptm, "Java source class doesn't exists.");
PhysicalTypeDetails ptd = ptm.getMemberHoldingTypeDetails();
Validate.notNull(ptd, "Java source code details unavailable for type "
+ PhysicalTypeIdentifier.getFriendlyName(id));
Validate.isInstanceOf(ClassOrInterfaceTypeDetails.class, ptd,
"Java source code is immutable for type "
+ PhysicalTypeIdentifier.getFriendlyName(id));
return (ClassOrInterfaceTypeDetails) ptd;
}
/**
* {@inheritDoc}
*/
public boolean checkNamespaceFormat(String namespace) {
if (StringUtils.isNotBlank(namespace)) {
try {
new URI(namespace);
}
catch (URISyntaxException e) {
return false;
}
}
return true;
}
/**
* Get the path identifier for a java type in the project.
*
* @param javaType Java type
* @return Path identifier in project
*/
protected String getJavaTypeIdentifier(JavaType javaType) {
return projectOperations.getPathResolver().getIdentifier(
LogicalPath.getInstance(Path.SRC_MAIN_JAVA, ""),
javaType.getFullyQualifiedTypeName()
.replace('.', File.separatorChar).concat(".java"));
}
/**
* {@inheritDoc}
*/
public String getWebServiceDefaultNamespace(JavaType javaType) {
// Get and validate mutable type details
ClassOrInterfaceTypeDetails typeDetails = typeLocationService
.getTypeDetails(javaType);
Validate.isInstanceOf(ClassOrInterfaceTypeDetails.class, typeDetails,
"Can't modify ".concat(typeDetails.getName().toString()));
// Get and validate gvNIX web service annotation
AnnotationMetadata annotation = typeDetails
.getTypeAnnotation(new JavaType(GvNIXWebService.class.getName()));
Validate.isTrue(
annotation != null,
"Launch command 'service define ws --class ".concat(
javaType.getFullyQualifiedTypeName()).concat(
"' to export class to Web Service."));
// Get and validate gvNIX web service annotation target namespace attr
StringAttributeValue targetNamespace = (StringAttributeValue) annotation
.getAttribute(new JavaSymbolName("targetNamespace"));
Validate.notNull(
targetNamespace,
"You must define 'targetNamespace' annotation attribute in @GvNIXWebService in class: '"
.concat(javaType.getFullyQualifiedTypeName()).concat(
"'."));
Validate.isTrue(
StringUtils.isNotBlank(targetNamespace.getValue()),
"You must define 'targetNamespace' annotation attribute in @GvNIXWebService in class: '"
.concat(javaType.getFullyQualifiedTypeName()).concat(
"'."));
// Get and validate gvNIX web service annotation target namespace value
String targetNamespaceValue = targetNamespace.getValue();
Validate.isTrue(
checkNamespaceFormat(targetNamespaceValue),
"Attribute 'targetNamespace' in @GvNIXWebService for Web Service class '"
.concat(javaType.getFullyQualifiedTypeName())
.concat("'has to start with 'http://'.\ni.e.: http://name.of.namespace/"));
return targetNamespaceValue;
}
}