blob: 25908885e3cad045b3ee0aa564a1f7750f8e12b6 [file] [log] [blame]
/*
* 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.File;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Generated;
import javax.lang.model.element.Modifier;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import org.apache.camel.component.extension.MetaDataExtension;
import org.apache.camel.component.servicenow.ServiceNowComponent;
import org.apache.camel.component.servicenow.annotations.ServiceNowSysParm;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.support.jsse.SSLContextParameters;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
/**
* Goal to generate DTOs for ServiceNow objects
*/
@Mojo(name = "generate", requiresProject = false, defaultPhase = LifecyclePhase.GENERATE_SOURCES)
public class CamelServiceNowGenerateMojo extends AbstractMojo {
/**
* Location of generated DTO files, defaults to target/generated-sources/camel-salesforce.
*/
@Parameter(
property = "camel.servicenow.output.directory",
defaultValue = "${project.build.directory}/generated-sources/camel-servicenow")
protected File outputDirectory;
/**
* Java package name for generated DTOs.
*/
@Parameter(
property = "camel.servicenow.output.package",
defaultValue = "org.apache.camel.servicenow.dto")
protected String packageName;
/**
* ServiceNow instance name.
*/
@Parameter(
property = "camel.servicenow.instance.name", required = true)
protected String instanceName;
/**
* ServiceName user name.
*/
@Parameter(
property = "camel.servicenow.user.name", required = true)
protected String userName;
/**
* ServiceNow user password.
*/
@Parameter(
property = "camel.servicenow.user.password", required = true)
protected String userPassword;
/**
* ServiceNow OAuth2 client id.
*/
@Parameter(
property = "camel.servicenow.oauth2.client.id")
protected String oauthClientId;
/**
* ServiceNow OAuth2 client secret.
*/
@Parameter(
property = "camel.servicenow.oauth2.client.secret")
protected String oauthClientSecret;
/**
* SSL Context parameters.
*/
@Parameter(
property = "camel.servicenow.ssl.parameters")
protected SSLContextParameters sslParameters;
/**
* ServiceNow objects for which DTOs must be generated.
*/
@Parameter(required = true)
protected List<String> objects = Collections.emptyList();
/**
* ServiceNow fields to include in generated DTOs.
*/
@Parameter
protected Map<String, String> fields = Collections.emptyMap();
/**
* ServiceNow fields to exclude from generated DTOs, fields explicit included
* have the precedence.
*/
@Parameter
protected Map<String, String> fieldsExcludePattern = Collections.emptyMap();
// ************************************
//
// ************************************
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
final DefaultCamelContext context = new DefaultCamelContext();
final ServiceNowComponent component = new ServiceNowComponent(context);
for (String objectName : objects) {
Map<String, Object> parameters = new HashMap<>();
parameters.put("instanceName", instanceName);
parameters.put("userName", userName);
parameters.put("password", userPassword);
parameters.put("oauthClientId", oauthClientId);
parameters.put("oauthClientSecret", oauthClientSecret);
parameters.put("objectType", "table");
parameters.put("objectName", objectName);
for (Map.Entry<String, String> entry : fields.entrySet()) {
parameters.put("object." + entry.getKey() + ".fields", entry.getValue());
}
for (Map.Entry<String, String> entry : fieldsExcludePattern.entrySet()) {
parameters.put("object." + entry.getKey() + ".fields.exclude.pattern", entry.getValue());
}
JsonNode schema = component.getExtension(MetaDataExtension.class)
.flatMap(e -> e.meta(parameters))
.flatMap(m -> Optional.ofNullable(m.getPayload(JsonNode.class)))
.orElseThrow(() -> new MojoExecutionException("Unable to get grab MetaData for object: " + objectName)
);
validateSchema(schema);
generateBean(objectName, schema);
}
}
// ************************************
// Beans
// ************************************
private void generateBean(String name, JsonNode schema) throws MojoExecutionException {
try {
TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(toCamelCase(name, false))
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addAnnotation(Generated.class)
.addAnnotation(AnnotationSpec.builder(ServiceNowSysParm.class)
.addMember("name", "$S", "sysparm_exclude_reference_link")
.addMember("value", "$S", "true")
.build())
.addAnnotation(AnnotationSpec.builder(JsonIgnoreProperties.class)
.addMember("ignoreUnknown", "$L", "true")
.build())
.addAnnotation(AnnotationSpec.builder(JsonInclude.class)
.addMember("value", "$L", "JsonInclude.Include.NON_NULL")
.build());
schema.get("properties").fields().forEachRemaining(
entry -> generateBeanProperty(typeBuilder, schema, entry.getKey(), entry.getValue())
);
JavaFile.builder(packageName, typeBuilder.build())
.indent(" ")
.build()
.writeTo(outputDirectory);
} catch (IOException e) {
throw new MojoExecutionException("Unable to generate Class", e);
}
}
private void generateBeanProperty(TypeSpec.Builder typeBuilder, JsonNode schema, String name, JsonNode definition) {
final ArrayNode required = (ArrayNode)schema.get("required");
final String fieldName = toCamelCase(name, true);
final String methodName = toCamelCase(name, false);
final JsonNode type = definition.get("type");
final JsonNode format = definition.get("format");
Class<?> javaType = String.class;
if (type != null) {
if ("boolean".equalsIgnoreCase(type.textValue())) {
javaType = boolean.class;
}
if ("integer".equalsIgnoreCase(type.textValue())) {
javaType = Integer.class;
}
if ("number".equalsIgnoreCase(type.textValue())) {
javaType = Double.class;
}
if ("string".equalsIgnoreCase(type.textValue())) {
javaType = String.class;
}
}
if (javaType == String.class && format != null) {
if ("date".equalsIgnoreCase(format.textValue())) {
javaType = LocalDate.class;
}
if ("time".equalsIgnoreCase(format.textValue())) {
javaType = LocalTime.class;
}
if ("date-time".equalsIgnoreCase(format.textValue())) {
javaType = LocalDateTime.class;
}
}
FieldSpec field = FieldSpec.builder(javaType, toCamelCase(name, true))
.addModifiers(Modifier.PRIVATE)
.addAnnotation(AnnotationSpec.builder(JsonProperty.class)
.addMember("value", "$S", name)
.addMember("required", "$L", required.has(name))
.build())
.build();
MethodSpec getter = MethodSpec.methodBuilder("get" + methodName)
.addModifiers(Modifier.PUBLIC)
.returns(javaType)
.addStatement("return this.$L", fieldName)
.build();
MethodSpec setter = MethodSpec.methodBuilder("set" + methodName)
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(javaType, fieldName)
.addStatement("this.$L = $L", fieldName, fieldName)
.build();
typeBuilder.addField(field);
typeBuilder.addMethod(getter);
typeBuilder.addMethod(setter);
}
// ************************************
// Helpers
// ************************************
private String toCamelCase(String text, boolean lowerCaseFirst) {
String result = Stream.of(text.split("[^a-zA-Z0-9]"))
.map(v -> v.substring(0, 1).toUpperCase() + v.substring(1).toLowerCase())
.collect(Collectors.joining());
if (lowerCaseFirst) {
result = result.substring(0, 1).toLowerCase() + result.substring(1);
}
return result;
}
private Optional<String> getNodeTextValue(JsonNode root, String... path) {
return getNode(root, path).map(JsonNode::asText);
}
private Optional<JsonNode> getNode(JsonNode root, String... path) {
JsonNode node = root;
for (String name : path) {
node = node.get(name);
if (node == null) {
break;
}
}
return Optional.ofNullable(node);
}
private void validateSchema(JsonNode schema) throws MojoExecutionException {
getNode(schema, "required")
.orElseThrow(() -> new MojoExecutionException("Invalid JsonSchema: 'required' element not found"));
}
}