/**
* Mule Development Kit
* Copyright 2010-2011 (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mule.devkit.maven;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProjectHelper;
import org.jfrog.maven.annomojo.annotations.MojoComponent;
import org.jfrog.maven.annomojo.annotations.MojoGoal;
import org.jfrog.maven.annomojo.annotations.MojoParameter;
import org.jfrog.maven.annomojo.annotations.MojoPhase;
import org.jfrog.maven.annomojo.annotations.MojoRequiresDependencyResolution;
import org.mule.api.annotations.Configurable;
import org.mule.api.annotations.Module;
import org.mule.api.annotations.Processor;
import org.mule.api.annotations.param.Optional;
import org.mule.api.annotations.rest.HttpMethod;
import org.mule.api.annotations.rest.RestCall;
import org.mule.api.annotations.rest.RestHeaderParam;
import org.mule.api.annotations.rest.RestQueryParam;
import org.mule.api.annotations.rest.RestUriParam;
import org.mule.devkit.model.code.AnnotationUse;
import org.mule.devkit.model.code.CatchBlock;
import org.mule.devkit.model.code.ClassAlreadyExistsException;
import org.mule.devkit.model.code.ClassType;
import org.mule.devkit.model.code.CodeModel;
import org.mule.devkit.model.code.CodeWriter;
import org.mule.devkit.model.code.DefinedClass;
import org.mule.devkit.model.code.ExpressionFactory;
import org.mule.devkit.model.code.FieldVariable;
import org.mule.devkit.model.code.Invocation;
import org.mule.devkit.model.code.Method;
import org.mule.devkit.model.code.Modifier;
import org.mule.devkit.model.code.Op;
import org.mule.devkit.model.code.TryStatement;
import org.mule.devkit.model.code.TypeReference;
import org.mule.devkit.model.code.Variable;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.annotation.PostConstruct;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
@MojoPhase("generate-sources")
@MojoGoal("generate-mashape-client")
@MojoRequiresDependencyResolution("runtime")
public class MashapeGeneratorMojo extends AbstractMuleMojo {
@MojoComponent
private MavenProjectHelper projectHelper;
/**
* Directory containing the XML definitions for the services
*/
@MojoParameter(expression = "${project.basedir}/src/main/resources", required = true)
private File inputFolder;
@MojoParameter(expression = "${project.build.directory}/generated-sources/mashape", required = true)
private File defaultOutputDirectory;
@MojoParameter(required = false)
private String mashapeName;
@MojoParameter(required = false)
private String mashapeVersion;
@MojoParameter(required = false)
private String mashapeProxyHost;
/**
* Code Model
*/
private CodeModel codeModel;
public MashapeGeneratorMojo() {
codeModel = new CodeModel(new CodeWriter() {
@Override
public OutputStream openBinary(org.mule.devkit.model.code.Package pkg, String fileName) throws IOException {
File file = new File(defaultOutputDirectory, pkg.name().replace(".", "/") + "/" + fileName);
return FileUtils.openOutputStream(file);
}
@Override
public void close() throws IOException {
}
});
}
public void execute() throws MojoExecutionException, MojoFailureException {
if (inputFolder == null) {
return;
}
getLog().info("Source directory: " + defaultOutputDirectory.getAbsolutePath() + " added");
project.addCompileSourceRoot(defaultOutputDirectory.getAbsolutePath());
try {
traverse(inputFolder);
} catch (IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}
private void traverse(final File f) throws IOException, MojoExecutionException {
final File[] childs = f.listFiles();
if (childs != null) {
for (File child : childs) {
if (child.isDirectory()) {
traverse(child);
} else if (FilenameUtils.isExtension(child.getAbsolutePath(), "mashape")) {
generateMashapeClient(child);
}
}
}
}
private void generateMashapeClient(File inputFile) throws MojoExecutionException {
if( mashapeName == null ) {
throw new MojoExecutionException("You must specify a mashapeName");
}
if( mashapeVersion == null ) {
throw new MojoExecutionException("You must specify a mashapeVersion");
}
if( mashapeProxyHost == null ) {
throw new MojoExecutionException("You must specify a mashapeProxyHost");
}
getLog().info("Generating POJO for " + inputFile.getAbsolutePath());
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder db = null;
try {
db = factory.newDocumentBuilder();
Document dom = db.parse(inputFile);
DefinedClass clazz = codeModel._class(Modifier.ABSTRACT | Modifier.PUBLIC, "org.mule.modules.tinypayme.TinyPayMeConnector", ClassType.CLASS);
clazz.javadoc().add("Unkown\n\nAutomatically generated from Mashape XML file\n\n@author MuleSoft, Inc.");
AnnotationUse moduleAnnotation = clazz.annotate(Module.class);
moduleAnnotation.param("name", mashapeName);
moduleAnnotation.param("schemaVersion", mashapeVersion);
FieldVariable mashapePrivateKey = generateMashapePrivateKey(clazz);
FieldVariable mashapePublicKey = generateMashapePublicKeyField(clazz);
FieldVariable mashapeAuthorization = generateMashapeAuthorizationField(clazz);
generateInit(clazz, mashapePrivateKey, mashapePublicKey, mashapeAuthorization);
Node api = dom.getElementsByTagName("api").item(0);
NodeList methods = api.getChildNodes();
for (int i = 0; i < methods.getLength(); i++) {
Node method = methods.item(i);
if (!"method".equals(method.getNodeName())) {
continue;
}
Method restCallMethod = clazz.method(Modifier.PUBLIC | Modifier.ABSTRACT, ref(String.class), method.getAttributes().getNamedItem("name").getNodeValue());
restCallMethod._throws(ref(IOException.class));
restCallMethod.annotate(Processor.class);
AnnotationUse restCall = restCallMethod.annotate(RestCall.class);
Node url = null;
for (int j = 0; j < method.getChildNodes().getLength(); j++) {
if (!"url".equals(method.getChildNodes().item(j).getNodeName())) {
continue;
}
url = method.getChildNodes().item(j);
break;
}
String uri = url.getTextContent();
if (uri.contains("?")) {
uri = uri.split("\\?")[0];
}
restCall.param("uri", "https://" + mashapeProxyHost + uri);
if ("GET".equals(method.getAttributes().getNamedItem("http").getNodeValue())) {
restCall.param("method", ref(HttpMethod.class).staticRef("GET"));
} else if ("PUT".equals(method.getAttributes().getNamedItem("http").getNodeValue())) {
restCall.param("method", ref(HttpMethod.class).staticRef("PUT"));
} else if ("POST".equals(method.getAttributes().getNamedItem("http").getNodeValue())) {
restCall.param("method", ref(HttpMethod.class).staticRef("POST"));
}
Node parameters = null;
for (int j = 0; j < method.getChildNodes().getLength(); j++) {
if (!"parameters".equals(method.getChildNodes().item(j).getNodeName())) {
continue;
}
parameters = method.getChildNodes().item(j);
break;
}
for (int j = 0; j < parameters.getChildNodes().getLength(); j++) {
if (!"parameter".equals(parameters.getChildNodes().item(j).getNodeName())) {
continue;
}
Node parameter = parameters.getChildNodes().item(j);
Variable restCallParameter = restCallMethod.param(ref(String.class), StringUtils.uncapitalize(camel(parameter.getTextContent())));
if (uri.contains("{" + parameter.getTextContent() + "}")) {
AnnotationUse restUriParam = restCallParameter.annotate(ref(RestUriParam.class));
restUriParam.param("value", parameter.getTextContent());
} else {
AnnotationUse restQueryParam = restCallParameter.annotate(ref(RestQueryParam.class));
restQueryParam.param("value", parameter.getTextContent());
}
if (parameter.getAttributes().getNamedItem("optional") != null &&
"true".equals(parameter.getAttributes().getNamedItem("optional").getNodeValue())) {
restCallParameter.annotate(ref(Optional.class));
}
}
}
codeModel.build();
} catch (ParserConfigurationException e) {
throw new MojoExecutionException(e.getMessage(), e);
} catch (SAXException e) {
throw new MojoExecutionException(e.getMessage(), e);
} catch (IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
} catch (ClassAlreadyExistsException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}
private String camel(String uncamelCaseName) {
String result = "";
String[] parts = uncamelCaseName.split("_");
for (int i = 0; i < parts.length; i++) {
result += StringUtils.capitalize(parts[i].toLowerCase());
}
return result;
}
private FieldVariable generateMashapePrivateKey(DefinedClass clazz) {
FieldVariable mashapePrivateKey = clazz.field(Modifier.PRIVATE, ref(String.class), "mashapePrivateKey");
mashapePrivateKey.javadoc().add("Mashape Private Key");
mashapePrivateKey.annotate(Configurable.class);
Method setMashapePrivateKey = clazz.method(Modifier.PUBLIC, codeModel.VOID, "setMashapePrivateKey");
Variable thatMashapePrivateKey = setMashapePrivateKey.param(ref(String.class), "newMashapePrivateKey");
setMashapePrivateKey.body().assign(mashapePrivateKey, thatMashapePrivateKey);
return mashapePrivateKey;
}
private FieldVariable generateMashapePublicKeyField(DefinedClass clazz) {
FieldVariable mashapePublicKey = clazz.field(Modifier.PRIVATE, ref(String.class), "mashapePublicKey");
mashapePublicKey.javadoc().add("Mashape Public Key");
mashapePublicKey.annotate(Configurable.class);
Method setMashapePublicKey = clazz.method(Modifier.PUBLIC, codeModel.VOID, "setMashapePublicKey");
Variable thatMashapePublicKey = setMashapePublicKey.param(ref(String.class), "newMashapePublicKey");
setMashapePublicKey.body().assign(mashapePublicKey, thatMashapePublicKey);
return mashapePublicKey;
}
private FieldVariable generateMashapeAuthorizationField(DefinedClass clazz) {
FieldVariable mashapeAuthorization = clazz.field(Modifier.PRIVATE, ref(String.class), "mashapeAuthorization");
mashapeAuthorization.javadoc().add("Mashape Authorization");
AnnotationUse mashapeAuthorizationRestHeaderParam = mashapeAuthorization.annotate(ref(RestHeaderParam.class));
mashapeAuthorizationRestHeaderParam.param("value", "X-Mashape-Authorization");
Method getMashapeAuthorization = clazz.method(Modifier.PUBLIC, ref(String.class), "getMashapeAuthorization");
getMashapeAuthorization.body()._return(mashapeAuthorization);
return mashapeAuthorization;
}
private void generateInit(DefinedClass clazz, FieldVariable mashapePrivateKey, FieldVariable mashapePublicKey, FieldVariable mashapeAuthorization) {
Method init = clazz.method(Modifier.PUBLIC, codeModel.VOID, "init");
init.annotate(ref(PostConstruct.class));
TryStatement attempt = init.body()._try();
Variable uuid = attempt.body().decl(ref(String.class), "uuid", ref(UUID.class).staticInvoke("randomUUID").invoke("toString"));
Variable signingKey = attempt.body().decl(ref(SecretKeySpec.class), "signingKey", ExpressionFactory._new(ref(SecretKeySpec.class)).arg(mashapePrivateKey.invoke("getBytes")).arg("HmacSHA1"));
Variable mac = attempt.body().decl(ref(Mac.class), "mac", ref(Mac.class).staticInvoke("getInstance").arg("HmacSHA1"));
attempt.body().add(mac.invoke("init").arg(signingKey));
Variable rawHmac = attempt.body().decl(codeModel.BYTE.array(), "rawHmac", mac.invoke("doFinal").arg(uuid.invoke("getBytes")));
Variable hex = attempt.body().decl(codeModel.BYTE.array(), "hex", ExpressionFactory._new(ref(Hex.class)).invoke("encode").arg(rawHmac));
Variable headerValue = attempt.body().decl(ref(String.class), "headerValue", Op.plus(Op.plus(Op.plus(mashapePublicKey, ExpressionFactory.lit(":")), ExpressionFactory._new(ref(String.class)).arg(hex).arg("UTF-8")), uuid));
Invocation getHeaderValueBytes = ref(Base64.class).staticInvoke("encodeBase64String").arg(headerValue.invoke("getBytes"));
attempt.body().assign(mashapeAuthorization, getHeaderValueBytes.invoke("replace").arg("\r\n").arg(""));
CatchBlock catchUnsupportedEncodingExpception = attempt._catch(ref(UnsupportedEncodingException.class));
catchUnsupportedEncodingExpception.body()._throw(ExpressionFactory._new(ref(RuntimeException.class)));
CatchBlock catchNoSuchAlgorithmException = attempt._catch(ref(NoSuchAlgorithmException.class));
catchNoSuchAlgorithmException.body()._throw(ExpressionFactory._new(ref(RuntimeException.class)));
CatchBlock catchInvalidKeyException = attempt._catch(ref(InvalidKeyException.class));
catchInvalidKeyException.body()._throw(ExpressionFactory._new(ref(RuntimeException.class)));
}
private TypeReference ref(Class<?> clazz) {
return codeModel.ref(clazz);
}
}