package org.springframework.roo.addon.ws.addon; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.springframework.roo.addon.ws.annotations.RooWsClients; import org.springframework.roo.addon.ws.annotations.SoapBindingType; import org.springframework.roo.classpath.PhysicalTypeIdentifier; import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils; import org.springframework.roo.classpath.PhysicalTypeMetadata; import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails; import org.springframework.roo.classpath.details.FieldMetadataBuilder; import org.springframework.roo.classpath.details.MethodMetadataBuilder; import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder; 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.JavaSymbolName; import org.springframework.roo.model.JavaType; import org.springframework.roo.model.SpringJavaType; import org.springframework.roo.project.LogicalPath; import org.springframework.roo.support.logging.HandlerUtils; /** * Metadata for {@link RooWsClients}. * * @author Juan Carlos GarcĂ­a * @since 2.0 */ public class WsClientsMetadata extends AbstractItdTypeDetailsProvidingMetadataItem { protected final static Logger LOGGER = HandlerUtils.getLogger(WsClientsMetadata.class); private static final String PROVIDES_TYPE_STRING = WsClientsMetadata.class.getName(); private static final String PROVIDES_TYPE = MetadataIdentificationUtils .create(PROVIDES_TYPE_STRING); private JavaType governor; private String profile; private List<WsClientEndpoint> endPoints; private Map<String, MethodMetadataBuilder> endPointMethods = new HashMap<String, MethodMetadataBuilder>(); private Map<String, FieldMetadataBuilder> endPointFields = new HashMap<String, FieldMetadataBuilder>(); private FieldMetadataBuilder loggerField; public static String createIdentifier(final JavaType javaType, final LogicalPath path) { return PhysicalTypeIdentifierNamingUtils.createIdentifier(PROVIDES_TYPE_STRING, javaType, path); } public static String createIdentifier(ClassOrInterfaceTypeDetails details) { final LogicalPath logicalPath = PhysicalTypeIdentifier.getPath(details.getDeclaredByMetadataId()); return createIdentifier(details.getType(), logicalPath); } public static JavaType getJavaType(final String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.getJavaType(PROVIDES_TYPE_STRING, metadataIdentificationString); } public static String getMetadataIdentiferType() { return PROVIDES_TYPE; } public static LogicalPath getPath(final String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.getPath(PROVIDES_TYPE_STRING, metadataIdentificationString); } public static boolean isValid(final String metadataIdentificationString) { return PhysicalTypeIdentifierNamingUtils.isValid(PROVIDES_TYPE_STRING, metadataIdentificationString); } /** * Constructor * * @param identifier * the identifier for this item of metadata (required) * @param aspectName * the Java type of the ITD (required) * @param governorPhysicalTypeMetadata * the governor, which is expected to contain a * {@link ClassOrInterfaceTypeDetails} (required) * @param endPoints * the endpoints that this @Configuration class will manage * @param profile * the profile where this @Configuration class will be active * */ public WsClientsMetadata(final String identifier, final JavaType aspectName, final PhysicalTypeMetadata governorPhysicalTypeMetadata, List<WsClientEndpoint> endPoints, String profile) { super(identifier, aspectName, governorPhysicalTypeMetadata); this.governor = governorPhysicalTypeMetadata.getType(); this.endPoints = endPoints; this.profile = profile; // Ensure that the annotated class is annotated with @Configuration ensureGovernorIsAnnotated(new AnnotationMetadataBuilder(SpringJavaType.CONFIGURATION)); // Ensure that the annotated class is annotated with @ConditionalOnWebApplication ensureGovernorIsAnnotated(new AnnotationMetadataBuilder(new JavaType( "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication"))); // Ensure that the annotated class has LOGGER message ensureGovernorHasField(getLoggerField()); // If developer has specify some profile, annotate class with @Profile if (StringUtils.isNotEmpty(profile)) { AnnotationMetadataBuilder profileAnnotation = new AnnotationMetadataBuilder(SpringJavaType.PROFILE); profileAnnotation.addStringAttribute("value", profile); ensureGovernorIsAnnotated(profileAnnotation); } // Include new field and new method for each registered endpoint for (WsClientEndpoint endPoint : endPoints) { ensureGovernorHasField(getEndPointField(endPoint)); // Create method ensureGovernorHasMethod(getEndpointMethod(endPoint)); } // Build the ITD itdTypeDetails = builder.build(); } /** * This method obtains the LOGGER field * * @return FieldMetadataBuilder that contians information about * the LOGGER field */ public FieldMetadataBuilder getLoggerField() { if (loggerField != null) { return loggerField; } // Create the field FieldMetadataBuilder loggerField = new FieldMetadataBuilder(getId(), Modifier.PRIVATE + Modifier.STATIC + Modifier.FINAL, new JavaSymbolName("LOGGER"), new JavaType("org.slf4j.Logger"), String.format( "%s.getLogger(%s.class);", getNameOfJavaType(new JavaType("org.slf4j.LoggerFactory")), getNameOfJavaType(this.governor))); this.loggerField = loggerField; return loggerField; } /** * This method provides the related field for the provided endPoint * * @param endPoint to obtain the related field * * @return FieldMetadataBuilder that contains all information about the field */ public FieldMetadataBuilder getEndPointField(WsClientEndpoint endPoint) { // Checking if already exists the endPoint field to // prevent to generate it again if (endPointFields.get(endPoint.getName()) != null) { return endPointFields.get(endPoint.getName()); } // Calculate the field name JavaSymbolName fieldName = new JavaSymbolName(StringUtils.uncapitalize(endPoint.getName()).concat("Url")); // Create the field FieldMetadataBuilder endPointField = new FieldMetadataBuilder(getId(), Modifier.PRIVATE, fieldName, JavaType.STRING, null); // Include @Value annotation AnnotationMetadataBuilder valueAnnotation = new AnnotationMetadataBuilder(SpringJavaType.VALUE); valueAnnotation.addStringAttribute("value", String.format("${url/%s}", endPoint.getName())); endPointField.addAnnotation(valueAnnotation); // Cache generated fields endPointFields.put(endPoint.getName(), endPointField); return endPointField; } /** * This method provides the related method for the provided endPoint * * @param endPoint to obtain the related method * * @return MethodMetadataBuilder that contains all information about the method */ public MethodMetadataBuilder getEndpointMethod(WsClientEndpoint endPoint) { // Checking if already exists the endPoint method to // prevent to generate it again if (endPointMethods.get(endPoint.getName()) != null) { return endPointMethods.get(endPoint.getName()); } // If not, obtain necessary info about the provided enpoint String targetNameSpace = endPoint.getTargetNameSpace(); Validate.notEmpty(targetNameSpace, "ERROR: Provided endpoint has not been registered inside @RooWsClients annotation"); // Creating valid endpoint JavaType JavaType endPointType = new JavaType(String.format("%s.%s", getPackageNameFromTargetNameSpace(targetNameSpace), StringUtils.capitalize(endPoint.getName()))); // Getting method name JavaSymbolName methodName = new JavaSymbolName(StringUtils.uncapitalize(endPoint.getName())); // Generating method body InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder(); // JaxWsProxyFactoryBean jaxWsFactory = new JaxWsProxyFactoryBean(); bodyBuilder.appendFormalLine("%s jaxWsFactory = new JaxWsProxyFactoryBean();", getNameOfJavaType(new JavaType("org.apache.cxf.jaxws.JaxWsProxyFactoryBean"))); //jaxWsFactory.setServiceClass(ENDPOINT.class); bodyBuilder.appendFormalLine("jaxWsFactory.setServiceClass(%s.class);", getNameOfJavaType(endPointType)); // jaxWsFactory.setAddress(this.ENDPOINTFIELD); bodyBuilder.appendFormalLine("jaxWsFactory.setAddress(this.%s);", getEndPointField(endPoint) .getFieldName()); // Check bindingType. If is SOAP 1.2, is necessary to generate some extra code if (endPoint.getBindingType().getField().getSymbolName().equals(SoapBindingType.SOAP12.name())) { // jaxWsFactory.setBindingId(SOAPBinding.SOAP12HTTP_BINDING); bodyBuilder.appendFormalLine("jaxWsFactory.setBindingId(%s.SOAP12HTTP_BINDING);", getNameOfJavaType(new JavaType("javax.xml.ws.soap.SOAPBinding"))); // jaxWsFactory.setTransportId(SoapTransportFactory.SOAP_12_HTTP_BINDING); bodyBuilder.appendFormalLine("jaxWsFactory.setTransportId(%s.SOAP_12_HTTP_BINDING);", getNameOfJavaType(new JavaType("org.apache.cxf.binding.soap.SoapTransportFactory"))); } // jaxWsFactory.setFeatures(Arrays.asList(new TraceeCxfFeature(), new LoggingFeature())); bodyBuilder.appendFormalLine("jaxWsFactory.setFeatures(%s.asList(new %s(), new %s()));", getNameOfJavaType(JavaType.ARRAYS), getNameOfJavaType(new JavaType( "io.tracee.binding.cxf.TraceeCxfFeature")), getNameOfJavaType(new JavaType( "org.apache.cxf.feature.LoggingFeature"))); // LOGGER.info("Web Service client ENDPOINTFIELD has been created. URL: '{}'", this.ENDPOINTFIELD); bodyBuilder.appendFormalLine( "%s.info(\"Web Service client %s has been created. URL: '{}'\", this.%s);", getLoggerField().getFieldName(), getEndPointField(endPoint).getFieldName() .getSymbolNameCapitalisedFirstLetter(), getEndPointField(endPoint).getFieldName()); // return (ENDPOINT) jaxWsFactory.create(); bodyBuilder.appendFormalLine("return (%s) jaxWsFactory.create();", getNameOfJavaType(endPointType)); // Generate new method related with the provided endpoint MethodMetadataBuilder method = new MethodMetadataBuilder(getId(), Modifier.PUBLIC, methodName, endPointType, bodyBuilder); method.addAnnotation(new AnnotationMetadataBuilder(SpringJavaType.BEAN)); // Cache generated methods endPointMethods.put(endPoint.getName(), method); return method; } /** * This method obtains a valid package name from the provided targetNamespace. * * Ex: Providing 'http://roo.springframework.org/' targetNameSpace, the obtained * Package name will be 'org.springframework.roo'. * * @param targetNameSpace * @return String with the calculated package name */ public String getPackageNameFromTargetNameSpace(String targetNameSpace) { Validate.notEmpty(targetNameSpace, "ERROR: You must provide valid targetNameSpace to the package name."); // Remove protocols and unecessary elements targetNameSpace = targetNameSpace.replaceAll("http://", ""); targetNameSpace = targetNameSpace.replaceAll("https://", ""); targetNameSpace = targetNameSpace.replaceAll("file://", ""); targetNameSpace = targetNameSpace.replaceAll("/", ""); // Return reverse URL delimited by dots return StringUtils.reverseDelimited(targetNameSpace.toLowerCase(), '.'); } @Override public String toString() { final ToStringBuilder builder = new ToStringBuilder(this); builder.append("identifier", getId()); builder.append("valid", valid); builder.append("aspectName", aspectName); builder.append("destinationType", destination); builder.append("governor", governorPhysicalTypeMetadata.getId()); builder.append("itdTypeDetails", itdTypeDetails); return builder.toString(); } public String getProfile() { return profile; } public List<WsClientEndpoint> getEndPoints() { return endPoints; } }