/*
* 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.importt;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.IOUtils;
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.security.SecurityService;
import org.gvnix.service.roo.addon.addon.security.WSServiceSecurityMetadata;
import org.gvnix.service.roo.addon.addon.util.WsdlParserUtils;
import org.gvnix.service.roo.addon.annotations.GvNIXWebServiceProxy;
import org.gvnix.service.roo.addon.annotations.GvNIXWebServiceSecurity;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.StringAttributeValue;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.process.manager.MutableFile;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.ProjectOperations;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Addon for Handle Web Service Proxy 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 WSImportOperationsImpl implements WSImportOperations {
private static final String CERTIFICATION_FILE_KEY = "org.apache.ws.security.crypto.merlin.file";
private static Logger LOGGER = Logger.getLogger(WSImportOperations.class
.getName());
@Reference
private FileManager fileManager;
@Reference
private ProjectOperations projectOperations;
@Reference
private JavaParserService javaParserService;
@Reference
private AnnotationsService annotationsService;
@Reference
private TypeLocationService typeLocationService;
@Reference
private SecurityService securityService;
/**
* {@inheritDoc}
* <p>
* If the class to add annotation doesn't exist it will be created
* automatically in 'src/main/java' directory inside the package defined.
* </p>
*/
public void addImportAnnotation(JavaType serviceClass, String wsdlLocation) {
// Check URL connection and WSDL format
securityService.getWsdl(wsdlLocation);
// Service class path
String fileLocation = projectOperations.getPathResolver()
.getIdentifier(
LogicalPath.getInstance(Path.SRC_MAIN_JAVA, ""),
serviceClass.getFullyQualifiedTypeName()
.replace('.', '/').concat(".java"));
// If class not exists, create it
if (!fileManager.exists(fileLocation)) {
// Create service class with Service Annotation.
javaParserService.createServiceClass(serviceClass);
LOGGER.log(
Level.FINE,
"New service class created: "
+ serviceClass.getFullyQualifiedTypeName());
}
// Check if import annotation is already defined
if (javaParserService.isAnnotationIntroduced(
GvNIXWebServiceProxy.class.getName(),
typeLocationService.getTypeDetails(serviceClass))) {
LOGGER.log(Level.WARNING,
"Provided class is already importing a service");
}
else {
// Add the import definition annotation and attributes to the class
List<AnnotationAttributeValue<?>> annotationAttributeValues = new ArrayList<AnnotationAttributeValue<?>>();
annotationAttributeValues.add(new StringAttributeValue(
new JavaSymbolName("wsdlLocation"), wsdlLocation));
annotationsService.addJavaTypeAnnotation(serviceClass,
GvNIXWebServiceProxy.class.getName(),
annotationAttributeValues, false);
// Add GvNixAnnotations to the project.
annotationsService.addAddonDependency();
}
}
/** {@inheritDoc} **/
public List<String> getServiceList() {
List<String> classNames = new ArrayList<String>();
Set<ClassOrInterfaceTypeDetails> cids = typeLocationService
.findClassesOrInterfaceDetailsWithAnnotation(new JavaType(
GvNIXWebServiceProxy.class.getName()));
for (ClassOrInterfaceTypeDetails cid : cids) {
if (Modifier.isAbstract(cid.getModifier())) {
continue;
}
classNames.add(cid.getName().getFullyQualifiedTypeName());
}
return classNames;
}
/** {@inheritDoc} **/
public void addSignatureAnnotation(JavaType importedServiceClass,
File certificate, String password, String alias) {
// get class
ClassOrInterfaceTypeDetails importedServiceDetails = typeLocationService
.getTypeDetails(importedServiceClass);
// checks if already has security annotation
final boolean alreadyAnnotated = javaParserService
.isAnnotationIntroduced(
GvNIXWebServiceSecurity.class.getName(),
importedServiceDetails);
// checks if class is really a imported service and if it's a
// RPC-Encoded
Document wsdl = getWSDLFromClass(importedServiceClass);
Validate.notNull(wsdl, importedServiceDetails.getName().toString()
.concat(" is not a imported service"));
Validate.isTrue(
WsdlParserUtils.isRpcEncoded(wsdl.getDocumentElement()),
"Only RPC-Encoded services is supported");
// Get service name from wsdl
String serviceName = getServiceName(wsdl);
// Check if certificate file exist
Validate.isTrue(certificate.exists(), certificate.getAbsolutePath()
.concat(" not found"));
Validate.isTrue(certificate.isFile(), certificate.getAbsolutePath()
.concat(" is not a file"));
// Check certificate extension
if (!certificate.getName().endsWith(".p12")) {
// if it's not .p12 show a warning
LOGGER.warning("Currently this action only supports pkcs12. "
.concat(certificate.getAbsolutePath()).concat(
" has no '.p12' extension"));
}
// Copy certificate file into resources
File targetCertificated = copyCertificateFileIntoResources(
importedServiceClass, certificate);
String propertiesPath = WSServiceSecurityMetadata
.getPropertiesPath(importedServiceClass);
String propertiesAbsolutePath = getSecurityPropertiesAbsolutePath(importedServiceClass);
try {
// update client-config.wsdd
securityService
.addOrUpdateAxisClientService(serviceName,
WSServiceSecurityMetadata
.getServiceWsddConfigurationParameters(
importedServiceClass, alias,
propertiesPath));
// write properties file
createSecurityPropertiesFile(importedServiceClass, serviceName,
password, alias, targetCertificated.getName(),
propertiesAbsolutePath);
}
catch (Exception e) {
throw new IllegalStateException(
"Error generating security configuration", e);
}
if (!alreadyAnnotated) {
// Add annotation to class
List<AnnotationAttributeValue<?>> annotationAttributeValues = new ArrayList<AnnotationAttributeValue<?>>();
annotationsService.addJavaTypeAnnotation(importedServiceClass,
GvNIXWebServiceSecurity.class.getName(),
annotationAttributeValues, false);
}
// Add GvNixAnnotations to the project.
annotationsService.addAddonDependency();
}
@Override
public String getSecurityPropertiesAbsolutePath(
JavaType importedServiceClass) {
// Resolve properties absolute path
String propertiesPath = projectOperations.getPathResolver()
.getIdentifier(
LogicalPath.getInstance(Path.SRC_MAIN_RESOURCES, ""),
WSServiceSecurityMetadata
.getPropertiesPath(importedServiceClass));
return propertiesPath;
}
/**
* Returns properties for service configuration
*
* @return
*/
private Properties getSecurityProperties(String password, String alias,
String certificatePath) {
Properties properties = new Properties();
// security provider
properties.put("org.apache.ws.security.crypto.provider",
"org.apache.ws.security.components.crypto.Merlin");
// certificate Keystore type
properties.put("org.apache.ws.security.crypto.merlin.keystore.type",
"pkcs12");
// certificate keystore password
properties.put(
"org.apache.ws.security.crypto.merlin.keystore.password",
password);
// alias password
properties.put("org.apache.ws.security.crypto.merlin.alias.password",
password);
// certificate keystore alias
properties.put("org.apache.ws.security.crypto.merlin.keystore.alias",
alias);
// certificate keystore file
properties.put(CERTIFICATION_FILE_KEY, certificatePath);
return properties;
}
/**
* <p>
* Copy a certificate file into project resources
* </p>
* <p>
* If a file with the same name already exists, file will be copied using a
* new name adding an suffix to original base name (see
* {@link #computeCertificateTargetName(File, JavaType)}).
* </p>
*
* @param importedServiceClass
* @param certificate
* @return final file generated
*/
private File copyCertificateFileIntoResources(
JavaType importedServiceClass, File certificate) {
// Prepare target file name
File targetCertificated = computeCertificateTargetName(certificate,
importedServiceClass);
// Create target folder (if not exists)
if (!targetCertificated.getParentFile().exists()) {
fileManager.createDirectory(targetCertificated.getParentFile()
.getAbsolutePath());
}
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new FileInputStream(certificate);
outputStream = fileManager.createFile(
targetCertificated.getAbsolutePath()).getOutputStream();
IOUtils.copy(inputStream, outputStream);
}
catch (Exception e) {
throw new IllegalStateException(e);
}
finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
}
return targetCertificated;
}
/**
* <p>
* Generates file names for certificate file to copy into project resource
* folder until it find a unused one.
* </p>
* <p>
* The first try is <code>{target-folder}/{certificate_fileName}</code>
* </p>
* <p>
* Pattern:
* <code>{target-folder}/{certificate_name}_{counter}.{certificate_extension}</code>
* </p>
*
* @param certificate
* @param importedServiceClass
* @return
*/
private File computeCertificateTargetName(File certificate,
JavaType importedServiceClass) {
String certificateName = certificate.getName();
String extension = certificateName.substring(
certificateName.lastIndexOf('.'), certificateName.length());
certificateName = certificateName.substring(0,
certificateName.lastIndexOf('.'));
String targetPath = WSServiceSecurityMetadata.getCertificatePath(
importedServiceClass, certificate.getName());
String baseNamePath = targetPath.replace(certificate.getName(), "");
targetPath = projectOperations.getPathResolver().getIdentifier(
LogicalPath.getInstance(Path.SRC_MAIN_RESOURCES, ""),
targetPath);
int index = 1;
while (fileManager.exists(targetPath)) {
targetPath = baseNamePath.concat(certificateName)
.concat("_" + index).concat(extension);
targetPath = projectOperations.getPathResolver().getIdentifier(
LogicalPath.getInstance(Path.SRC_MAIN_RESOURCES, ""),
targetPath);
index++;
}
return new File(targetPath);
}
/**
* Returns the wsdl document for a proxy class
*
* @param serviceClass
* @return wsdl or null if it's not a proxy class
*/
public Document getWSDLFromClass(JavaType serviceClass) {
// get class
ClassOrInterfaceTypeDetails importedServiceDetails = typeLocationService
.getTypeDetails(serviceClass);
AnnotationMetadata annotation = javaParserService.getAnnotation(
GvNIXWebServiceProxy.class.getName(), importedServiceDetails);
if (annotation == null) {
return null;
}
Document wsdl = securityService.getWsdl((String) annotation
.getAttribute(new JavaSymbolName("wsdlLocation")).getValue());
return wsdl;
}
/**
* Creates <code>{Service_Class}Sercurity.properties</code> file in
* <code>scr/main/resorces/{Service_Class_Package}</code>
*
* @param serviceClass
* @param serviceName
* @param password
* @param alias
* @param certificatePath
* @param propertiesAbsolutePath
*/
private void createSecurityPropertiesFile(JavaType serviceClass,
String serviceName, String password, String alias,
String certificatePath, String propertiesAbsolutePath) {
OutputStream os;
// Gets final properties
Properties properties = getSecurityProperties(password, alias,
certificatePath);
// Checks if file exists
MutableFile mutableFile;
if (fileManager.exists(propertiesAbsolutePath)) {
// Load current file content
Properties storedProperties = new Properties();
mutableFile = fileManager.updateFile(propertiesAbsolutePath);
InputStream is = null;
try {
is = mutableFile.getInputStream();
storedProperties.load(is);
}
catch (IOException ioException) {
throw new IllegalStateException(ioException);
}
finally {
IOUtils.closeQuietly(is);
}
// Compare content
if (propertiesEquals(properties, storedProperties)) {
// File Content is up to date --> exit
return;
}
}
else {
// Unable to find the file, so let's create it
mutableFile = fileManager.createFile(propertiesAbsolutePath);
}
// Store properties in file
os = null;
try {
os = mutableFile.getOutputStream();
storeProperties(serviceClass, serviceName, os, properties);
}
catch (IOException ioException) {
throw new IllegalStateException(ioException);
}
finally {
IOUtils.closeQuietly(os);
}
}
/**
* Compares the values of two Property instances
*
* @param one
* @param other
* @return
*/
private boolean propertiesEquals(Properties one, Properties other) {
if (one.size() != other.size()) {
return false;
}
Set<Entry<Object, Object>> entrySet = one.entrySet();
Object otherValue;
for (Entry<Object, Object> entry : entrySet) {
otherValue = other.get(entry.getKey());
if (otherValue == null) {
if (entry.getValue() != null) {
return false;
}
}
else if (!otherValue.equals(entry.getValue())) {
return false;
}
}
return true;
}
/**
* Store properties into a output stream
*
* @param serviceClass
* @param serviceName
* @param os
* @param properties
* @throws IOException
*/
private void storeProperties(JavaType serviceClass, String serviceName,
OutputStream os, Properties properties) throws IOException {
properties.store(
os,
"Service '".concat(serviceName).concat(
"' security properities. Class ".concat(serviceClass
.getFullyQualifiedTypeName())));
}
@Override
public String getServiceName(JavaType serviceClass) {
Document wsdl = getWSDLFromClass(serviceClass);
return getServiceName(wsdl);
}
@Override
public String getServiceName(String wsdlLocation) {
Document wsdl = securityService.getWsdl(wsdlLocation);
Validate.notNull(wsdl, "Can't get WSDl from ".concat(wsdlLocation));
return getServiceName(wsdl);
}
@Override
public String getServiceName(Document wsdl) {
// loads wsdl
Validate.isTrue(
WsdlParserUtils.isRpcEncoded(wsdl.getDocumentElement()),
"Only RPC-Encoded services is supported");
// Gets first port
Element port = WsdlParserUtils.findFirstCompatiblePort(wsdl
.getDocumentElement());
return port.getAttribute("name");
}
@Override
public String getCertificate(JavaType serviceClass) {
Properties properties;
try {
properties = loadSecurityProperties(serviceClass);
}
catch (IOException e) {
throw new IllegalArgumentException(e);
}
return properties.getProperty(CERTIFICATION_FILE_KEY);
}
/**
* Load security properties file for <code>serviceClass</code>
*
* @param serviceClass
* @return
* @throws IOException
*/
private Properties loadSecurityProperties(JavaType serviceClass)
throws IOException {
String path = getSecurityPropertiesAbsolutePath(serviceClass);
File file = new File(path);
if (!file.exists() || !file.isFile()) {
throw new IOException("Sercurity properties file for '"
.concat(serviceClass.getFullyQualifiedTypeName())
.concat("' not found: ").concat(path));
}
FileInputStream fileIn = null;
Properties properties = null;
try {
properties = new Properties();
fileIn = new FileInputStream(file);
if (fileIn != null) {
properties.load(fileIn);
}
}
finally {
IOUtils.closeQuietly(fileIn);
}
return properties;
}
}