/**
* Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.seedstack.io.internal;
import com.google.inject.Injector;
import io.nuun.kernel.api.plugin.InitState;
import io.nuun.kernel.api.plugin.context.Context;
import io.nuun.kernel.api.plugin.context.InitContext;
import io.nuun.kernel.api.plugin.request.ClasspathScanRequest;
import io.nuun.kernel.api.plugin.request.ClasspathScanRequestBuilder;
import org.apache.commons.lang.StringUtils;
import org.kametic.specifications.Specification;
import org.seedstack.io.Parser;
import org.seedstack.io.Renderer;
import org.seedstack.io.spi.StaticTemplateLoader;
import org.seedstack.io.spi.TemplateLoader;
import org.seedstack.seed.core.internal.AbstractSeedPlugin;
import org.seedstack.seed.core.internal.utils.SpecificationBuilder;
import org.seedstack.shed.reflect.ClassPredicates;
import javax.inject.Inject;
import javax.inject.Named;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This plugin is responsible for detecting templates, renderers and parsers.
*/
public class IOPlugin extends AbstractSeedPlugin {
private static final Specification<Class<?>> templateLoaderSpec = new SpecificationBuilder<>(ClassPredicates.classIsDescendantOf(TemplateLoader.class)
.and(ClassPredicates.classIsInterface().negate())
.and(ClassPredicates.classModifierIs(Modifier.ABSTRACT).negate())
).build();
private static final Specification<Class<?>> rendererSpec = new SpecificationBuilder<>(ClassPredicates.classIsDescendantOf(Renderer.class)
.and(ClassPredicates.classIsInterface().negate())
.and(ClassPredicates.classModifierIs(Modifier.ABSTRACT).negate())
).build();
private static final Specification<Class<?>> parserSpec = new SpecificationBuilder<>(ClassPredicates.classIsDescendantOf(Parser.class)
.and(ClassPredicates.classIsInterface().negate())
.and(ClassPredicates.classModifierIs(Modifier.ABSTRACT).negate())
).build();
private static final String META_INF_TEMPLATES = "META-INF/templates/";
private final List<StaticTemplateLoader<?>> templateLoaderRegexs = new ArrayList<>();
private final List<TemplateLoader<?>> templateLoaders = new ArrayList<>();
private final Map<String, Class<Renderer>> renderers = new HashMap<>();
private final Map<String, Class<Parser>> parsers = new HashMap<>();
@Inject
private Injector injector;
@Override
public String name() {
return "seed-io-function";
}
@Override
public String pluginPackageRoot() {
return "META-INF.templates";
}
@SuppressWarnings("unchecked")
@Override
public Collection<ClasspathScanRequest> classpathScanRequests() {
// Scan templateLoaders
if (round.isFirst()) {
return classpathScanRequestBuilder()
.specification(templateLoaderSpec)
.specification(rendererSpec)
.specification(parserSpec)
.build();
} else {
// for each templateLoader with regex, parse "META-INF/templates/" directory to find corresponding templates
ClasspathScanRequestBuilder classpathScanRequestBuilder = classpathScanRequestBuilder();
for (StaticTemplateLoader<?> templateLoader : templateLoaderRegexs) {
classpathScanRequestBuilder.resourcesRegex(templateLoader.templatePathRegex());
}
return classpathScanRequestBuilder.build();
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public InitState initialize(InitContext initContext) {
if (round.isFirst()) {
Map<Specification, Collection<Class<?>>> scannedClassesByAnnotationClass = initContext.scannedTypesBySpecification();
// Gets all implementation of TemplateLoader
Collection<Class<?>> templateLoaderClasses = scannedClassesByAnnotationClass.get(templateLoaderSpec);
if (templateLoaderClasses != null) {
for (Class<?> templateLoaderClass : templateLoaderClasses) {
if (TemplateLoader.class.isAssignableFrom(templateLoaderClass)) {
if (StaticTemplateLoader.class.isAssignableFrom(templateLoaderClass)) {
Class<StaticTemplateLoader<?>> staticTemplateLoaderClass = (Class<StaticTemplateLoader<?>>) templateLoaderClass;
StaticTemplateLoader<?> staticTemplateLoader;
try {
Constructor<StaticTemplateLoader<?>> declaredConstructor = staticTemplateLoaderClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
staticTemplateLoader = declaredConstructor.newInstance();
} catch (Exception e) {
throw new RuntimeException("Unable to instantiate StaticTemplateLoader " + staticTemplateLoaderClass.getCanonicalName(), e);
}
templateLoaderRegexs.add(staticTemplateLoader);
} else {
Class<TemplateLoader<?>> dynamicTemplateLoaderClass = (Class<TemplateLoader<?>>) templateLoaderClass;
TemplateLoader<?> dynamicTemplateLoader;
try {
dynamicTemplateLoader = dynamicTemplateLoaderClass.newInstance();
} catch (Exception e) {
throw new RuntimeException("Unable to instantiate TemplateLoader " + dynamicTemplateLoaderClass.getCanonicalName(), e);
}
templateLoaders.add(dynamicTemplateLoader);
}
}
}
}
// Gets all implementation of Renderer
Collection<Class<?>> rendererClasses = scannedClassesByAnnotationClass.get(rendererSpec);
if (rendererClasses != null) {
for (Class<?> rendererClass : rendererClasses) {
if (Renderer.class.isAssignableFrom(rendererClass) && rendererClass.isAnnotationPresent(Named.class)) {
renderers.put(rendererClass.getAnnotation(Named.class).value(), (Class<Renderer>) rendererClass);
}
}
}
// Gets all implementation of Parser
Collection<Class<?>> parserClasses = scannedClassesByAnnotationClass.get(parserSpec);
if (parserClasses != null) {
for (Class<?> parserClass : parserClasses) {
if (Parser.class.isAssignableFrom(parserClass) && parserClass.isAnnotationPresent(Named.class)) {
parsers.put(parserClass.getAnnotation(Named.class).value(), (Class<Parser>) parserClass);
}
}
}
return InitState.NON_INITIALIZED;
} else {
Map<String, Collection<String>> mapResourcesByRegex = initContext.mapResourcesByRegex();
for (StaticTemplateLoader<?> staticTemplateLoader : templateLoaderRegexs) {
Map<String, URL> templateURLs = new HashMap<>();
Collection<String> templateUrls = mapResourcesByRegex.get(staticTemplateLoader.templatePathRegex());
for (String templateUrl : templateUrls) {
if (templateUrl.startsWith(META_INF_TEMPLATES)) {
String templateName = StringUtils.substringAfter(templateUrl, META_INF_TEMPLATES);
Matcher matcher = Pattern.compile(staticTemplateLoader.templatePathRegex()).matcher(templateName);
if (matcher.matches()) {
URL url = IOPlugin.class.getResource("/" + templateUrl);
if (url != null) {
templateURLs.put(matcher.group(1), url);
}
}
}
}
staticTemplateLoader.setTemplateURLs(templateURLs);
this.templateLoaders.add(staticTemplateLoader);
}
}
return InitState.INITIALIZED;
}
@Override
public void start(Context context) {
// template loaders have been created manually in init step
for (TemplateLoader<?> templateLoader : templateLoaders) {
injector.injectMembers(templateLoader);
}
}
@Override
public Object nativeUnitModule() {
return new IOModule(templateLoaders, renderers, parsers);
}
}