/*
* 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.security;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.security.auth.callback.CallbackHandler;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.gvnix.service.roo.addon.annotations.GvNIXWebServiceSecurity;
import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.details.ConstructorMetadata;
import org.springframework.roo.classpath.details.ConstructorMetadataBuilder;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.MethodMetadata;
import org.springframework.roo.classpath.details.MethodMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.populator.AutoPopulationUtils;
import org.springframework.roo.classpath.itd.AbstractItdTypeDetailsProvidingMetadataItem;
import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder;
import org.springframework.roo.metadata.MetadataIdentificationUtils;
import org.springframework.roo.model.DataType;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.project.LogicalPath;
/**
* <p>
* Web Service Security Metadata
* </p>
* <p>
* Manage {@link GvNIXWebServiceSecurity} annotation
* </p>
* <p>
* Prepares Metadata for configure Web service Proxy configuration. Currently
* only can generate WS client configuration and only for <code>Signature</code>
* action and <code>pck12</code> certificated.
* </p>
* <p>
* This generates:
* <ul>
* <li>a ITD with PasswordHandler implementation (internal ITD builder)</li>
* <li> <code>properties</code> for configuration file (
* {@link #getSecurityProperties()})</li>
* <li>a <code>service</code> parameters for <code>client-config.wsdd</code>
* file ( {@link #getServiceWsddConfigurationParameters()})
* </ul>
* </p>
* <p>
* TODO extend it to support more wssl4 security actions
* </p>
*
* @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>
*/
public class WSServiceSecurityMetadata extends
AbstractItdTypeDetailsProvidingMetadataItem {
private static final JavaSymbolName CALBACK_PARAM_NAME = new JavaSymbolName(
"callbacks");
private static final JavaType CALLBACKS_PARAM_TYPE = new JavaType(
"javax.security.auth.callback.Callback", 1, DataType.TYPE, null,
null);
private static final JavaType UNSUPPORTED_CALLBACK_TYPE = new JavaType(
"javax.security.auth.callback.UnsupportedCallbackException");
private static final JavaType IO_EXCEPTION_TYPE = new JavaType(
"java.io.IOException");
private static final String PROVIDES_TYPE_STRING = WSServiceSecurityMetadata.class
.getName();
private static final String PROVIDES_TYPE = MetadataIdentificationUtils
.create(PROVIDES_TYPE_STRING);
private static final JavaSymbolName HANDLE_METHOD_NAME = new JavaSymbolName(
"handle");
private final String certificate;
/**
* Path (relative to classpath ) to Certificate
*/
private final String certificatePath;
/**
* Path (relative to classpath) to Properties file
*/
private final String propertiesPath;
private final String serviceName;
private final JavaType serviceClass;
public WSServiceSecurityMetadata(String identifier, JavaType aspectName,
PhysicalTypeMetadata governorPhysicalTypeMetadata,
String serviceName, String certificate) {
super(identifier, aspectName, governorPhysicalTypeMetadata);
Validate.isTrue(isValid(identifier), "Metadata identification string '"
+ identifier + "' does not appear to be a valid");
// Process values from the annotation, if present (XXX ???)
AnnotationMetadata annotation = governorTypeDetails
.getAnnotation(new JavaType(GvNIXWebServiceSecurity.class
.getName()));
// XXX annotation null? (???)
if (annotation != null) {
AutoPopulationUtils.populate(this, annotation);
}
// ServiceClass
this.serviceClass = governorTypeDetails.getName();
// ServiceName
this.serviceName = serviceName;
// Certificate
this.certificate = certificate;
// CertificatePath
this.certificatePath = getCertificatePath(serviceClass, certificate);
// Compute path to properties
propertiesPath = getPropertiesPath(governorTypeDetails.getName());
// Adding CallBackHander implementation
builder.addImplementsType(new JavaType(CallbackHandler.class
.getCanonicalName()));
// Adding default constructor
builder.addConstructor(getDefaultConstructor());
// Adding CallBackHandler method
MethodMetadata callBackHandler = getCallBackHandlerMethod();
if (callBackHandler != null) {
builder.addMethod(callBackHandler);
}
// Create output ITD
itdTypeDetails = builder.build();
}
/**
* Returns default class constructor (with no parameters)
*
* @return
*/
private ConstructorMetadata getDefaultConstructor() {
// Checks if default constructor is already defined
for (ConstructorMetadata constructor : governorTypeDetails
.getDeclaredConstructors()) {
if (constructor.getParameterTypes() == null
|| constructor.getParameterTypes().isEmpty()) {
return constructor;
}
}
// Create the constructor
InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
bodyBuilder.appendFormalLine("super();");
ConstructorMetadataBuilder constructorBuilder = new ConstructorMetadataBuilder(
getId());
constructorBuilder.setModifier(Modifier.PUBLIC);
constructorBuilder.setBodyBuilder(bodyBuilder);
return constructorBuilder.build();
}
/**
* Return method for handle password
*
* @return
*/
private MethodMetadata getCallBackHandlerMethod() {
// Prepare method parameter definition
List<JavaType> parameterTypes = new ArrayList<JavaType>();
parameterTypes.add(CALLBACKS_PARAM_TYPE);
List<AnnotatedJavaType> parameters = AnnotatedJavaType
.convertFromJavaTypes(parameterTypes);
// Check if a method with the same signature already exists in the
// target type
if (MemberFindingUtils.getDeclaredMethod(governorTypeDetails,
HANDLE_METHOD_NAME, parameterTypes) != null) {
// If it already exists, just return the method and omit its
// generation via the ITD
return null;
}
// Define method throws types
List<JavaType> throwsTypes = new ArrayList<JavaType>();
throwsTypes.add(IO_EXCEPTION_TYPE);
throwsTypes.add(UNSUPPORTED_CALLBACK_TYPE);
// Define method parameter names
List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
parameterNames.add(CALBACK_PARAM_NAME);
// Create the method body
InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
bodyBuilder.appendFormalLine("final String propPath = \"".concat(
propertiesPath).concat("\";"));
bodyBuilder
.appendFormalLine("final String propKey = \"org.apache.ws.security.crypto.merlin.keystore.password\";");
bodyBuilder.appendFormalLine("try {");
bodyBuilder.indent();
bodyBuilder.append("// Get class loader to get file from project");
bodyBuilder.newLine();
bodyBuilder
.appendFormalLine("ClassLoader classLoader = Thread.currentThread().getContextClassLoader();");
bodyBuilder
.appendFormalLine("java.io.File file = new java.io.File(classLoader.getResource(propPath).toURI());");
bodyBuilder.appendFormalLine("if (file != null && file.exists()) {");
bodyBuilder.indent();
bodyBuilder.append("// Load properties");
bodyBuilder.newLine();
bodyBuilder
.appendFormalLine("java.util.Properties properties = new java.util.Properties();");
bodyBuilder.appendFormalLine("java.io.FileInputStream ins = null;");
bodyBuilder.appendFormalLine("try {");
bodyBuilder.indent();
bodyBuilder
.appendFormalLine("ins = new java.io.FileInputStream(file);");
bodyBuilder.appendFormalLine("properties.load(ins);");
bodyBuilder.indentRemove();
bodyBuilder.appendFormalLine("} finally {");
bodyBuilder.indent();
bodyBuilder.appendFormalLine("if (ins != null) {");
bodyBuilder.indent();
bodyBuilder.appendFormalLine("ins.close();");
bodyBuilder.indentRemove();
bodyBuilder.appendFormalLine("}"); // End if
bodyBuilder.indentRemove();
bodyBuilder.appendFormalLine("}"); // End try (ins)
bodyBuilder
.appendFormalLine("String value = properties.getProperty(propKey);");
bodyBuilder.appendFormalLine("if (value != null) {");
bodyBuilder.indent();
bodyBuilder
.appendFormalLine("((org.apache.ws.security.WSPasswordCallback) callbacks[0]).setPassword(value);");
bodyBuilder.indentRemove();
bodyBuilder.appendFormalLine("} else {"); // Else value != null
bodyBuilder.indent();
bodyBuilder
.appendFormalLine("throw new IOException(\"Property \".concat(propKey).concat(\" not exists\"));");
bodyBuilder.indentRemove();
bodyBuilder.appendFormalLine("}"); // Endif value != null
bodyBuilder.indentRemove();
bodyBuilder.appendFormalLine("} else {"); // Else file.exists()
bodyBuilder.indent();
bodyBuilder
.appendFormalLine("throw new IOException(\"File \".concat(propPath).concat(\" not exists\"));");
bodyBuilder.indentRemove();
bodyBuilder.appendFormalLine("}"); // Endif file.exists()
bodyBuilder.indentRemove();
bodyBuilder
.appendFormalLine("} catch (java.net.URISyntaxException e) {");
bodyBuilder.indent();
bodyBuilder
.appendFormalLine("throw new IOException(\"Problem getting \".concat(propPath).concat(\" file\"),e);");
bodyBuilder.indentRemove();
bodyBuilder.appendFormalLine("}"); // End try
// Use the MethodMetadataBuilder for easy creation of MethodMetadata
MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
getId(), Modifier.PUBLIC, HANDLE_METHOD_NAME,
JavaType.VOID_PRIMITIVE, parameters, parameterNames,
bodyBuilder);
methodBuilder.setThrowsTypes(throwsTypes);
return methodBuilder.build(); // Build and return a MethodMetadata
// instance
}
/**
* @return Map with parameters for client-config.wsdd
*/
public static Map<String, String> getServiceWsddConfigurationParameters(
JavaType serviceClass, String alias, String propertiesPath) {
Map<String, String> parameters = new HashMap<String, String>();
// <parameter name="action" value="Signature"/>
parameters.put("action", "Signature");
// <parameter name="user" value="alias"/>
parameters.put("user", alias);
// <parameter name="passwordCallbackClass" value="governor"/>
parameters.put("passwordCallbackClass",
serviceClass.getFullyQualifiedTypeName());
// <parameter name="signaturePropFile" value="path_To_Properties"/>
parameters.put("signaturePropFile", propertiesPath);
// <parameter name="signatureKeyIdentifier" value="DirectReference" />
parameters.put("signatureKeyIdentifier", "DirectReference");
return parameters;
}
/**
* @return service name (identifier)
*/
public String getServiceName() {
return serviceName;
}
/**
* @return service class
*/
public JavaType getServiceClass() {
return serviceClass;
}
/**
* @return certificate keystore file
*/
public String getCertificate() {
return certificate;
}
/**
* @return properties file path (relative to classpath)
*/
public String getPropertiesPath() {
return propertiesPath;
}
/**
* @return certificate keystore file path (relative to classpath)
*/
public String getCertificatePath() {
return certificatePath;
}
/**
* Compute certificate keystore file path (relative to classpath) for a
* serviceClass and certificateFileName
*
* @param serviceClass
* @param certificateFileName
* @return
*/
public static String getCertificatePath(JavaType serviceClass,
String certificateFileName) {
String path = serviceClass.getFullyQualifiedTypeName();
path = path.replace('.', '/');
path = path.substring(0, path.lastIndexOf('/') + 1);
return path.concat(certificateFileName);
}
/**
* Compute property file path (relative to classpath) for a serviceClass
*
* @param serviceClass
* @return
*/
public static String getPropertiesPath(JavaType serviceClass) {
String classpath = serviceClass.getFullyQualifiedTypeName();
String path = classpath.replace('.', '/');
return path.concat("Security.properties");
}
// Typically, no changes are required beyond this point
@Override
public String toString() {
ToStringBuilder tsc = new ToStringBuilder(this);
tsc.append("identifier", getId());
tsc.append("valid", valid);
tsc.append("aspectName", aspectName);
tsc.append("destinationType", destination);
tsc.append("governor", governorPhysicalTypeMetadata.getId());
tsc.append("itdTypeDetails", itdTypeDetails);
return tsc.toString();
}
public static final String getMetadataIdentiferType() {
return PROVIDES_TYPE;
}
public static final String createIdentifier(JavaType javaType,
LogicalPath path) {
return PhysicalTypeIdentifierNamingUtils.createIdentifier(
PROVIDES_TYPE_STRING, javaType, path);
}
public static final JavaType getJavaType(String metadataIdentificationString) {
return PhysicalTypeIdentifierNamingUtils.getJavaType(
PROVIDES_TYPE_STRING, metadataIdentificationString);
}
public static final LogicalPath getPath(String metadataIdentificationString) {
return PhysicalTypeIdentifierNamingUtils.getPath(PROVIDES_TYPE_STRING,
metadataIdentificationString);
}
public static boolean isValid(String metadataIdentificationString) {
return PhysicalTypeIdentifierNamingUtils.isValid(PROVIDES_TYPE_STRING,
metadataIdentificationString);
}
}