org.apache.camel.maven.CamelSalesforceMojo Maven / Gradle / Ivy
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.camel.maven;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import org.apache.camel.component.salesforce.SalesforceEndpointConfig;
import org.apache.camel.component.salesforce.SalesforceLoginConfig;
import org.apache.camel.component.salesforce.api.SalesforceException;
import org.apache.camel.component.salesforce.api.dto.AbstractSObjectBase;
import org.apache.camel.component.salesforce.api.dto.GlobalObjects;
import org.apache.camel.component.salesforce.api.dto.PickListValue;
import org.apache.camel.component.salesforce.api.dto.SObject;
import org.apache.camel.component.salesforce.api.dto.SObjectDescription;
import org.apache.camel.component.salesforce.api.dto.SObjectField;
import org.apache.camel.component.salesforce.internal.PayloadFormat;
import org.apache.camel.component.salesforce.internal.SalesforceSession;
import org.apache.camel.component.salesforce.internal.client.DefaultRestClient;
import org.apache.camel.component.salesforce.internal.client.RestClient;
import org.apache.camel.component.salesforce.internal.client.SyncResponseCallback;
import org.apache.log4j.Logger;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.log.Log4JLogChute;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.codehaus.jackson.map.ObjectMapper;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.RedirectListener;
/**
* Goal which generates POJOs for Salesforce SObjects
*/
@Mojo(name = "generate", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
public class CamelSalesforceMojo extends AbstractMojo {
private static final String JAVA_EXT = ".java";
private static final String PACKAGE_NAME_PATTERN = "^[a-z]+(\\.[a-z][a-z0-9]*)*$";
private static final String SOBJECT_POJO_VM = "/sobject-pojo.vm";
private static final String SOBJECT_QUERY_RECORDS_VM = "/sobject-query-records.vm";
private static final String SOBJECT_PICKLIST_VM = "/sobject-picklist.vm";
// used for velocity logging, to avoid creating velocity.log
private static final Logger LOG = Logger.getLogger(CamelSalesforceMojo.class.getName());
private static final int TIMEOUT = 60000;
/**
* Salesforce client id
*/
@Parameter(property = "camelSalesforce.clientId", required = true)
protected String clientId;
/**
* Salesforce client secret
*/
@Parameter(property = "camelSalesforce.clientSecret", required = true)
protected String clientSecret;
/**
* Salesforce user name
*/
@Parameter(property = "camelSalesforce.userName", required = true)
protected String userName;
/**
* Salesforce password
*/
@Parameter(property = "camelSalesforce.password", required = true)
protected String password;
/**
* Salesforce version
*/
@Parameter(property = "camelSalesforce.version", defaultValue = SalesforceEndpointConfig.DEFAULT_VERSION)
protected String version;
/**
* Location of the file.
*/
@Parameter(property = "camelSalesforce.outputDirectory",
defaultValue = "${project.build.directory}/generated-sources/camel-salesforce")
protected File outputDirectory;
/**
* Names of Salesforce SObject for which POJOs must be generated
*/
@Parameter
protected String[] includes;
/**
* Do NOT generate POJOs for these Salesforce SObjects
*/
@Parameter
protected String[] excludes;
/**
* Include Salesforce SObjects that match pattern
*/
@Parameter(property = "camelSalesforce.includePattern")
protected String includePattern;
/**
* Exclude Salesforce SObjects that match pattern
*/
@Parameter(property = "camelSalesforce.excludePattern")
protected String excludePattern;
/**
* Java package name for generated POJOs
*/
@Parameter(property = "camelSalesforce.packageName", defaultValue = "org.apache.camel.salesforce.dto")
protected String packageName;
private VelocityEngine engine;
/**
* Execute the mojo to generate SObject POJOs
*
* @throws MojoExecutionException
*/
// CHECKSTYLE:OFF
public void execute() throws MojoExecutionException {
// initialize velocity to load resources from class loader and use Log4J
Properties velocityProperties = new Properties();
velocityProperties.setProperty(RuntimeConstants.RESOURCE_LOADER, "cloader");
velocityProperties.setProperty("cloader.resource.loader.class", ClasspathResourceLoader.class.getName());
velocityProperties.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, Log4JLogChute.class.getName());
velocityProperties.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM + ".log4j.logger", LOG.getName());
engine = new VelocityEngine(velocityProperties);
engine.init();
// make sure we can load both templates
if (!engine.resourceExists(SOBJECT_POJO_VM) || !engine.resourceExists(SOBJECT_QUERY_RECORDS_VM)) {
throw new MojoExecutionException("Velocity templates not found");
}
// connect to Salesforce
final HttpClient httpClient = new HttpClient();
httpClient.registerListener(RedirectListener.class.getName());
httpClient.setConnectTimeout(TIMEOUT);
httpClient.setTimeout(TIMEOUT);
try {
httpClient.start();
} catch (Exception e) {
throw new MojoExecutionException("Error creating HTTP client: " + e.getMessage(), e);
}
final SalesforceSession session = new SalesforceSession(httpClient,
new SalesforceLoginConfig(SalesforceLoginConfig.DEFAULT_LOGIN_URL,
clientId, clientSecret, userName, password, false));
getLog().info("Salesforce login...");
try {
session.login(null);
} catch (SalesforceException e) {
String msg = "Salesforce login error " + e.getMessage();
throw new MojoExecutionException(msg, e);
}
getLog().info("Salesforce login successful");
// create rest client
RestClient restClient;
try {
restClient = new DefaultRestClient(httpClient,
version, PayloadFormat.JSON, session);
// remember to start the active client object
((DefaultRestClient) restClient).start();
} catch (Exception e) {
final String msg = "Unexpected exception creating Rest client: " + e.getMessage();
throw new MojoExecutionException(msg, e);
}
try {
// use Jackson json
final ObjectMapper mapper = new ObjectMapper();
// call getGlobalObjects to get all SObjects
final Set objectNames = new HashSet();
final SyncResponseCallback callback = new SyncResponseCallback();
try {
getLog().info("Getting Salesforce Objects...");
restClient.getGlobalObjects(callback);
if (!callback.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
throw new MojoExecutionException("Timeout waiting for getGlobalObjects!");
}
final SalesforceException ex = callback.getException();
if (ex != null) {
throw ex;
}
final GlobalObjects globalObjects = mapper.readValue(callback.getResponse(),
GlobalObjects.class);
// create a list of object names
for (SObject sObject : globalObjects.getSobjects()) {
objectNames.add(sObject.getName());
}
} catch (Exception e) {
String msg = "Error getting global Objects " + e.getMessage();
throw new MojoExecutionException(msg, e);
}
// check if we are generating POJOs for all objects or not
if ((includes != null && includes.length > 0)
|| (excludes != null && excludes.length > 0)
|| (includePattern != null && !includePattern.trim().isEmpty())
|| (excludePattern != null && !excludePattern.trim().isEmpty())) {
getLog().info("Looking for matching Object names...");
// create a list of accepted names
final Set includedNames = new HashSet();
if (includes != null && includes.length > 0) {
for (String name : includes) {
name = name.trim();
if (name.isEmpty()) {
throw new MojoExecutionException("Invalid empty name in includes");
}
includedNames.add(name);
}
}
final Set excludedNames = new HashSet();
if (excludes != null && excludes.length > 0) {
for (String name : excludes) {
name = name.trim();
if (name.isEmpty()) {
throw new MojoExecutionException("Invalid empty name in excludes");
}
excludedNames.add(name);
}
}
// check whether a pattern is in effect
Pattern incPattern;
if (includePattern != null && !includePattern.trim().isEmpty()) {
incPattern = Pattern.compile(includePattern.trim());
} else if (includedNames.isEmpty()) {
// include everything by default if no include names are set
incPattern = Pattern.compile(".*");
} else {
// include nothing by default if include names are set
incPattern = Pattern.compile("^$");
}
// check whether a pattern is in effect
Pattern excPattern;
if (excludePattern != null && !excludePattern.trim().isEmpty()) {
excPattern = Pattern.compile(excludePattern.trim());
} else {
// exclude nothing by default
excPattern = Pattern.compile("^$");
}
final Set acceptedNames = new HashSet();
for (String name : objectNames) {
// name is included, or matches include pattern
// and is not excluded and does not match exclude pattern
if ((includedNames.contains(name) || incPattern.matcher(name).matches())
&& !excludedNames.contains(name) && !excPattern.matcher(name).matches()) {
acceptedNames.add(name);
}
}
objectNames.clear();
objectNames.addAll(acceptedNames);
getLog().info(String.format("Found %s matching Objects", objectNames.size()));
} else {
getLog().warn(String.format("Generating Java classes for all %s Objects, this may take a while...", objectNames.size()));
}
// for every accepted name, get SObject description
final Set descriptions = new HashSet();
try {
getLog().info("Retrieving Object descriptions...");
for (String name : objectNames) {
callback.reset();
restClient.getDescription(name, callback);
if (!callback.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
throw new MojoExecutionException("Timeout waiting for getDescription for sObject " + name);
}
final SalesforceException ex = callback.getException();
if (ex != null) {
throw ex;
}
descriptions.add(mapper.readValue(callback.getResponse(), SObjectDescription.class));
}
} catch (Exception e) {
String msg = "Error getting SObject description " + e.getMessage();
throw new MojoExecutionException(msg, e);
}
// create package directory
// validate package name
if (!packageName.matches(PACKAGE_NAME_PATTERN)) {
throw new MojoExecutionException("Invalid package name " + packageName);
}
final File pkgDir = new File(outputDirectory, packageName.trim().replace('.', File.separatorChar));
if (!pkgDir.exists()) {
if (!pkgDir.mkdirs()) {
throw new MojoExecutionException("Unable to create " + pkgDir);
}
}
getLog().info("Generating Java Classes...");
// generate POJOs for every object description
final GeneratorUtility utility = new GeneratorUtility();
// should we provide a flag to control timestamp generation?
final String generatedDate = new Date().toString();
for (SObjectDescription description : descriptions) {
processDescription(pkgDir, description, utility, generatedDate);
}
getLog().info(String.format("Successfully generated %s Java Classes", descriptions.size() * 2));
} finally {
// remember to stop the client
try {
((DefaultRestClient) restClient).stop();
} catch (Exception ignore) {
}
// Salesforce session stop
try {
session.stop();
} catch (Exception ignore) {
}
// release HttpConnections
try {
httpClient.stop();
} catch (Exception ignore) {
}
}
}
// CHECKSTYLE:ON
private void processDescription(File pkgDir, SObjectDescription description, GeneratorUtility utility, String generatedDate) throws MojoExecutionException {
// generate a source file for SObject
String fileName = description.getName() + JAVA_EXT;
BufferedWriter writer = null;
try {
final File pojoFile = new File(pkgDir, fileName);
writer = new BufferedWriter(new FileWriter(pojoFile));
VelocityContext context = new VelocityContext();
context.put("packageName", packageName);
context.put("utility", utility);
context.put("desc", description);
context.put("generatedDate", generatedDate);
Template pojoTemplate = engine.getTemplate(SOBJECT_POJO_VM);
pojoTemplate.merge(context, writer);
// close pojoFile
writer.close();
// write required Enumerations for any picklists
for (SObjectField field : description.getFields()) {
if (utility.isPicklist(field)) {
fileName = utility.enumTypeName(field.getName()) + JAVA_EXT;
File enumFile = new File(pkgDir, fileName);
writer = new BufferedWriter(new FileWriter(enumFile));
context = new VelocityContext();
context.put("packageName", packageName);
context.put("utility", utility);
context.put("field", field);
context.put("generatedDate", generatedDate);
Template queryTemplate = engine.getTemplate(SOBJECT_PICKLIST_VM);
queryTemplate.merge(context, writer);
// close Enum file
writer.close();
}
}
// write the QueryRecords class
fileName = "QueryRecords" + description.getName() + JAVA_EXT;
File queryFile = new File(pkgDir, fileName);
writer = new BufferedWriter(new FileWriter(queryFile));
context = new VelocityContext();
context.put("packageName", packageName);
context.put("desc", description);
context.put("generatedDate", generatedDate);
Template queryTemplate = engine.getTemplate(SOBJECT_QUERY_RECORDS_VM);
queryTemplate.merge(context, writer);
// close QueryRecords file
writer.close();
} catch (Exception e) {
String msg = "Error creating " + fileName + ": " + e.getMessage();
throw new MojoExecutionException(msg, e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException ignore) {
}
}
}
}
public static class GeneratorUtility {
private static final Set BASE_FIELDS;
private static final Map LOOKUP_MAP;
static {
BASE_FIELDS = new HashSet();
for (Field field : AbstractSObjectBase.class.getDeclaredFields()) {
BASE_FIELDS.add(field.getName());
}
// create a type map
// using JAXB mapping, for the most part
// uses Joda time instead of XmlGregorianCalendar
// TODO do we need support for commented types???
final String[][] typeMap = new String[][]{
{"ID", "String"}, // mapping for tns:ID SOAP type
{"string", "String"},
{"integer", "java.math.BigInteger"},
{"int", "Integer"},
{"long", "Long"},
{"short", "Short"},
{"decimal", "java.math.BigDecimal"},
{"float", "Float"},
{"double", "Double"},
{"boolean", "Boolean"},
{"byte", "Byte"},
// {"QName", "javax.xml.namespace.QName"},
// {"dateTime", "javax.xml.datatype.XMLGregorianCalendar"},
{"dateTime", "org.joda.time.DateTime"},
// the blob base64Binary type is mapped to String URL for retrieving the blob
{"base64Binary", "String"},
// {"hexBinary", "byte[]"},
{"unsignedInt", "Long"},
{"unsignedShort", "Integer"},
{"unsignedByte", "Short"},
// {"time", "javax.xml.datatype.XMLGregorianCalendar"},
{"time", "org.joda.time.DateTime"},
// {"date", "javax.xml.datatype.XMLGregorianCalendar"},
{"date", "org.joda.time.DateTime"},
// {"g", "javax.xml.datatype.XMLGregorianCalendar"},
{"g", "org.joda.time.DateTime"},
// Salesforce maps any types like string, picklist, reference, etc. to string
{"anyType", "String"},
/*
{"anySimpleType", "java.lang.Object"},
{"anySimpleType", "java.lang.String"},
{"duration", "javax.xml.datatype.Duration"},
{"NOTATION", "javax.xml.namespace.QName"}
*/
};
LOOKUP_MAP = new HashMap();
for (String[] entry : typeMap) {
LOOKUP_MAP.put(entry[0], entry[1]);
}
}
private static final String BASE64BINARY = "base64Binary";
public boolean isBlobField(SObjectField field) {
final String soapType = field.getSoapType();
return BASE64BINARY.equals(soapType.substring(soapType.indexOf(':') + 1));
}
public boolean notBaseField(String name) {
return !BASE_FIELDS.contains(name);
}
public String getFieldType(SObjectField field) throws MojoExecutionException {
// check if this is a picklist
if (isPicklist(field)) {
// use a pick list enum, which will be created after generating the SObject class
return enumTypeName(field.getName());
} else {
// map field to Java type
final String soapType = field.getSoapType();
final String type = LOOKUP_MAP.get(soapType.substring(soapType.indexOf(':') + 1));
if (type == null) {
throw new MojoExecutionException(
String.format("Unsupported type %s for field %s", soapType, field.getName()));
}
return type;
}
}
public boolean hasPicklists(SObjectDescription desc) {
for (SObjectField field : desc.getFields()) {
if (isPicklist(field)) {
return true;
}
}
return false;
}
public PickListValue getLastEntry(SObjectField field) {
final List values = field.getPicklistValues();
return values.get(values.size() - 1);
}
public boolean isPicklist(SObjectField field) {
return field.getPicklistValues() != null && !field.getPicklistValues().isEmpty();
}
public String enumTypeName(String name) {
name = name.endsWith("__c") ? name.substring(0, name.length() - 3) : name;
return name + "Enum";
}
public String getEnumConstant(String value) {
// TODO add support for supplementary characters
final StringBuilder result = new StringBuilder();
boolean changed = false;
if (!Character.isJavaIdentifierStart(value.charAt(0))) {
result.append("_");
changed = true;
}
for (char c : value.toCharArray()) {
if (Character.isJavaIdentifierPart(c)) {
result.append(c);
} else {
// replace non Java identifier character with '_'
result.append('_');
changed = true;
}
}
return changed ? result.toString().toUpperCase() : value.toUpperCase();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy