blob: 8b36663945ad299a01f0cee54144f97a748c1183 [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.sling.maven.bundlesupport;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.json.Json;
import javax.json.JsonException;
import javax.json.JsonWriter;
import org.apache.maven.model.Resource;
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;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.sling.adapter.annotations.Adaptable;
import org.apache.sling.adapter.annotations.Adaptables;
import org.codehaus.plexus.util.StringUtils;
import io.github.classgraph.AnnotationClassRef;
import io.github.classgraph.AnnotationInfo;
import io.github.classgraph.AnnotationInfoList.AnnotationInfoFilter;
import io.github.classgraph.AnnotationParameterValue;
import io.github.classgraph.AnnotationParameterValueList;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassGraph.ClasspathElementFilter;
import io.github.classgraph.ClassGraph.ClasspathElementURLFilter;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;
/**
* Build <a href="http://sling.apache.org/documentation/the-sling-engine/adapters.html">adapter metadata (JSON)</a> for the Web Console Plugin at {@code /system/console/status-adapters} and
* {@code /system/console/adapters} from classes annotated with
* <a href="https://github.com/apache/sling-adapter-annotations">adapter annotations</a>.
*/
@Mojo(name="generate-adapter-metadata", defaultPhase = LifecyclePhase.PROCESS_CLASSES,
threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE)
public class GenerateAdapterMetadataMojo extends AbstractMojo {
private static final String DEFAULT_CONDITION = "If the adaptable is a %s.";
/** The directory which to check for classes with adapter metadata annotations. */
@Parameter(defaultValue = "${project.build.outputDirectory}")
File buildOutputDirectory;
/**
* Name of the generated descriptor file.
*/
@Parameter(property = "adapter.descriptor.name", defaultValue = "SLING-INF/adapters.json")
String fileName;
/**
* The output directory in which to emit the descriptor file with name {@link GenerateAdapterMetadataMojo#fileName}.
*/
@Parameter(defaultValue = "${project.build.outputDirectory}", required = true)
File outputDirectory;
/**
* The Maven project.
*/
@Parameter( defaultValue = "${project}", readonly = true )
private MavenProject project;
public void execute() throws MojoExecutionException, MojoFailureException {
final Map<String,Object> descriptor = new HashMap<>();
ClassGraph classGraph = new ClassGraph()
.enableAnnotationInfo() // only consider annotation info
.overrideClasspath(buildOutputDirectory) // just the classpath of the output directory needed (annotation classes themselves not relevant)
.enableExternalClasses();
if (getLog().isDebugEnabled()) {
classGraph.verbose();
}
try (ScanResult result = classGraph.scan()) {
ClassInfoList classInfoList = result.getClassesWithAnnotation(Adaptable.class);
classInfoList = classInfoList.union(result.getClassesWithAnnotation(Adaptables.class));
for (ClassInfo annotationClassInfo : classInfoList) {
getLog().info(String.format("found adaptable annotation on %s", annotationClassInfo.getSimpleName()));
for (AnnotationInfo annotationInfo : annotationClassInfo.getAnnotationInfo().filter(new AdaptableAnnotationInfoFilter())) {
AnnotationParameterValueList annotationParameterValues = annotationInfo.getParameterValues();
if (annotationInfo.getName().equals(Adaptables.class.getName())) {
parseAdaptablesAnnotation(annotationParameterValues, annotationClassInfo.getSimpleName(), descriptor);
} else if (annotationInfo.getName().equals(Adaptable.class.getName())) {
parseAdaptableAnnotation(annotationParameterValues, annotationClassInfo.getSimpleName(), descriptor);
} else {
throw new IllegalStateException("Unexpected annotation class found: " + annotationInfo);
}
}
}
final File outputFile = new File(outputDirectory, fileName);
outputFile.getParentFile().mkdirs();
try (FileWriter writer = new FileWriter(outputFile);
JsonWriter jsonWriter = Json.createWriter(writer)) {
jsonWriter.writeObject(JsonSupport.toJson(descriptor));
}
} catch (IOException|JsonException e) {
throw new MojoExecutionException("Unable to generate metadata", e);
}
}
private static final class AdaptableAnnotationInfoFilter implements AnnotationInfoFilter {
@Override
public boolean accept(AnnotationInfo annotationInfo) {
return (annotationInfo.getName().equals(Adaptables.class.getName()) || annotationInfo.getName().equals(Adaptable.class.getName()));
}
}
private void parseAdaptablesAnnotation(AnnotationParameterValueList annotationParameterValues, String annotatedClassName, final Map<String,Object> descriptor) throws JsonException {
// only one mandatory parameter "value" of type Adaptable[]
Object[] annotationInfos = (Object[])annotationParameterValues.get(0).getValue();
for (Object annotationInfo : annotationInfos) {
parseAdaptableAnnotation(((AnnotationInfo)annotationInfo).getParameterValues(), annotatedClassName, descriptor);
}
}
@SuppressWarnings("unchecked")
private void parseAdaptableAnnotation(AnnotationParameterValueList annotationParameterValues, String annotatedClassName, final Map<String,Object> descriptor) throws JsonException {
// two parameters: adaptableClass and Adapter[] adapters
String adaptableClassName = ((AnnotationClassRef) annotationParameterValues.get("adaptableClass").getValue()).getName();
Object[] adapters = (Object[]) annotationParameterValues.get("adapters").getValue();
Map<String,Object> adaptableDescription;
if (descriptor.containsKey(adaptableClassName)) {
adaptableDescription = (Map<String,Object>)descriptor.get(adaptableClassName);
} else {
adaptableDescription = new HashMap<>();
descriptor.put(adaptableClassName, adaptableDescription);
}
for (final Object adapter : adapters) {
parseAdapterAnnotation(((AnnotationInfo)adapter).getParameterValues(), annotatedClassName, adaptableDescription);
}
}
private void parseAdapterAnnotation(AnnotationParameterValueList annotationParameterValues, String annotatedClassName, final Map<String,Object> adaptableDescription) throws JsonException {
AnnotationParameterValue conditionParameterValue = annotationParameterValues.get("condition");
String condition = null;
if (conditionParameterValue != null) {
condition = (String) conditionParameterValue.getValue();
}
if (StringUtils.isEmpty(condition)) {
condition = String.format(DEFAULT_CONDITION, annotatedClassName);
}
Object[] adapterClasses = (Object[]) annotationParameterValues.get("value").getValue();
if (adapterClasses == null) {
throw new IllegalArgumentException("Adapter annotation is malformed. Expecting a list of adapter classes");
}
for (final Object adapterClass : adapterClasses) {
String adapterClassName = ((AnnotationClassRef)adapterClass).getName();
JsonSupport.accumulate(adaptableDescription, condition, adapterClassName);
}
}
}