Skip to content

Commit

Permalink
Merge 4e3073e into 210d027
Browse files Browse the repository at this point in the history
  • Loading branch information
badvision committed May 28, 2020
2 parents 210d027 + 4e3073e commit 3422651
Show file tree
Hide file tree
Showing 14 changed files with 1,120 additions and 1,008 deletions.
1,562 changes: 793 additions & 769 deletions bundle/pom.xml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

/**
*
* @deprecated Will be removed in 5.0 entirely, no longer needed
*/
@ObjectClassDefinition(name = "ACS AEM Commons - Dialog Resource Provider Configuration", description = "Service Configuration")
@Deprecated
public @interface DialogResourceProviderConfiguration {
@AttributeDefinition(name = "Enable feature", description = "If checked, feature will be enabled")
boolean enabled();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
import org.apache.sling.spi.resource.provider.ResourceProvider;
import org.osgi.framework.ServiceRegistration;

/**
* Factory for dialog resource providers, removed after 4.7.0 by way of the new annotation processor.
* @deprecated Will be removed in 5.0 entirely, no longer needed
*/
@Deprecated
public interface DialogResourceProviderFactory {

void registerClass(String className);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,25 @@
package com.adobe.acs.commons.mcp.form;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Signifies a bean which gets an automatically-generated dialog
* Signifies a bean which gets an automatically-generated dialog. Note that this
* will only work if you declare a resource type as well, see the
* DialogResourceProviderImpl class for more details on setting the resource
* type. If you declare this annotation without a resource type the annotation
* processor will produce a non-fatal warning and not generate a service. If you
* are declaring this on a base class, it is inherited and that warning can
* therefore be ignored.
*
* @see com.adobe.acs.commons.mcp.form.impl.DialogResourceProviderImpl
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface DialogProvider {

static enum DialogStyle {
Expand All @@ -46,7 +56,8 @@ static enum DialogStyle {
String propertiesTab() default "Properties";

/**
* @return Style used (component is default and uses a dialog component, page is more generic)
* @return Style used (component is default and uses a dialog component,
* page is more generic)
*/
DialogStyle style() default DialogStyle.COMPONENT;

Expand Down
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;
}
}
}
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";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@
import org.apache.sling.models.annotations.Model;

/**
* Generates a dialog out of @FormField annotations Ideally your sling model
* should extend this class to inherit its features but you can also just use
* the @DialogProvider annotation
* Generates a dialog out of @FormField annotations. Ideally your sling model
* should extend this class to inherit its API. Otherwise if you just want a
* generated dialog without the methods in this class, apply the DialogProvider
* annotation to your class by itself. NOTE: Subclassing will only produce a
* dialog provider OSGi service if you declare the resourceType either in the
* Model annotation or via a getter or public property for resourceType. The
* DialogProvider annotation will also provide an OSGi service but under the
* same conditions that resourceType is defined by your class.
*/
@Model(
adaptables = {SlingHttpServletRequest.class},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
/**
* Form components for MCP
*/
@Version("5.1.0")
@Version("5.2.0")
package com.adobe.acs.commons.mcp.form;

import org.osgi.annotation.versioning.Version;

0 comments on commit 3422651

Please sign in to comment.