/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012-2014 ForgeRock AS. All Rights Reserved
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* http://forgerock.org/license/CDDLv1.0.html
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at http://forgerock.org/license/CDDLv1.0.html
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*/
package org.forgerock.openicf.maven;
import java.io.Writer;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.shared.artifact.filter.PatternExcludesArtifactFilter;
import org.apache.maven.shared.artifact.filter.PatternIncludesArtifactFilter;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.VelocityException;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.PathTool;
import org.codehaus.plexus.util.ReaderFactory;
import org.identityconnectors.common.CollectionUtil;
import org.identityconnectors.common.Pair;
import org.identityconnectors.framework.api.APIConfiguration;
import org.identityconnectors.framework.api.ConfigurationProperty;
import org.identityconnectors.framework.api.ConnectorFacadeFactory;
import org.identityconnectors.framework.api.ConnectorInfo;
import org.identityconnectors.framework.api.ConnectorInfoManager;
import org.identityconnectors.framework.api.ConnectorInfoManagerFactory;
import org.identityconnectors.framework.api.operations.APIOperation;
import org.identityconnectors.framework.api.operations.AuthenticationApiOp;
import org.identityconnectors.framework.api.operations.CreateApiOp;
import org.identityconnectors.framework.api.operations.DeleteApiOp;
import org.identityconnectors.framework.api.operations.GetApiOp;
import org.identityconnectors.framework.api.operations.ResolveUsernameApiOp;
import org.identityconnectors.framework.api.operations.SchemaApiOp;
import org.identityconnectors.framework.api.operations.ScriptOnConnectorApiOp;
import org.identityconnectors.framework.api.operations.ScriptOnResourceApiOp;
import org.identityconnectors.framework.api.operations.SearchApiOp;
import org.identityconnectors.framework.api.operations.SyncApiOp;
import org.identityconnectors.framework.api.operations.TestApiOp;
import org.identityconnectors.framework.api.operations.UpdateApiOp;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.ObjectClassInfo;
import org.identityconnectors.framework.common.objects.OperationOptionInfo;
import org.identityconnectors.framework.common.objects.Schema;
import org.identityconnectors.framework.impl.api.local.LocalConnectorInfoImpl;
import org.identityconnectors.framework.spi.Connector;
import org.identityconnectors.framework.spi.operations.SchemaOp;
/**
* A ConnectorDocBuilder .
*
* @author Laszlo Hordos
*/
public class ConnectorDocBuilder {
private final ConnectorMojoBridge handler;
public ConnectorDocBuilder(ConnectorMojoBridge bridge) {
this.handler = bridge;
}
protected static final List<Class<? extends APIOperation>> OBJECTCLASS_OPERATIONS =
CollectionUtil.newList(AuthenticationApiOp.class, CreateApiOp.class, DeleteApiOp.class,
ResolveUsernameApiOp.class, SearchApiOp.class, SyncApiOp.class,
UpdateApiOp.class);
protected static final List<Class<? extends APIOperation>> OPERATIONS = CollectionUtil.newList(
AuthenticationApiOp.class, CreateApiOp.class, DeleteApiOp.class,
ResolveUsernameApiOp.class, SchemaApiOp.class, ScriptOnConnectorApiOp.class,
ScriptOnResourceApiOp.class, SearchApiOp.class, SyncApiOp.class, TestApiOp.class,
UpdateApiOp.class);
protected static final Map<Class<? extends APIOperation>, Pair<String, String>> OP_DICTIONARY;
static {
Map<Class<? extends APIOperation>, Pair<String, String>> dictionary =
new HashMap<Class<? extends APIOperation>, Pair<String, String>>();
dictionary.put(AuthenticationApiOp.class, Pair.of(
AuthenticationApiOp.class.getSimpleName(), "Authenticate"));
dictionary.put(CreateApiOp.class, Pair.of(CreateApiOp.class.getSimpleName(), "Create"));
dictionary.put(DeleteApiOp.class, Pair.of(DeleteApiOp.class.getSimpleName(), "Delete"));
dictionary.put(ResolveUsernameApiOp.class, Pair.of(ResolveUsernameApiOp.class
.getSimpleName(), "Resolve Username"));
dictionary.put(SchemaApiOp.class, Pair.of(SchemaApiOp.class.getSimpleName(), "Schema"));
dictionary.put(ScriptOnConnectorApiOp.class, Pair.of(ScriptOnConnectorApiOp.class
.getSimpleName(), "Script on Connector"));
dictionary.put(ScriptOnResourceApiOp.class, Pair.of(ScriptOnResourceApiOp.class
.getSimpleName(), "Script on Resource"));
dictionary.put(SearchApiOp.class, Pair.of(SearchApiOp.class.getSimpleName(), "Search"));
dictionary.put(GetApiOp.class, Pair.of(GetApiOp.class.getSimpleName(), "Read"));
dictionary.put(SyncApiOp.class, Pair.of(SyncApiOp.class.getSimpleName(), "Sync"));
dictionary.put(TestApiOp.class, Pair.of(TestApiOp.class.getSimpleName(), "Test"));
dictionary.put(UpdateApiOp.class, Pair.of(UpdateApiOp.class.getSimpleName(), "Update"));
OP_DICTIONARY = CollectionUtil.newReadOnlyMap(dictionary);
}
/**
* Execute the generation of the report.
*
* @throws org.apache.maven.reporting.MavenReportException
* if any
*/
protected void executeReport() throws MojoExecutionException {
List<ConnectorInfo> infoList = null;
try {
infoList = listAllConnectorInfo();
} catch (Exception e) {
handler.getLog().error("Failed to get the ConnectorInfoManager", e);
return;
}
for (ConnectorInfo info : infoList) {
handler.getLog().debug("Processing ConnectorInfo: " + info.toString());
try {
Context context = new VelocityContext();
context.put("connectorInfo", info);
context.put("connectorDisplayName", info.getConnectorDisplayName());
context.put("connectorCategory", info.getConnectorCategory());
context.put("bookName", handler.getMavenProject().getArtifactId()+"-"+handler.getMavenProject().getVersion());
String connectorName =
info.getConnectorKey().getConnectorName().substring(
info.getConnectorKey().getConnectorName().lastIndexOf('.') + 1)
.toLowerCase();
if (connectorName.endsWith("connector")) {
connectorName =
connectorName.substring(0, connectorName.length() - 9) + "-connector";
}
context.put("connectorName", connectorName);
context.put("uniqueConnectorName", info.getConnectorKey().getConnectorName().replaceAll("\\.","-") + "-"
+ info.getConnectorKey().getBundleVersion());
APIConfiguration config = info.createDefaultAPIConfiguration();
context.put("APIConfiguration", config);
try {
if (config.getSupportedOperations().contains(SchemaApiOp.class)) {
Schema schema = null;
try {
APIConfiguration facadeConfig = info.createDefaultAPIConfiguration();
if (null != handler.getConfigurationProperties()) {
handler.getConfigurationProperties().mergeConfigurationProperties(
facadeConfig.getConfigurationProperties());
}
schema =
ConnectorFacadeFactory.getInstance().newInstance(facadeConfig)
.schema();
} catch (Throwable t) {
handler.getLog().debug("Getting Schema with ConnectorFacade", t);
}
if (null == schema && info instanceof LocalConnectorInfoImpl) {
Class<? extends Connector> connectorClass =
((LocalConnectorInfoImpl) info).getConnectorClass();
try {
SchemaOp connector = (SchemaOp) connectorClass.newInstance();
schema = connector.schema();
} catch (Throwable t) {
handler.getLog().debug("Getting Schema with Connector Instance", t);
}
}
if (null != schema) {
SortedMap<Pair<String, String>, List<Map<String, Object>>> operationOptionsMap =
new TreeMap<Pair<String, String>, List<Map<String, Object>>>(
PAIR_COMPARATOR);
for (Class<? extends APIOperation> op : OPERATIONS) {
if (SchemaApiOp.class.equals(op) || TestApiOp.class.equals(op)) {
continue;
}
List<Map<String, Object>> optionList = null;
for (OperationOptionInfo optionInfo : schema
.getSupportedOptionsByOperation(op)) {
Map<String, Object> optionInfoMap =
new HashMap<String, Object>();
optionInfoMap.put("name", optionInfo.getName());
optionInfoMap.put("type", optionInfo.getType().getSimpleName());
optionInfoMap.put("description", info.getMessages().format(
optionInfo.getName() + ".help", "Additional description is not available"));
if (null == optionList) {
optionList = new ArrayList<Map<String, Object>>();
}
optionList.add(optionInfoMap);
}
if (null != optionList) {
operationOptionsMap.put(OP_DICTIONARY.get(op), optionList);
}
}
List<Map<String, Object>> objectClasses =
new ArrayList<Map<String, Object>>();
Set<Class<? extends APIOperation>> operationSet =
new TreeSet<Class<? extends APIOperation>>(
new Comparator<Class<? extends APIOperation>>() {
public int compare(
Class<? extends APIOperation> o1,
Class<? extends APIOperation> o2) {
return String.CASE_INSENSITIVE_ORDER.compare(o1
.getCanonicalName(), o2
.getCanonicalName());
}
});
operationSet.addAll(config.getSupportedOperations());
operationSet.retainAll(OBJECTCLASS_OPERATIONS);
for (ObjectClassInfo objectClassInfo : schema.getObjectClassInfo()) {
Map<String, Object> objectClassInfoMap =
new HashMap<String, Object>();
ObjectClass oc = new ObjectClass(objectClassInfo.getType());
objectClassInfoMap.put("name", objectClassInfo.getType());
objectClassInfoMap.put("displayName", info.getMessages().format(
oc.getDisplayNameKey(), oc.getObjectClassValue()));
objectClassInfoMap.put("attributes", objectClassInfo
.getAttributeInfo());
boolean limited = false;
List<Pair<String, String>> operations =
new ArrayList<Pair<String, String>>();
for (Class<? extends APIOperation> op : operationSet) {
if (schema.getSupportedObjectClassesByOperation(op).contains(
objectClassInfo)) {
operations.add(OP_DICTIONARY.get(op));
} else {
limited = true;
}
}
objectClassInfoMap.put("operations", limited ? operations : null);
objectClasses.add(objectClassInfoMap);
}
context.put("schema", schema);
context.put("operationOptions", operationOptionsMap);
context.put("objectClasses", objectClasses);
}
}
} catch (Throwable e) {
if (handler.getLog().isDebugEnabled()) {
handler.getLog().debug("Getting the default Schema.", e);
}
}
try {
Set<Pair<String, String>> interfaces =
new TreeSet<Pair<String, String>>(PAIR_COMPARATOR);
for (Class<? extends APIOperation> clazz : config.getSupportedOperations()) {
if (OP_DICTIONARY.containsKey(clazz)) {
interfaces.add(OP_DICTIONARY.get(clazz));
}
}
context.put("connectorInterfaces", interfaces);
} catch (Throwable e) {
handler.getLog().error("Getting the connector interfaces.", e);
}
Map<String, List<Map<String, Object>>> configurationTable =
new LinkedHashMap<String, List<Map<String, Object>>>();
for (String propertyName : config.getConfigurationProperties().getPropertyNames()) {
ConfigurationProperty property =
config.getConfigurationProperties().getProperty(propertyName);
String groupKey = property.getGroup("Configuration");
List<Map<String, Object>> configurationGroup = configurationTable.get(groupKey);
if (configurationGroup == null) {
configurationGroup = new ArrayList<Map<String, Object>>();
configurationTable.put(groupKey, configurationGroup);
}
Map<String, Object> propertyMap = new HashMap<String, Object>();
propertyMap.put("name", propertyName);
propertyMap.put("type", property.getType().getSimpleName());
propertyMap.put("property", property);
propertyMap.put("required", property.isRequired());
propertyMap.put("operations", convertOperations(property.getOperations()));
propertyMap.put("confidential", property.isConfidential());
propertyMap.put("description", convertHTMLtoDocBook(property
.getHelpMessage("Description is not available")));
configurationGroup.add(propertyMap);
}
context.put("configurationProperties", configurationTable);
context.put("connectorPoolingSupported", config.isConnectorPoolingSupported());
context.put("PathTool", new PathTool());
context.put("FileUtils", new FileUtils());
context.put("StringUtils", new org.codehaus.plexus.util.StringUtils());
context.put("ConnectorUtils", new ConnectorUtils());
context.put("i18n", handler.getI18N());
context.put("project", handler.getMavenProject());
handler.generate(this, context, connectorName);
} catch (ResourceNotFoundException e) {
throw new MojoExecutionException("Resource not found.", e);
} catch (VelocityException e) {
throw new MojoExecutionException(e.toString(), e);
}
}
}
private String convertHTMLtoDocBook(String source) {
return source;
}
private Set<Pair<String, String>> convertOperations(
Set<Class<? extends APIOperation>> operations) {
if (null != operations) {
Set<Pair<String, String>> operationNames =
new TreeSet<Pair<String, String>>(PAIR_COMPARATOR);
for (Class<? extends APIOperation> op : operations) {
operationNames.add(OP_DICTIONARY.get(op));
}
return operationNames;
}
return null;
}
/**
* Create the velocity template
*
* @param context
* velocity context that has the parameter values
* @param templateName
* velocity template which will the context be merged
* @throws org.apache.velocity.exception.ResourceNotFoundException
* , VelocityException, IOException
*/
public void processTemplate(VelocityEngine engine, Context context, String templateName,
Writer writer) throws MojoExecutionException {
try {
engine.setApplicationAttribute("baseDirectory", handler.getBasedir());
String sourceEncoding = handler.getSourceEncoding();
if (StringUtils.isEmpty(sourceEncoding)) {
sourceEncoding = ReaderFactory.FILE_ENCODING;
handler.getLog().warn(
"File encoding has not been set, using platform encoding "
+ handler.getSourceEncoding()
+ ", i.e. build is platform dependent!");
}
Template velocityTemplate =
engine.getTemplate(handler.getTemplateDirectory() + "/" + templateName,
sourceEncoding);
velocityTemplate.merge(context, writer);
} catch (ResourceNotFoundException e) {
throw new ResourceNotFoundException("Template not found. ( "
+ handler.getTemplateDirectory() + "/" + templateName + " )");
} catch (VelocityException e) {
throw new VelocityException(e.toString());
} catch (Exception e) {
if (e.getCause() != null) {
handler.getLog().warn(e.getCause());
}
throw new MojoExecutionException(e.toString(), e.getCause());
}
}
public List<ConnectorInfo> listAllConnectorInfo() throws MojoExecutionException {
List<ConnectorInfo> infos = new ArrayList<ConnectorInfo>();
PatternIncludesArtifactFilter includesFilter =
handler.getIncludes() == null ? null : new PatternIncludesArtifactFilter(Arrays
.asList(handler.getIncludes()));
PatternExcludesArtifactFilter excludesFilter =
handler.getExcludes() == null ? null : new PatternExcludesArtifactFilter(Arrays
.asList(handler.getExcludes()));
for (ConnectorInfo info : getConnectorInfoManager().getConnectorInfos()) {
Artifact artifact =
new DefaultArtifact(info.getConnectorKey().getBundleName(), info
.getConnectorKey().getConnectorName(), info.getConnectorKey()
.getBundleVersion(), "compile", info.getConnectorKey()
.getBundleVersion(), "", null);
if (includesFilter != null && !includesFilter.include(artifact)) {
handler.getLog().debug(
"Connector " + info.getConnectorKey()
+ " ignored as it does not match include rules.");
continue;
}
if (excludesFilter != null && !excludesFilter.include(artifact)) {
handler.getLog().debug(
"Connector " + info.getConnectorKey()
+ " ignored as it does matches exclude rules.");
continue;
}
handler.getLog().info("Processing ConnectorInfo: " + info.getConnectorKey());
infos.add(info);
}
return infos;
}
public ConnectorInfoManager getConnectorInfoManager() throws MojoExecutionException {
if (null != handler.getRemoteFrameworkConnectionInfo()) {
return ConnectorInfoManagerFactory.getInstance().getRemoteManager(
handler.getRemoteFrameworkConnectionInfo());
} else if (handler.getBuildOutputDirectory().exists()) {
try {
return ConnectorInfoManagerFactory.getInstance().getLocalManager(
handler.getBuildOutputDirectory().toURI().toURL());
} catch (MalformedURLException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
} else if ("pom".equalsIgnoreCase(handler.getMavenProject().getPackaging())) {
throw new MojoExecutionException(
"The required remoteFrameworkConnectionInfo is missing");
} else {
throw new MojoExecutionException("The " + handler.getBuildOutputDirectory()
+ " is not exist");
}
}
private static final Comparator<Pair<String, String>> PAIR_COMPARATOR =
new Comparator<Pair<String, String>>() {
public int compare(Pair<String, String> left, Pair<String, String> right) {
if (left == null && right == null){
return 0;
} else if (left == null ){
return -1;
} else if (right == null){
return 1;
}
return String.CASE_INSENSITIVE_ORDER.compare(left.first, right.first);
}
};
}