-
Notifications
You must be signed in to change notification settings - Fork 595
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
1,120 additions
and
1,008 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
bundle/src/main/java/com/adobe/acs/commons/mcp/form/DialogProviderAnnotationProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/* | ||
* #%L | ||
* ACS AEM Commons Bundle | ||
* %% | ||
* Copyright (C) 2020 Adobe | ||
* %% | ||
* 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. | ||
* #L% | ||
*/ | ||
package com.adobe.acs.commons.mcp.form; | ||
|
||
import java.io.IOException; | ||
import java.io.PrintWriter; | ||
import java.util.Arrays; | ||
import java.util.HashSet; | ||
import java.util.Set; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
import javax.annotation.Generated; | ||
import javax.annotation.processing.AbstractProcessor; | ||
import javax.annotation.processing.RoundEnvironment; | ||
import javax.lang.model.SourceVersion; | ||
import javax.lang.model.element.Element; | ||
import javax.lang.model.element.TypeElement; | ||
import javax.tools.JavaFileObject; | ||
import org.apache.commons.lang.StringUtils; | ||
import org.apache.sling.models.annotations.Model; | ||
|
||
/** | ||
* Processes the DialogProvider annotation, producing a corresponding OSGi | ||
* service to provide the generated sling dialog resources. This annotation | ||
* processor will skip any classes which do not identify their corresponding | ||
* sling model either as part of the model annotation or by a property or getter | ||
* method. | ||
*/ | ||
@Generated("Don't run code coverage, JaCoco!") | ||
public class DialogProviderAnnotationProcessor extends AbstractProcessor { | ||
|
||
private static final Logger LOG = Logger.getLogger(DialogProviderAnnotationProcessor.class.getName()); | ||
|
||
@Override | ||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { | ||
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(DialogProvider.class)) { | ||
try { | ||
processDialogProviderAnnotation(annotatedElement); | ||
} catch (IOException ex) { | ||
LOG.log(Level.SEVERE, null, ex); | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
@Override | ||
public Set<String> getSupportedAnnotationTypes() { | ||
return new HashSet<>(Arrays.asList(DialogProvider.class.getCanonicalName())); | ||
} | ||
|
||
@Override | ||
public SourceVersion getSupportedSourceVersion() { | ||
return SourceVersion.RELEASE_8; | ||
} | ||
|
||
private void processDialogProviderAnnotation(Element element) throws IOException { | ||
TypeElement t = (TypeElement) element; | ||
String className = t.getQualifiedName().toString(); | ||
String serviceClassName = DialogResourceProvider.getServiceClassName(className); | ||
if (providesResourceType(t)) { | ||
if (LOG.isLoggable(Level.INFO)) { | ||
LOG.log(Level.INFO, String.format("Generated resource provider service for class %s => %s", className, serviceClassName)); | ||
} | ||
JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(serviceClassName); | ||
writeServiceStub(builderFile, serviceClassName, className); | ||
} else { | ||
if (LOG.isLoggable(Level.WARNING)) { | ||
LOG.log(Level.INFO, String.format("Class %s declares or inherits the DialogProvider annotation but does not declare a resource type -- no resource provider generated.", className)); | ||
} | ||
} | ||
} | ||
|
||
private void writeServiceStub(JavaFileObject builderFile, String serviceClass, String targetClass) throws IOException { | ||
try (PrintWriter out = new PrintWriter(builderFile.openWriter())) { | ||
String packageName = StringUtils.substringBeforeLast(serviceClass, "."); | ||
String className = StringUtils.substringAfterLast(serviceClass, "."); | ||
String osgiService = DialogResourceProvider.class.getCanonicalName(); | ||
out.println(String.format("package %s;", packageName)); | ||
out.println(); | ||
out.println("import javax.annotation.Generated;"); | ||
out.println("import org.osgi.annotation.versioning.ConsumerType;"); | ||
out.println("import org.osgi.framework.BundleContext;"); | ||
out.println("import org.osgi.service.component.annotations.*;"); | ||
out.println(); | ||
out.println("@Generated(\"Created by the ACS Commons DialogProviderAnnotationProcessor\")"); | ||
out.println("@ConsumerType"); | ||
out.println(String.format("@Component(service = %s.class, immediate = true)", osgiService)); | ||
out.println(String.format("public class %s implements %s {", className, osgiService)); | ||
out.println(); | ||
out.println(String.format(" @Override%n public Class getTargetClass() {%n return %s.class;%n }", targetClass)); | ||
out.println(" @Activate\n public void activate(BundleContext context) throws InstantiationException, IllegalAccessException {\n this.doActivate(context);\n }\n"); | ||
out.println(" @Deactivate\n public void deactivate(BundleContext context) {\n this.doDeactivate();\n }"); | ||
out.println("}"); | ||
out.flush(); | ||
} | ||
} | ||
|
||
private boolean providesResourceType(TypeElement t) { | ||
Model model = t.getAnnotation(Model.class); | ||
if (model != null && model.resourceType() != null && model.resourceType().length > 0) { | ||
return true; | ||
} else { | ||
return t.getEnclosedElements().stream().anyMatch(this::elementProvidesResourceType); | ||
} | ||
} | ||
|
||
private boolean elementProvidesResourceType(Element t) { | ||
switch (t.getKind()) { | ||
case LOCAL_VARIABLE: | ||
case FIELD: | ||
return t.getSimpleName().contentEquals("resourceType"); | ||
case METHOD: | ||
return t.getSimpleName().contentEquals("getResourceType"); | ||
default: | ||
return false; | ||
} | ||
} | ||
} |
88 changes: 88 additions & 0 deletions
88
bundle/src/main/java/com/adobe/acs/commons/mcp/form/DialogResourceProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* | ||
* #%L | ||
* ACS AEM Commons Bundle | ||
* %% | ||
* Copyright (C) 2020 Adobe | ||
* %% | ||
* 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. | ||
* #L% | ||
*/ | ||
package com.adobe.acs.commons.mcp.form; | ||
|
||
import com.adobe.acs.commons.mcp.impl.DialogResourceProviderImpl; | ||
import java.util.Collections; | ||
import java.util.Dictionary; | ||
import java.util.HashMap; | ||
import java.util.Hashtable; | ||
import java.util.Map; | ||
import org.apache.sling.spi.resource.provider.ResourceProvider; | ||
import org.osgi.framework.BundleContext; | ||
import org.osgi.framework.ServiceRegistration; | ||
|
||
/** | ||
* Provides an OSGi factory service for a resource provider. Service | ||
* implementations are usually generated by the AnnotationProcessor so to reduce | ||
* the amount of generated code, as much of the underlying implementation is | ||
* here in default methods. | ||
*/ | ||
@SuppressWarnings("squid:S1214") // There are no constants declared here, not sure why this code smell is being detected | ||
public interface DialogResourceProvider { | ||
Class getTargetClass(); | ||
|
||
default DialogProvider getDialogProvider() { | ||
return (DialogProvider) getTargetClass().getAnnotation(DialogProvider.class); | ||
} | ||
|
||
public static Map<Class, ServiceRegistration> registeredProviders = Collections.synchronizedMap(new HashMap<>()); | ||
|
||
@SuppressWarnings("squid:S1149") // Yes HashTable sucks but it's required here. | ||
default void doActivate(BundleContext bundleContext) throws InstantiationException, IllegalAccessException { | ||
DialogResourceProviderImpl provider = new DialogResourceProviderImpl(getTargetClass(), getDialogProvider()); | ||
@SuppressWarnings("UseOfObsoleteCollectionType") | ||
Dictionary<String, Object> props = new Hashtable<>(); | ||
props.put(ResourceProvider.PROPERTY_NAME, provider.getRoot()); | ||
props.put(ResourceProvider.PROPERTY_ROOT, provider.getRoot()); | ||
props.put(ResourceProvider.PROPERTY_USE_RESOURCE_ACCESS_SECURITY, Boolean.FALSE); | ||
ServiceRegistration providerRegistration = bundleContext.registerService(ResourceProvider.class, provider, props); | ||
registeredProviders.put(getTargetClass(), providerRegistration); | ||
} | ||
|
||
default void doDeactivate() { | ||
ServiceRegistration providerRegistration = registeredProviders.get(getTargetClass()); | ||
if (providerRegistration != null) { | ||
providerRegistration.unregister(); | ||
} | ||
registeredProviders.remove(getTargetClass()); | ||
} | ||
|
||
public static String getServiceClassName(String modelClass) { | ||
String[] parts = modelClass.split("\\."); | ||
String name = ""; | ||
String separator = "."; | ||
for (String part : parts) { | ||
char firstChar = part.charAt(0); | ||
String newSeparator = separator; | ||
if (firstChar >= 'A' && firstChar <= 'Z' && separator.equals(".")) { | ||
newSeparator = "$"; | ||
name += ".impl"; | ||
} | ||
if (name.length() > 0) { | ||
name += separator; | ||
} | ||
name += part; | ||
separator = newSeparator; | ||
} | ||
return name + "_dialogResourceProvider"; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.