From c8dbdc7ca00f6bbff40599944f2ec107fe2d8368 Mon Sep 17 00:00:00 2001 From: eschleb Date: Fri, 27 Sep 2024 14:54:04 +0200 Subject: [PATCH] Implement templateBuilder module --- README.md | 3 + magnolia-templatebuilder/README.md | 273 ++++++++++++++++++ magnolia-templatebuilder/pom.xml | 63 ++++ .../templatebuilder/DynamicAutoGenerator.java | 80 +++++ .../TemplateAvailabilityResolver.java | 61 ++++ .../TemplateBuilderModule.java | 39 +++ .../TemplateConfigurationSource.java | 37 +++ .../TemplateDefinitionProvider.java | 226 +++++++++++++++ .../templatebuilder/annotation/Available.java | 10 + .../annotation/DynamicFragment.java | 19 ++ .../templatebuilder/annotation/Template.java | 61 ++++ .../annotation/TemplateFactories.java | 13 + .../annotation/TernaryBoolean.java | 17 ++ .../templatebuilder/annotation/area/Area.java | 36 +++ .../annotation/area/AreaType.java | 29 ++ .../annotation/area/AutoGenerator.java | 15 + .../area/AvailableComponentClasses.java | 21 ++ .../annotation/area/AvailableComponents.java | 20 ++ .../annotation/area/ComponentCategory.java | 30 ++ .../area/ComponentInheritanceMode.java | 31 ++ .../annotation/area/Inherits.java | 16 + .../area/PropertyInheritanceMode.java | 29 ++ ...mplateBuilderGuiceComponentConfigurer.java | 15 + ...AutoGeneratorParameterResolverFactory.java | 12 + .../AvailabilityParameterResolverFactory.java | 11 + ...DefaultAutoGeneratorParameterResolver.java | 67 +++++ .../DefaultAvailabilityParameterResolver.java | 67 +++++ .../magnolia/magnolia-templatebuilder.xml | 35 +++ pom.xml | 8 + 29 files changed, 1344 insertions(+) create mode 100644 magnolia-templatebuilder/README.md create mode 100644 magnolia-templatebuilder/pom.xml create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/DynamicAutoGenerator.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateAvailabilityResolver.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateBuilderModule.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateConfigurationSource.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateDefinitionProvider.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/Available.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/DynamicFragment.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/Template.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/TemplateFactories.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/TernaryBoolean.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/Area.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AreaType.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AutoGenerator.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AvailableComponentClasses.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AvailableComponents.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/ComponentCategory.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/ComponentInheritanceMode.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/Inherits.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/PropertyInheritanceMode.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/configuration/TemplateBuilderGuiceComponentConfigurer.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/AutoGeneratorParameterResolverFactory.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/AvailabilityParameterResolverFactory.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/DefaultAutoGeneratorParameterResolver.java create mode 100644 magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/DefaultAvailabilityParameterResolver.java create mode 100644 magnolia-templatebuilder/src/main/resources/META-INF/magnolia/magnolia-templatebuilder.xml diff --git a/README.md b/README.md index d7c0919..6bea5f4 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,6 @@ The DialogBuilder module is a builder for Magnolia dialogs in a java. ## [Magnolia-VirtualUriMapping-builder](magnolia-virtualUriMapping-builder/README.md) The VirtualUriMapping-Builder module is a builder for Magnolia virtualUriMappings in a java. + +## [Magnolia-templatebuilder](magnolia-templatebuilder/README.md) +The TemplateBuilder module is a builder for Magnolia templates in a java. \ No newline at end of file diff --git a/magnolia-templatebuilder/README.md b/magnolia-templatebuilder/README.md new file mode 100644 index 0000000..b49d020 --- /dev/null +++ b/magnolia-templatebuilder/README.md @@ -0,0 +1,273 @@ +# Magnolia Templatebuilder + +The TemplateBuilder module is a builder for Magnolia templates in a java. + +## Setup + +### Add Maven dependency: +```xml + + com.merkle.oss.magnolia + magnolia-templatebuilder + 1.4.0 + +``` + +### DI-Bindings +```xml + + SomeModule + ... + + main + + GuiceComponentConfigurer + + + ... + +``` + +```java +import info.magnolia.objectfactory.guice.AbstractGuiceComponentConfigurer; +import info.magnolia.virtualuri.VirtualUriMapping; + +import org.apache.commons.lang3.reflect.TypeLiteral; +import org.reflections.Reflections; + +import com.google.inject.multibindings.Multibinder; +import com.merkle.oss.magnolia.templatebuilder.annotation.Template; +import com.merkle.oss.magnolia.templatebuilder.annotation.TemplateFactories; + +public class GuiceComponentConfigurer extends AbstractGuiceComponentConfigurer { + @Override + protected void configure() { + // Here we use Reflections, but you can also use ClassPathScanningCandidateComponentProvider or bind each factory manually + final Multibinder> templateFactoryMultibinder = Multibinder.newSetBinder(binder, new TypeLiteral<>() {}, TemplateFactories.class); + new Reflections(getClass()).getTypesAnnotatedWith(Template.class).forEach(clazz -> templateFactoryMultibinder.addBinding().toInstance(clazz)); + } +} +``` + +## How to use + +## Example + +### Area-component-category +```java +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.merkle.oss.magnolia.templatebuilder.annotation.area.ComponentCategory; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@ComponentCategory +public @interface ContentArea {} +``` + +### Component-template + +```java +import com.merkle.oss.magnolia.templatebuilder.annotation.Template; + +@ContentArea +@Template( + id = SomeComponent.ID, + title = "templates.components." + SomeComponent.NAME + ".title", + dialog = SomeComponentDialog.ID, + description = "templates.components." + SomeComponent.NAME + ".description", + renderer = "freemarker", + templateScript = "/someModule/templates/components/someComponent.ftl" +) +public class SomeComponent extends BaseComponent { + public static final String NAME = "SomeComponent"; + public static final String ID = "SomeApp:components/" + NAME; +} +``` +### Page-template with area + +```java +import java.util.List; +import java.util.Set; + +import javax.jcr.Node; + +import com.merkle.oss.magnolia.templatebuilder.annotation.Available; +import com.merkle.oss.magnolia.templatebuilder.annotation.Template; +import com.merkle.oss.magnolia.templatebuilder.annotation.area.Area; +import com.merkle.oss.magnolia.templatebuilder.annotation.area.AvailableComponentClasses; +import com.merkle.oss.magnolia.templatebuilder.annotation.area.AvailableComponents; + +@Template( + id = SomePage.ID, + title = "templates.pages." + SomePage.NAME + ".title", + dialog = SomePageDialog.ID, + description = "templates.pages." + SomePage.NAME + ".description", + renderer = "freemarker", + templateScript = "/someModule/templates/pages/somePage.ftl" +) +public class SomePage { + public static final String NAME = "SomePage"; + public static final String ID = "SomeApp:pages/" + NAME; + + @Available + public boolean isAvailable(final Node node) { + //TODO implement + return true; + } + + @Area( + id = ContentArea.ID, + name = ContentArea.NAME, + title = "templates.areas." + SomePage.ContentArea.NAME + ".title", + renderer = "freemarker", //optional, uses templates renderer if not specified + templateScript = "/someModule/templates/areas/contentArea.ftl" + ) + @AvailableComponentClasses({ ContentArea.class }) + @AvailableComponents({ "someComponentId" }) + public static class ContentArea { + public static final String NAME = "ContentArea"; + public static final String ID = SomePage.ID + "/" + NAME; + } +} +``` + +## Customization +### Definition decorators +Implement a decorator: +```java +import info.magnolia.config.registry.DefinitionProvider; +import info.magnolia.config.registry.DefinitionProviderWrapper; +import info.magnolia.config.registry.Registry; +import info.magnolia.config.registry.decoration.DefinitionDecorator; +import info.magnolia.config.registry.decoration.DefinitionDecoratorMetadata; +import info.magnolia.rendering.template.TemplateDefinition; + + +public class CustomTemplateDefinitionDecorator implements DefinitionDecorator { + @Override + public DefinitionDecoratorMetadata metadata() { + return () -> "someModule"; + } + + @Override + public boolean appliesTo(final DefinitionProvider definitionProvider) { + //TODO implement + return true; + } + + @Override + public DefinitionProvider decorate(final DefinitionProvider definitionProvider) { + return new DefinitionProviderWrapper<>(definitionProvider) { + @Override + public TemplateDefinition get() throws Registry.InvalidDefinitionException { + //TODO deorate definition + return definitionProvider.get(); + } + }; + } +} +``` + +Override the TemplateDefinitionProvider.Factory and pass your custom decorator to TemplateDefinitionProvider: +```java +import info.magnolia.objectfactory.Components; +import info.magnolia.rendering.template.configured.ConfiguredAreaDefinition; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import javax.inject.Inject; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import com.merkle.oss.magnolia.templatebuilder.TemplateAvailabilityResolver; +import com.merkle.oss.magnolia.templatebuilder.TemplateDefinitionProvider; + +public class CustomTemplateDefinitionProviderFactory extends TemplateDefinitionProvider.Factory { + private final TemplateAvailabilityResolver templateAvailabilityResolver; + private final CustomTemplateDefinitionDecorator customTemplateDefinitionDecorator; + + @Inject + public CustomTemplateDefinitionProviderFactory( + final TemplateAvailabilityResolver templateAvailabilityResolver, + final CustomTemplateDefinitionDecorator customTemplateDefinitionDecorator + ) { + super(templateAvailabilityResolver); + this.templateAvailabilityResolver = templateAvailabilityResolver; + this.customTemplateDefinitionDecorator = customTemplateDefinitionDecorator; + } + + public TemplateDefinitionProvider create(final Set> templateFactories, final Class factoryClass) { + return new TemplateDefinitionProvider( + List.of(customTemplateDefinitionDecorator), + templateAvailabilityResolver, + () -> Components.newInstance(ConfiguredAreaDefinition.class), + templateFactories, + () -> Components.newInstance(factoryClass), + factoryClass + ); + } +} +``` +bind custom factory: +```xml + + com.merkle.oss.magnolia.templatebuilder.TemplateDefinitionProvider.Factory + com.somepackage.CustomTemplateDefinitionProviderFactory + +``` + +### ParameterResolver +Implement and bind different AvailabilityParameterResolverFactory to customize injectable `@Availabile` method arguments. + +```java +import info.magnolia.rendering.template.TemplateDefinition; + +import javax.inject.Inject; +import javax.jcr.Node; + +import com.merkle.oss.magnolia.builder.parameter.ParameterResolver; +import com.merkle.oss.magnolia.powernode.PowerNodeService; +import com.merkle.oss.magnolia.templatebuilder.parameter.AvailabilityParameterResolverFactory; +import com.merkle.oss.magnolia.templatebuilder.parameter.DefaultAvailabilityParameterResolver; + +public class CustomAvailabilityParamResolver extends ParameterResolver { + + public CustomAvailabilityParamResolver( + final Node node, + final TemplateDefinition templateDefinition + ) { + super(new DefaultAvailabilityParameterResolver(node, templateDefinition)); + } + + @Override + public Object resolveParameter(final Class parameterType) { + if (parameterType.equals(SomeCustomParam.class)) { + return new SomeCustomParam(); + } + return super.resolveParameter(parameterType); + } + + public static class Factory implements AvailabilityParameterResolverFactory { + @Override + public ParameterResolver create(final Node node, final TemplateDefinition templateDefinition) { + return new CustomAvailabilityParamResolver(node, templateDefinition); + } + } +} +``` + +```xml + + com.merkle.oss.magnolia.templatebuilder.parameter.AvailabilityParameterResolverFactory + com.somepackage.CustomAvailabilityParamResolver$Factory + +``` \ No newline at end of file diff --git a/magnolia-templatebuilder/pom.xml b/magnolia-templatebuilder/pom.xml new file mode 100644 index 0000000..93fb663 --- /dev/null +++ b/magnolia-templatebuilder/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + com.merkle.oss.magnolia + magnolia-dynamic-builder + 1.4.1-SNAPSHOT + + + magnolia-templatebuilder + + + + com.merkle.oss.magnolia + magnolia-dynamic-builder-common + + + + + info.magnolia + magnolia-core + compile + + + info.magnolia.ui + magnolia-ui-framework + compile + + + info.magnolia + magnolia-rendering + compile + + + info.magnolia.advancedcache + magnolia-advanced-cache-dpc + provided + + + + jakarta.servlet + jakarta.servlet-api + + + com.google.code.findbugs + jsr305 + + + + + org.junit.jupiter + junit-jupiter + test + + + org.mockito + mockito-core + test + + + \ No newline at end of file diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/DynamicAutoGenerator.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/DynamicAutoGenerator.java new file mode 100644 index 0000000..bc21bd3 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/DynamicAutoGenerator.java @@ -0,0 +1,80 @@ +package com.merkle.oss.magnolia.templatebuilder; + +import info.magnolia.rendering.engine.RenderException; +import info.magnolia.rendering.generator.Generator; +import info.magnolia.rendering.template.configured.ConfiguredAreaDefinition; +import info.magnolia.rendering.template.configured.ConfiguredAutoGeneration; +import info.magnolia.rendering.template.configured.ConfiguredTemplateDefinition; + +import java.lang.reflect.Method; +import java.util.Arrays; + +import javax.inject.Inject; +import javax.jcr.Node; + +import com.merkle.oss.magnolia.builder.parameter.ParameterResolver; +import com.merkle.oss.magnolia.templatebuilder.parameter.AutoGeneratorParameterResolverFactory; + +public class DynamicAutoGenerator implements Generator { + private final AutoGeneratorParameterResolverFactory autoGeneratorParameterResolverFactory; + private final Node node; + + @Inject + public DynamicAutoGenerator( + final AutoGeneratorParameterResolverFactory autoGeneratorParameterResolverFactory, + final Node node + ) { + this.autoGeneratorParameterResolverFactory = autoGeneratorParameterResolverFactory; + this.node = node; + } + + @Override + public void generate(final Definition configuration) throws RenderException { + try { + final ParameterResolver parameterResolver = autoGeneratorParameterResolverFactory.create(node, configuration.getTemplate(), configuration.getArea()); + final Object[] parameters = Arrays + .stream(configuration.getMethod().getParameterTypes()) + .map(parameterResolver::resolveParameter) + .toArray(); + configuration.getMethod().invoke(configuration.areaClazz, parameters); + } catch (Exception e) { + throw new RenderException("AutoGeneration of " + configuration.getAreaClazz().getName() + "." + configuration.getMethod().getName() + " failed!", e); + } + } + + public static class Definition extends ConfiguredAutoGeneration { + private final ConfiguredTemplateDefinition template; + private final ConfiguredAreaDefinition area; + private final Class areaClazz; + private final Method method; + + public Definition( + final ConfiguredTemplateDefinition template, + final ConfiguredAreaDefinition area, + final Class areaClazz, + final Method method + ) { + setGeneratorClass((Class) DynamicAutoGenerator.class); + this.method = method; + this.template = template; + this.area = area; + this.areaClazz = areaClazz; + } + + public ConfiguredTemplateDefinition getTemplate() { + return template; + } + + public ConfiguredAreaDefinition getArea() { + return area; + } + + public Class getAreaClazz() { + return areaClazz; + } + + public Method getMethod() { + return method; + } + } +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateAvailabilityResolver.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateAvailabilityResolver.java new file mode 100644 index 0000000..4cf8543 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateAvailabilityResolver.java @@ -0,0 +1,61 @@ +package com.merkle.oss.magnolia.templatebuilder; + +import info.magnolia.rendering.template.TemplateAvailability; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import javax.inject.Inject; +import javax.jcr.Node; + +import com.merkle.oss.magnolia.builder.parameter.ParameterResolver; +import com.merkle.oss.magnolia.templatebuilder.annotation.Available; +import com.merkle.oss.magnolia.templatebuilder.parameter.AvailabilityParameterResolverFactory; + +public class TemplateAvailabilityResolver { + private final AvailabilityParameterResolverFactory availabilityParameterResolverFactory; + + @Inject + public TemplateAvailabilityResolver(final AvailabilityParameterResolverFactory availabilityParameterResolverFactory) { + this.availabilityParameterResolverFactory = availabilityParameterResolverFactory; + } + + public TemplateAvailability resolve(final Object template) { + final List matchingMethods = streamMethods(template.getClass(), Available.class).toList(); + + if (matchingMethods.isEmpty()) { + return (node, templateDefinition) -> false; + } + if (matchingMethods.size() > 1) { + throw new IllegalStateException("Multiple @Available annotated methods found for template [" + template.getClass() + "]"); + } + final Method method = matchingMethods.get(0); + if (!method.getReturnType().equals(Boolean.TYPE)) { + throw new IllegalStateException("Method " + method.getName() + " annotated with @Available for template [" + template.getClass() + "] has wrong return type [" + method.getReturnType() + "] should be boolean."); + } + return (node, templateDefinition) -> invoke(template, method, availabilityParameterResolverFactory.create(node, templateDefinition)); + } + + private boolean invoke(final Object template, final Method availableMethod, final ParameterResolver parameterResolver) { + final Object[] parameters = Arrays + .stream(availableMethod.getParameterTypes()) + .map(parameterResolver::resolveParameter) + .toArray(); + try { + return (boolean)availableMethod.invoke(template, parameters); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Could not invoke " + availableMethod.getName() + ", for template " + template.getClass(), e); + } + } + + private Stream streamMethods(final Class clazz, final Class annotationClass) { + return Arrays + .stream(clazz.getDeclaredMethods()) + .filter(method -> method.isAnnotationPresent(annotationClass) && !Modifier.isStatic(method.getModifiers())); + } +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateBuilderModule.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateBuilderModule.java new file mode 100644 index 0000000..814009e --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateBuilderModule.java @@ -0,0 +1,39 @@ +package com.merkle.oss.magnolia.templatebuilder; + +import info.magnolia.module.ModuleLifecycle; +import info.magnolia.module.ModuleLifecycleContext; +import info.magnolia.rendering.template.registry.TemplateDefinitionRegistry; + +import java.lang.invoke.MethodHandles; + +import javax.inject.Inject; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TemplateBuilderModule implements ModuleLifecycle { + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private final TemplateDefinitionRegistry templateDefinitionRegistry; + private final TemplateConfigurationSource templateConfigurationSource; + + @Inject + public TemplateBuilderModule( + final TemplateDefinitionRegistry templateDefinitionRegistry, + final TemplateConfigurationSource templateConfigurationSource + ) { + this.templateDefinitionRegistry = templateDefinitionRegistry; + this.templateConfigurationSource = templateConfigurationSource; + } + + @Override + public void start(ModuleLifecycleContext moduleLifecycleContext) { + LOG.debug("Starting TemplateBuilder Module"); + templateDefinitionRegistry.bindTo(templateConfigurationSource); + templateConfigurationSource.start(); + } + + @Override + public void stop(ModuleLifecycleContext moduleLifecycleContext) { + LOG.debug("Stopping TemplateBuilder Module"); + } +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateConfigurationSource.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateConfigurationSource.java new file mode 100644 index 0000000..d54a59b --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateConfigurationSource.java @@ -0,0 +1,37 @@ +package com.merkle.oss.magnolia.templatebuilder; + +import info.magnolia.config.registry.DefinitionProvider; +import info.magnolia.rendering.DefinitionTypes; +import info.magnolia.rendering.template.TemplateDefinition; + +import java.lang.invoke.MethodHandles; +import java.util.Set; +import java.util.stream.Stream; + +import javax.inject.Inject; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.merkle.oss.magnolia.builder.AbstractDynamicConfigurationSource; +import com.merkle.oss.magnolia.templatebuilder.annotation.TemplateFactories; + +public class TemplateConfigurationSource extends AbstractDynamicConfigurationSource { + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private final TemplateDefinitionProvider.Factory templateDefinitionProviderFactory; + + @Inject + public TemplateConfigurationSource( + @TemplateFactories final Set> templateFactories, + final TemplateDefinitionProvider.Factory templateDefinitionProviderFactory + ) { + super(DefinitionTypes.TEMPLATE, templateFactories); + this.templateDefinitionProviderFactory = templateDefinitionProviderFactory; + } + + @Override + protected Stream> definitionProviders(final Class factoryClass) { + LOG.info("Registered dialog '{}' from {}", factoryClass.getSimpleName(), factoryClass.getName()); + return Stream.of(templateDefinitionProviderFactory.create(factories, factoryClass)); + } +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateDefinitionProvider.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateDefinitionProvider.java new file mode 100644 index 0000000..1dfee1e --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/TemplateDefinitionProvider.java @@ -0,0 +1,226 @@ +package com.merkle.oss.magnolia.templatebuilder; + +import info.magnolia.config.registry.DefinitionMetadata; +import info.magnolia.config.registry.Registry; +import info.magnolia.config.registry.decoration.DefinitionDecorator; +import info.magnolia.module.advancedcache.rendering.DynamicFragmentDefinition; +import info.magnolia.objectfactory.Components; +import info.magnolia.rendering.DefinitionTypes; +import info.magnolia.rendering.model.RenderingModel; +import info.magnolia.rendering.template.AreaDefinition; +import info.magnolia.rendering.template.ComponentAvailability; +import info.magnolia.rendering.template.TemplateDefinition; +import info.magnolia.rendering.template.configured.ConfiguredAreaDefinition; +import info.magnolia.rendering.template.configured.ConfiguredAutoGeneration; +import info.magnolia.rendering.template.configured.ConfiguredComponentAvailability; +import info.magnolia.rendering.template.configured.ConfiguredInheritance; +import info.magnolia.rendering.template.configured.ConfiguredTemplateDefinition; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.inject.Inject; +import javax.inject.Provider; + +import org.apache.commons.lang3.StringUtils; + +import com.merkle.oss.magnolia.builder.AbstractDynamicDefinitionProvider; +import com.merkle.oss.magnolia.builder.DynamicDefinitionMetaData; +import com.merkle.oss.magnolia.templatebuilder.annotation.DynamicFragment; +import com.merkle.oss.magnolia.templatebuilder.annotation.Template; +import com.merkle.oss.magnolia.templatebuilder.annotation.area.Area; +import com.merkle.oss.magnolia.templatebuilder.annotation.area.AutoGenerator; +import com.merkle.oss.magnolia.templatebuilder.annotation.area.AvailableComponentClasses; +import com.merkle.oss.magnolia.templatebuilder.annotation.area.AvailableComponents; +import com.merkle.oss.magnolia.templatebuilder.annotation.area.ComponentCategory; +import com.merkle.oss.magnolia.templatebuilder.annotation.area.Inherits; + +public class TemplateDefinitionProvider extends AbstractDynamicDefinitionProvider { + private final TemplateAvailabilityResolver templateAvailabilityResolver; + private final Provider areaDefinitionFactory; + private final Set> templateFactories; + private final Provider factoryObjectProvider; + private final Template annotation; + private final DefinitionMetadata metadata; + + public TemplateDefinitionProvider( + final List> decorators, + final TemplateAvailabilityResolver templateAvailabilityResolver, + final Provider areaDefinitionFactory, + final Set> templateFactories, + final Provider factoryObjectProvider, + final Class factoryClass + ) { + super(decorators); + this.templateAvailabilityResolver = templateAvailabilityResolver; + this.areaDefinitionFactory = areaDefinitionFactory; + this.templateFactories = templateFactories; + this.factoryObjectProvider = factoryObjectProvider; + this.annotation = factoryClass.getAnnotation(Template.class); + this.metadata = new DynamicDefinitionMetaData.Builder(factoryClass, annotation.id()) + .type(DefinitionTypes.TEMPLATE) + .build(); + } + + @Override + public DefinitionMetadata getMetadata() { + return metadata; + } + + @Override + public TemplateDefinition getInternal() throws Registry.InvalidDefinitionException { + final Object factoryObject = factoryObjectProvider.get(); + final ConfiguredTemplateDefinition template = new ConfiguredTemplateDefinition(templateAvailabilityResolver.resolve(factoryObject)); + template.setId(annotation.id()); + template.setName(metadata.getName()); + template.setTitle(StringUtils.trimToNull(annotation.title())); + template.setDescription(StringUtils.trimToNull(annotation.description())); + template.setDialog(StringUtils.trimToNull(annotation.dialog())); + template.setVisible(annotation.visible().getValue()); + template.setType(annotation.type()); + template.setSubtype(StringUtils.trimToNull(annotation.subtype())); + template.setRenderType(annotation.renderer()); + template.setWritable(annotation.writable().getValue()); + template.setMoveable(annotation.moveable().getValue()); + template.setDeletable(annotation.deletable().getValue()); + template.setAreas(getAreas(template, factoryObject.getClass())); + Optional.of(annotation.modelClass()).filter(RenderingModel.class::isAssignableFrom).ifPresent(template::setModelClass); + Optional.ofNullable(StringUtils.trimToNull(annotation.templateScript())).ifPresent(template::setTemplateScript); + dynamicFragment(factoryObject.getClass()).ifPresent(template::setFragmentDefinition); + return template; + } + + protected Map getAreas(final ConfiguredTemplateDefinition definition, final Class clazz) { + return streamClasses(clazz, Area.class) + .map(areaClazz -> getAreaDefinition(definition, areaClazz, areaClazz.getAnnotation(Area.class))) + .collect(Collectors.toMap(AreaDefinition::getName, Function.identity())); + } + + protected AreaDefinition getAreaDefinition(final ConfiguredTemplateDefinition template, final Class areaClazz, final Area annotation) { + final ConfiguredAreaDefinition area = areaDefinitionFactory.get(); + area.setId(annotation.id()); + area.setName(annotation.name()); + area.setTitle(StringUtils.trimToNull(annotation.title())); + area.setRenderType(Optional.ofNullable(StringUtils.trimToNull(annotation.renderer())).orElseGet(this.annotation::renderer)); + area.setDialog(StringUtils.trimToNull(annotation.dialog())); + area.setType(annotation.type().getDefinitionFormat()); + Optional.of(annotation.maxComponents()).filter(max -> Integer.MAX_VALUE != max).ifPresent(area::setMaxComponents); + area.setOptional(annotation.optional().getValue()); + area.setCreateAreaNode(annotation.createAreaNode().getValue()); + Optional.ofNullable(StringUtils.trimToNull(annotation.templateScript())).ifPresent(area::setTemplateScript); + getInheritanceConfiguration(areaClazz).ifPresent(area::setInheritance); + getAutoGenerationConfiguration(template, area, areaClazz).ifPresent(area::setAutoGeneration); + area.setAvailableComponents(getAvailableComponents(areaClazz)); + area.setAreas(getAreas(template, areaClazz)); + dynamicFragment(areaClazz).ifPresent(template::setFragmentDefinition); + return area; + } + + private Optional getAutoGenerationConfiguration( + final ConfiguredTemplateDefinition template, + final ConfiguredAreaDefinition area, + final Class areaClazz + ) { + final List matchingMethods = streamMethods(areaClazz, AutoGenerator.class).toList(); + if (matchingMethods.isEmpty()) { + return Optional.empty(); + } + if (matchingMethods.size() > 1) { + throw new IllegalStateException("Multiple @AutoGenerator annotated methods found for area [" + areaClazz + "]"); + } + return Optional.of(new DynamicAutoGenerator.Definition(template, area, areaClazz, matchingMethods.get(0))); + } + + private Optional getInheritanceConfiguration(final Class areaClazz) { + return Optional + .ofNullable(areaClazz.getAnnotation(Inherits.class)) + .map(inherits -> { + final ConfiguredInheritance inheritance = new ConfiguredInheritance(); + inheritance.setEnabled(true); + inheritance.setComponents(inherits.components().getConfigurationFormat()); + inheritance.setProperties(inherits.properties().getConfigurationFormat()); + return inheritance; + }); + } + + protected Map getAvailableComponents(final Class areaClazz) { + return Stream + .concat( + Stream.ofNullable(areaClazz.getAnnotation(AvailableComponents.class)) + .map(AvailableComponents::value).flatMap(Arrays::stream), + Stream.ofNullable(areaClazz.getAnnotation(AvailableComponentClasses.class)) + .map(AvailableComponentClasses::value).flatMap(Arrays::stream) + .flatMap(componentClazz -> resolveAvailableComponentIds(areaClazz, componentClazz)) + .map(componentClazz -> Optional.ofNullable(componentClazz.getAnnotation(Template.class)).map(Template::id).orElseThrow(() -> + new IllegalArgumentException("available component clazz " + componentClazz.getName() + " is not annotated with @" + Template.class.getSimpleName()) + )) + ) + .map(componentId -> { + final ConfiguredComponentAvailability availability = new ConfiguredComponentAvailability(); + availability.setId(componentId); + return availability; + }) + .collect(Collectors.toMap(ComponentAvailability::getId, Function.identity())); + } + + private Stream> resolveAvailableComponentIds(final Class areaClazz, final Class componentClazz) { + if (componentClazz.isAnnotation()) { + if (!componentClazz.isAnnotationPresent(ComponentCategory.class)) { + throw new IllegalArgumentException("Annotation [" + componentClazz.getName() + "] specified on area [" + areaClazz.getName() + "] is not a @" + ComponentCategory.class.getSimpleName()); + } + return templateFactories.stream().filter(templateClazz -> templateClazz.isAnnotationPresent((Class) componentClazz)); + } + return Stream.of(componentClazz); + } + + private Optional dynamicFragment(final Class templateClazz) { + return Optional.ofNullable(templateClazz.getAnnotation(DynamicFragment.class)).map(dynamicFragment -> { + final DynamicFragmentDefinition fragmentDefinition = new DynamicFragmentDefinition(); + fragmentDefinition.setMechanism("sitemesh"); + fragmentDefinition.setTtl(String.valueOf(dynamicFragment.ttlInSeconds())); + return fragmentDefinition; + }); + } + + private Stream> streamClasses(final Class clazz, final Class annotationClass) { + return Arrays + .stream(clazz.getDeclaredClasses()) + .filter(method -> method.isAnnotationPresent(annotationClass)); + } + + private Stream streamMethods(final Class clazz, final Class annotationClass) { + return Arrays + .stream(clazz.getDeclaredMethods()) + .filter(method -> method.isAnnotationPresent(annotationClass) && !Modifier.isStatic(method.getModifiers())); + } + + public static class Factory { + private final TemplateAvailabilityResolver templateAvailabilityResolver; + + @Inject + public Factory(final TemplateAvailabilityResolver templateAvailabilityResolver) { + this.templateAvailabilityResolver = templateAvailabilityResolver; + } + + public TemplateDefinitionProvider create(final Set> templateFactories, final Class factoryClass) { + return new TemplateDefinitionProvider( + Collections.emptyList(), + templateAvailabilityResolver, + () -> Components.newInstance(ConfiguredAreaDefinition.class), + templateFactories, + () -> Components.newInstance(factoryClass), + factoryClass + ); + } + } +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/Available.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/Available.java new file mode 100644 index 0000000..ffd1d28 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/Available.java @@ -0,0 +1,10 @@ +package com.merkle.oss.magnolia.templatebuilder.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Available {} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/DynamicFragment.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/DynamicFragment.java new file mode 100644 index 0000000..177f96f --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/DynamicFragment.java @@ -0,0 +1,19 @@ +package com.merkle.oss.magnolia.templatebuilder.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declares a component/area as dynamicFragment + *
+ * Can be used to disable caching of a component or an area + *
+ *
See Dynamic page caching + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface DynamicFragment { + long ttlInSeconds() default 0; +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/Template.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/Template.java new file mode 100644 index 0000000..c36d263 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/Template.java @@ -0,0 +1,61 @@ +package com.merkle.oss.magnolia.templatebuilder.annotation; + +import info.magnolia.rendering.template.type.DefaultTemplateTypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Template { + + String id(); + + String renderer(); + + Class modelClass() default Class.class; + + String title() default ""; + + String description() default ""; + + String dialog() default ""; + + String templateScript() default ""; + + /** + * Defines the visibility of the template. When set to false the template is never presented in the user interface. + * This is useful for templates that are only used for pages that are created by scheduled jobs rather than by + * editors. + */ + TernaryBoolean visible() default TernaryBoolean.UNSPECIFIED; + + /** + * Specifies the template type, e.g. "home", "section", etc. + * + * @see info.magnolia.rendering.template.type.DefaultTemplateTypes + */ + String type() default DefaultTemplateTypes.CONTENT; + + /** + * Specifies the template subtype, such as specific features, e.g. "news", etc. + */ + String subtype() default ""; + + /** + * Specifies whether a component can be changed. + */ + TernaryBoolean writable() default TernaryBoolean.UNSPECIFIED; + + /** + * Specifies whether a component can be moved. + */ + TernaryBoolean moveable() default TernaryBoolean.UNSPECIFIED; + + /** + * Specifies whether a component can be deleted. + */ + TernaryBoolean deletable() default TernaryBoolean.UNSPECIFIED; +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/TemplateFactories.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/TemplateFactories.java new file mode 100644 index 0000000..5942bbd --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/TemplateFactories.java @@ -0,0 +1,13 @@ +package com.merkle.oss.magnolia.templatebuilder.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.google.inject.BindingAnnotation; + +@BindingAnnotation +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.PARAMETER }) +public @interface TemplateFactories {} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/TernaryBoolean.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/TernaryBoolean.java new file mode 100644 index 0000000..2153799 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/TernaryBoolean.java @@ -0,0 +1,17 @@ +package com.merkle.oss.magnolia.templatebuilder.annotation; + +public enum TernaryBoolean { + TRUE(true), + FALSE(false), + UNSPECIFIED(null); + + private final Boolean value; + + TernaryBoolean(final Boolean value) { + this.value = value; + } + + public Boolean getValue() { + return value; + } +} \ No newline at end of file diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/Area.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/Area.java new file mode 100644 index 0000000..2f28c57 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/Area.java @@ -0,0 +1,36 @@ +package com.merkle.oss.magnolia.templatebuilder.annotation.area; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.merkle.oss.magnolia.templatebuilder.annotation.TernaryBoolean; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Area { + + String id(); + + String name(); + + /** + * Template renderer will be set if not specified + */ + String renderer() default ""; + + String templateScript() default ""; + + String title() default ""; + + String dialog() default ""; + + AreaType type() default AreaType.LIST; + + TernaryBoolean optional() default TernaryBoolean.UNSPECIFIED; + + int maxComponents() default Integer.MAX_VALUE; + + TernaryBoolean createAreaNode() default TernaryBoolean.UNSPECIFIED; +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AreaType.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AreaType.java new file mode 100644 index 0000000..dad05e3 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AreaType.java @@ -0,0 +1,29 @@ +package com.merkle.oss.magnolia.templatebuilder.annotation.area; + +import info.magnolia.rendering.template.AreaDefinition; + +/** + * Enumeration of area types. + *
    + *
  • NO_COMPONENT The area does not have any components within it
  • + *
  • LIST The area can have zero or more components within it
  • + *
  • SINGLE The area can have zero or one component within it
  • + *
+ * + * @see Area + */ +public enum AreaType { + NO_COMPONENT(AreaDefinition.TYPE_NO_COMPONENT), + LIST(AreaDefinition.TYPE_LIST), + SINGLE(AreaDefinition.TYPE_SINGLE); + + private final String definitionFormat; + + AreaType(final String definitionFormat) { + this.definitionFormat = definitionFormat; + } + + public String getDefinitionFormat() { + return definitionFormat; + } +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AutoGenerator.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AutoGenerator.java new file mode 100644 index 0000000..d320151 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AutoGenerator.java @@ -0,0 +1,15 @@ +package com.merkle.oss.magnolia.templatebuilder.annotation.area; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declares a method to be called when an area is rendered to perform automatic generation of content. Often used for + * adding components automatically when a new page is created so editors don't have to do it manually every time. + * @see Area + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface AutoGenerator {} \ No newline at end of file diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AvailableComponentClasses.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AvailableComponentClasses.java new file mode 100644 index 0000000..4706e7e --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AvailableComponentClasses.java @@ -0,0 +1,21 @@ +package com.merkle.oss.magnolia.templatebuilder.annotation.area; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation used to set the available components in an area either by specifying the component class directly or by + * specifying a component category annotation, in which case all components have that category annotation will be + * available. When used in combination with {@link AvailableComponents} components from both annotations are used. + * + * @see Area + * @see AvailableComponents + * @see ComponentCategory + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface AvailableComponentClasses { + Class[] value() default {}; +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AvailableComponents.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AvailableComponents.java new file mode 100644 index 0000000..8307009 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/AvailableComponents.java @@ -0,0 +1,20 @@ +package com.merkle.oss.magnolia.templatebuilder.annotation.area; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation used to set the available components in an area by their id. When used in combination with + * {@link AvailableComponentClasses} components from both annotations are used. + * + * @see Area + * @see AvailableComponentClasses + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface AvailableComponents { + + String[] value() default {}; +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/ComponentCategory.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/ComponentCategory.java new file mode 100644 index 0000000..ea62eaa --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/ComponentCategory.java @@ -0,0 +1,30 @@ +package com.merkle.oss.magnolia.templatebuilder.annotation.area; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Denotes an annotation as a component category annotation. + * + * A component category annotation is used to annotate components as being part of a certain category. The annotation + * is then specified in the `@AvailableComponentClasses` annotation on areas. All components in the category will + * then be available in the area. + *

+ *

+ * @Retention(RetentionPolicy.RUNTIME)
+ * @Target(ElementType.TYPE)
+ * @ComponentCategory
+ * public @interface Promo {
+ * }
+ * 
+ * + * @see com.merkle.oss.magnolia.templatebuilder.annotation.Template + * @see Area + * @see AvailableComponentClasses + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +public @interface ComponentCategory { +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/ComponentInheritanceMode.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/ComponentInheritanceMode.java new file mode 100644 index 0000000..e959984 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/ComponentInheritanceMode.java @@ -0,0 +1,31 @@ +package com.merkle.oss.magnolia.templatebuilder.annotation.area; + +import info.magnolia.rendering.template.configured.ConfiguredInheritance; + +/** + * Enumeration for modes of inheriting components. + *
    + *
  • ALL All components are inherited
  • + *
  • NONE No components are inherited
  • + *
  • FILTERED Only components having a property inheritable set to true are inherited
  • + *
+ * + * @see Inherits + * @see info.magnolia.rendering.template.InheritanceConfiguration + * @see ConfiguredInheritance + */ +public enum ComponentInheritanceMode { + ALL(ConfiguredInheritance.COMPONENTS_ALL), + NONE(ConfiguredInheritance.COMPONENTS_NONE), + FILTERED(ConfiguredInheritance.COMPONENTS_FILTERED); + + private final String configurationFormat; + + ComponentInheritanceMode(String configurationFormat) { + this.configurationFormat = configurationFormat; + } + + public String getConfigurationFormat() { + return configurationFormat; + } +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/Inherits.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/Inherits.java new file mode 100644 index 0000000..33dc1b2 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/Inherits.java @@ -0,0 +1,16 @@ +package com.merkle.oss.magnolia.templatebuilder.annotation.area; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation used to configure how an area inherits from parent pages. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Inherits { + ComponentInheritanceMode components() default ComponentInheritanceMode.FILTERED; + PropertyInheritanceMode properties() default PropertyInheritanceMode.NONE; +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/PropertyInheritanceMode.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/PropertyInheritanceMode.java new file mode 100644 index 0000000..2a6f2f3 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/annotation/area/PropertyInheritanceMode.java @@ -0,0 +1,29 @@ +package com.merkle.oss.magnolia.templatebuilder.annotation.area; + +import info.magnolia.rendering.template.configured.ConfiguredInheritance; + +/** + * Enumeration for modes of inheriting properties. + *
    + *
  • ALL All properties are inherited
  • + *
  • NONE No properties are inherited
  • + *
+ * + * @see Inherits + * @see info.magnolia.rendering.template.InheritanceConfiguration + * @see ConfiguredInheritance + */ +public enum PropertyInheritanceMode { + ALL(ConfiguredInheritance.PROPERTIES_ALL), + NONE(ConfiguredInheritance.PROPERTIES_NONE); + + private final String configurationFormat; + + PropertyInheritanceMode(final String configurationFormat) { + this.configurationFormat = configurationFormat; + } + + public String getConfigurationFormat() { + return configurationFormat; + } +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/configuration/TemplateBuilderGuiceComponentConfigurer.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/configuration/TemplateBuilderGuiceComponentConfigurer.java new file mode 100644 index 0000000..8cdcb26 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/configuration/TemplateBuilderGuiceComponentConfigurer.java @@ -0,0 +1,15 @@ +package com.merkle.oss.magnolia.templatebuilder.configuration; + +import info.magnolia.objectfactory.guice.AbstractGuiceComponentConfigurer; + +import com.google.inject.TypeLiteral; +import com.google.inject.multibindings.Multibinder; +import com.merkle.oss.magnolia.templatebuilder.annotation.TemplateFactories; + +public class TemplateBuilderGuiceComponentConfigurer extends AbstractGuiceComponentConfigurer { + @Override + protected void configure() { + super.configure(); + Multibinder.newSetBinder(binder(), new TypeLiteral>(){}, TemplateFactories.class); + } +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/AutoGeneratorParameterResolverFactory.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/AutoGeneratorParameterResolverFactory.java new file mode 100644 index 0000000..c0c7d82 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/AutoGeneratorParameterResolverFactory.java @@ -0,0 +1,12 @@ +package com.merkle.oss.magnolia.templatebuilder.parameter; + +import info.magnolia.rendering.template.configured.ConfiguredAreaDefinition; +import info.magnolia.rendering.template.configured.ConfiguredTemplateDefinition; + +import javax.jcr.Node; + +import com.merkle.oss.magnolia.builder.parameter.ParameterResolver; + +public interface AutoGeneratorParameterResolverFactory { + ParameterResolver create(Node node, ConfiguredTemplateDefinition template, ConfiguredAreaDefinition area); +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/AvailabilityParameterResolverFactory.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/AvailabilityParameterResolverFactory.java new file mode 100644 index 0000000..7232cbc --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/AvailabilityParameterResolverFactory.java @@ -0,0 +1,11 @@ +package com.merkle.oss.magnolia.templatebuilder.parameter; + +import info.magnolia.rendering.template.TemplateDefinition; + +import javax.jcr.Node; + +import com.merkle.oss.magnolia.builder.parameter.ParameterResolver; + +public interface AvailabilityParameterResolverFactory { + ParameterResolver create(Node node, TemplateDefinition templateDefinition); +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/DefaultAutoGeneratorParameterResolver.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/DefaultAutoGeneratorParameterResolver.java new file mode 100644 index 0000000..5a26ee9 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/DefaultAutoGeneratorParameterResolver.java @@ -0,0 +1,67 @@ +package com.merkle.oss.magnolia.templatebuilder.parameter; + +import info.magnolia.cms.security.MgnlUser; +import info.magnolia.cms.security.User; +import info.magnolia.context.Context; +import info.magnolia.context.MgnlContext; +import info.magnolia.context.WebContext; +import info.magnolia.rendering.template.TemplateDefinition; + +import javax.jcr.Node; + +import com.merkle.oss.magnolia.builder.parameter.ParameterResolver; + +/** + * Resolves the following types: + *
    + *
  • {@link Node} + *
  • {@link TemplateDefinition} + *
  • {@link WebContext} + *
  • {@link Context} + *
  • {@link User} + *
  • {@link MgnlUser} + *
+ */ +public class DefaultAutoGeneratorParameterResolver extends ParameterResolver { + private final Node node; + private final TemplateDefinition templateDefinition; + + public DefaultAutoGeneratorParameterResolver( + final Node node, + final TemplateDefinition templateDefinition + ) { + super(null); + this.node = node; + this.templateDefinition = templateDefinition; + } + + @Override + public Object resolveParameter(final Class parameterType) { + if (parameterType.equals(Node.class)) { + return node; + } + if (parameterType.equals(TemplateDefinition.class)) { + return templateDefinition; + } + if (parameterType.isAssignableFrom(WebContext.class)) { + return MgnlContext.getWebContext(); + } + if (parameterType.isAssignableFrom(Context.class)) { + return MgnlContext.getInstance(); + } + if (parameterType.isAssignableFrom(User.class)) { + return MgnlContext.getUser(); + } + if (parameterType.isAssignableFrom(MgnlUser.class)) { + return MgnlContext.getUser(); + } + return super.resolveParameter(parameterType); + } + + public static class Factory implements AvailabilityParameterResolverFactory { + @Override + public ParameterResolver create(final Node node, final TemplateDefinition templateDefinition) { + return new DefaultAutoGeneratorParameterResolver(node, templateDefinition); + } + } +} diff --git a/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/DefaultAvailabilityParameterResolver.java b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/DefaultAvailabilityParameterResolver.java new file mode 100644 index 0000000..97638c6 --- /dev/null +++ b/magnolia-templatebuilder/src/main/java/com/merkle/oss/magnolia/templatebuilder/parameter/DefaultAvailabilityParameterResolver.java @@ -0,0 +1,67 @@ +package com.merkle.oss.magnolia.templatebuilder.parameter; + +import info.magnolia.cms.security.MgnlUser; +import info.magnolia.cms.security.User; +import info.magnolia.context.Context; +import info.magnolia.context.MgnlContext; +import info.magnolia.context.WebContext; +import info.magnolia.rendering.template.TemplateDefinition; + +import javax.jcr.Node; + +import com.merkle.oss.magnolia.builder.parameter.ParameterResolver; + +/** + * Resolves the following types: + *
    + *
  • {@link Node} + *
  • {@link TemplateDefinition} + *
  • {@link WebContext} + *
  • {@link Context} + *
  • {@link User} + *
  • {@link MgnlUser} + *
+ */ +public class DefaultAvailabilityParameterResolver extends ParameterResolver { + private final Node node; + private final TemplateDefinition templateDefinition; + + public DefaultAvailabilityParameterResolver( + final Node node, + final TemplateDefinition templateDefinition + ) { + super(null); + this.node = node; + this.templateDefinition = templateDefinition; + } + + @Override + public Object resolveParameter(final Class parameterType) { + if (parameterType.equals(Node.class)) { + return node; + } + if (parameterType.equals(TemplateDefinition.class)) { + return templateDefinition; + } + if (parameterType.isAssignableFrom(WebContext.class)) { + return MgnlContext.getWebContext(); + } + if (parameterType.isAssignableFrom(Context.class)) { + return MgnlContext.getInstance(); + } + if (parameterType.isAssignableFrom(User.class)) { + return MgnlContext.getUser(); + } + if (parameterType.isAssignableFrom(MgnlUser.class)) { + return MgnlContext.getUser(); + } + return super.resolveParameter(parameterType); + } + + public static class Factory implements AvailabilityParameterResolverFactory { + @Override + public ParameterResolver create(final Node node, final TemplateDefinition templateDefinition) { + return new DefaultAvailabilityParameterResolver(node, templateDefinition); + } + } +} diff --git a/magnolia-templatebuilder/src/main/resources/META-INF/magnolia/magnolia-templatebuilder.xml b/magnolia-templatebuilder/src/main/resources/META-INF/magnolia/magnolia-templatebuilder.xml new file mode 100644 index 0000000..4a75d98 --- /dev/null +++ b/magnolia-templatebuilder/src/main/resources/META-INF/magnolia/magnolia-templatebuilder.xml @@ -0,0 +1,35 @@ + + + + magnolia-templatebuilder + Magnolia TemplateBuilder + The TemplateBuilder is a builder for Magnolia templates using java code. + com.merkle.oss.magnolia.templatebuilder.TemplateBuilderModule + ${project.version} + + + main + + com.merkle.oss.magnolia.templatebuilder.configuration.TemplateBuilderGuiceComponentConfigurer + + + com.merkle.oss.magnolia.templatebuilder.TemplateAvailabilityResolver + com.merkle.oss.magnolia.templatebuilder.TemplateAvailabilityResolver + + + com.merkle.oss.magnolia.templatebuilder.parameter.AvailabilityParameterResolverFactory + com.merkle.oss.magnolia.templatebuilder.parameter.DefaultAvailabilityParameterResolver$Factory + + + com.merkle.oss.magnolia.templatebuilder.parameter.AutoGeneratorParameterResolverFactory + com.merkle.oss.magnolia.templatebuilder.parameter.DefaultAutoGeneratorParameterResolver$Factory + + + + + + core + * + + + diff --git a/pom.xml b/pom.xml index de2aeda..0397149 100644 --- a/pom.xml +++ b/pom.xml @@ -39,12 +39,14 @@ magnolia-builder-common magnolia-appbuilder magnolia-dialogbuilder + magnolia-templatebuilder magnolia-virtualUriMapping-builder 6.3.0 + 3.0.0 3.0.2 1.2.0 @@ -78,6 +80,12 @@ pom import + + info.magnolia.advancedcache + magnolia-advanced-cache-dpc + ${magnolia.advancedcache.version} + + com.google.code.findbugs jsr305