org.ldp4j.application.kernel.template.MutableTemplateLibrary Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ldp4j-application-kernel-core Show documentation
Show all versions of ldp4j-application-kernel-core Show documentation
Implementation of the LDP4j Application Kernel
The newest version!
/**
* #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
* This file is part of the LDP4j Project:
* http://www.ldp4j.org/
*
* Center for Open Middleware
* http://www.centeropenmiddleware.com/
* #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
* Copyright (C) 2014-2016 Center for Open Middleware.
* #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
* 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.
* #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
* Artifact : org.ldp4j.framework:ldp4j-application-kernel-core:0.2.2
* Bundle : ldp4j-application-kernel-core-0.2.2.jar
* #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
*/
package org.ldp4j.application.kernel.template;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.lang.annotation.Annotation;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.ldp4j.application.ext.ContainerHandler;
import org.ldp4j.application.ext.ResourceHandler;
import org.ldp4j.application.ext.annotations.Attachment;
import org.ldp4j.application.ext.annotations.BasicContainer;
import org.ldp4j.application.ext.annotations.DirectContainer;
import org.ldp4j.application.ext.annotations.IndirectContainer;
import org.ldp4j.application.ext.annotations.MembershipRelation;
import org.ldp4j.application.ext.annotations.Resource;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
final class MutableTemplateLibrary implements TemplateLibrary {
private static interface TemplateRegistry {
void register(Class handlerClass, ResourceTemplate template);
}
private static interface TemplateResolver {
ResourceTemplate resolve(Class targetClass);
}
private abstract static class Processor> {
private final Class annotationClass;
public Processor(Class annotationClass) {
this.annotationClass = annotationClass;
}
M preProcess(Annotation annotation, Class handler) {
checkArgument(annotationClass.isInstance(annotation),"Invalid annotation");
return doProcess(annotationClass.cast(annotation),handler);
}
void postProcess(Annotation annotation, M template, TemplateResolver resolver) {
checkArgument(annotationClass.isInstance(annotation),"Invalid annotation");
doPostprocess(annotationClass.cast(annotation),template,resolver);
}
protected M doProcess(A annotation, Class handler) {
M template = createTemplate(annotation,handler);
template.setName(nullable(name(annotation)));
template.setDescription(nullable(description(annotation)));
return template;
}
protected void doPostprocess(A annotation, M template, TemplateResolver resolver) {
for(Attachment attachment:attachments(annotation)) {
try {
MutableAttachedTemplate attachedTemplate = template.attachTemplate(attachment.id(), resolver.resolve(attachment.handler()), attachment.path());
updateAttachmentPredicate(template,attachment,attachedTemplate);
} catch (IllegalArgumentException e) {
throw new InvalidAttachmentDefinitionException(template.id(),attachment.id(),"Invalid attachment definition",e);
}
}
}
private void updateAttachmentPredicate(M template, Attachment attachment, MutableAttachedTemplate attachedTemplate) {
String predicate = attachment.predicate();
if(predicate!=null && predicate.length()>0) {
try {
attachedTemplate.setPredicate(createOptionalURI(template.id(),predicate,"The attachment predicate"));
} catch (TemplateCreationException e) {
throw new InvalidAttachmentDefinitionException(
template.id(),
attachment.id(),
String.format("Attachment predicate value '%s' of attached template '%s' is not valid",predicate,attachedTemplate.id()),
e);
}
}
}
protected abstract M createTemplate(A annotation, Class handler);
protected abstract String name(A annotation);
protected abstract String description(A annotation);
protected abstract Attachment[] attachments(A annotation);
protected final String nullable(String value) {
String result=value.trim();
if(result.isEmpty()) {
result=null;
}
return result;
}
protected final URI createMandatoryURI(String id, String predicate, String uriType) {
if(predicate.isEmpty()) {
throw new TemplateCreationException(id,uriType+" cannot be empty");
}
return createOptionalURI(id, predicate, uriType);
}
private URI createOptionalURI(String id, String predicate, String uriType) {
try {
URI uri = new URI(predicate);
if(uri.normalize().equals(URI.create(""))) {
throw new TemplateCreationException(id,uriType+" cannot be the null URI");
} else if(uri.isOpaque()) {
/**
* TODO: Allow using opaque URIs whenever the RDF handling
* backend supports it (for the time being we are using
* Sesame and it requires using HTTP URIs)
*/
throw new TemplateCreationException(id,String.format("%s cannot be a opaque URI (%s)",uriType,predicate));
} else if(!uri.isAbsolute()) {
throw new TemplateCreationException(id,String.format("%s cannot be a hierarchical relative URI (%s)",uriType,predicate));
}
return uri;
} catch (URISyntaxException e) {
throw new TemplateCreationException(id,String.format("%s value '%s' is not valid",uriType,predicate),e);
}
}
}
private abstract static class ContainerProcessor extends Processor {
public ContainerProcessor(Class annotationClass) {
super(annotationClass);
}
@Override
protected T doProcess(A annotation, Class handler) {
T template = super.doProcess(annotation, handler);
template.setMemberPath(nullable(memberPath(annotation)));
return template;
}
@Override
protected void doPostprocess(A annotation, T template, TemplateResolver resolver) {
super.doPostprocess(annotation,template, resolver);
Class handler = memberHandler(annotation);
ResourceTemplate memberTemplate = resolver.resolve(handler);
if(memberTemplate==null) {
throw new TemplateCreationException(template.id(), "Could not resolve template for member handler '"+handler.getCanonicalName()+"' ");
}
template.setMemberTemplate(memberTemplate);
}
protected abstract String memberPath(A annotation);
protected abstract Class memberHandler(A annotation);
}
private abstract static class MembershipAwareContainerProcessor extends ContainerProcessor {
public MembershipAwareContainerProcessor(Class annotationClass) {
super(annotationClass);
}
@Override
protected T doProcess(A annotation, Class handler) {
T template = super.doProcess(annotation, handler);
template.setMembershipRelation(membershipRelation(annotation));
template.setMembershipPredicate(membershipPredicate(annotation, template));
return template;
}
private URI membershipPredicate(A annotation, T template) {
return createMandatoryURI(template.id(), membershipPredicate(annotation).trim(), "The membership predicate");
}
protected abstract String membershipPredicate(A annotation);
protected abstract MembershipRelation membershipRelation(A annotation);
}
private static class ResourceProcessor extends Processor {
public ResourceProcessor() {
super(Resource.class);
}
@Override
protected MutableResourceTemplate createTemplate(Resource annotation, Class handler) {
return new MutableResourceTemplate(annotation.id(), handler);
}
@Override
protected String name(Resource annotation) {
return annotation.name();
}
@Override
protected String description(Resource annotation) {
return annotation.description();
}
@Override
protected Attachment[] attachments(Resource annotation) {
return annotation.attachments();
}
}
private static class BasicContainerProcessor extends ContainerProcessor {
public BasicContainerProcessor() {
super(BasicContainer.class);
}
@Override
protected MutableBasicContainerTemplate createTemplate(BasicContainer annotation, Class handler) {
return new MutableBasicContainerTemplate(annotation.id(), handler);
}
@Override
protected String name(BasicContainer annotation) {
return annotation.name();
}
@Override
protected String description(BasicContainer annotation) {
return annotation.description();
}
@Override
protected Attachment[] attachments(BasicContainer annotation) {
return annotation.attachments();
}
@Override
protected String memberPath(BasicContainer annotation) {
return annotation.memberPath();
}
@Override
protected Class memberHandler(BasicContainer annotation) {
return annotation.memberHandler();
}
}
private static class DirectContainerProcessor extends MembershipAwareContainerProcessor {
public DirectContainerProcessor() {
super(DirectContainer.class);
}
@Override
protected MutableDirectContainerTemplate createTemplate(DirectContainer annotation, Class handler) {
return new MutableDirectContainerTemplate(annotation.id(), handler);
}
@Override
protected String name(DirectContainer annotation) {
return annotation.name();
}
@Override
protected String description(DirectContainer annotation) {
return annotation.description();
}
@Override
protected Attachment[] attachments(DirectContainer annotation) {
return annotation.attachments();
}
@Override
protected String memberPath(DirectContainer annotation) {
return annotation.memberPath();
}
@Override
protected Class memberHandler(DirectContainer annotation) {
return annotation.memberHandler();
}
@Override
protected String membershipPredicate(DirectContainer annotation) {
return annotation.membershipPredicate();
}
@Override
protected MembershipRelation membershipRelation(DirectContainer annotation) {
return annotation.membershipRelation();
}
}
private static class IndirectContainerProcessor extends MembershipAwareContainerProcessor {
public IndirectContainerProcessor() {
super(IndirectContainer.class);
}
@Override
protected MutableIndirectContainerTemplate createTemplate(IndirectContainer annotation, Class handler) {
String rawInsertedContentRelation = annotation.insertedContentRelation().trim();
URI insertedContentRelation=createMandatoryURI(annotation.id(), rawInsertedContentRelation, "The inserted content relation");
return new MutableIndirectContainerTemplate(annotation.id(),handler,insertedContentRelation);
}
@Override
protected String name(IndirectContainer annotation) {
return annotation.name();
}
@Override
protected String description(IndirectContainer annotation) {
return annotation.description();
}
@Override
protected Attachment[] attachments(IndirectContainer annotation) {
return annotation.attachments();
}
@Override
protected String memberPath(IndirectContainer annotation) {
return annotation.memberPath();
}
@Override
protected Class memberHandler(IndirectContainer annotation) {
return annotation.memberHandler();
}
@Override
protected String membershipPredicate(IndirectContainer annotation) {
return annotation.membershipPredicate();
}
@Override
protected MembershipRelation membershipRelation(IndirectContainer annotation) {
return annotation.membershipRelation();
}
}
private static final class SupportedAnnotation {
private static final SupportedAnnotation RESOURCE=new SupportedAnnotation(Resource.class,new ResourceProcessor(),false);
private static final SupportedAnnotation BASIC_CONTAINER=new SupportedAnnotation(BasicContainer.class,new BasicContainerProcessor(),true);
private static final SupportedAnnotation DIRECT_CONTAINER=new SupportedAnnotation(DirectContainer.class,new DirectContainerProcessor(),true);
private static final SupportedAnnotation INDIRECT_CONTAINER=new SupportedAnnotation(IndirectContainer.class,new IndirectContainerProcessor(),true);
private final Class annotationClass;
private final Processor> processor;
private final boolean container;
@SuppressWarnings("unchecked")
private > SupportedAnnotation(Class annotationClass, Processor processor, boolean container) {
this.annotationClass = annotationClass;
this.container = container;
this.processor = (Processor>) processor;
}
ResourceTemplate toTemplate(Annotation annotation, Class targetClass, TemplateRegistry templateRegistry, TemplateResolver templateResolver) {
AbstractMutableTemplate template = this.processor.preProcess(annotation,targetClass);
templateRegistry.register(targetClass, template);
this.processor.postProcess(annotation, template, templateResolver);
return template;
}
boolean isContainer() {
return this.container;
}
static SupportedAnnotation[] values() {
return new SupportedAnnotation[]{
SupportedAnnotation.RESOURCE,
SupportedAnnotation.BASIC_CONTAINER,
SupportedAnnotation.DIRECT_CONTAINER,
SupportedAnnotation.INDIRECT_CONTAINER
};
}
static SupportedAnnotation fromAnnotation(Annotation annotation) {
for(SupportedAnnotation candidate:values()) {
if(candidate.supports(annotation)) {
return candidate;
}
}
return null;
}
private boolean supports(Annotation annotation) {
return this.annotationClass.isInstance(annotation);
}
static String toString(Collection values) {
StringBuilder builder=new StringBuilder();
for(Iterator it=values.iterator();it.hasNext();) {
SupportedAnnotation candidate = it.next();
builder.append(candidate.annotationName());
if(it.hasNext()) {
builder.append(", ");
}
}
return builder.toString();
}
private String annotationName() {
return annotationClass.getCanonicalName();
}
static String toString(SupportedAnnotation... values) {
return toString(Arrays.asList(values));
}
}
private final class TemplateLoaderContext implements TemplateResolver, TemplateRegistry {
private final Map templatesByHandler;
private final Map templatesById;
private TemplateLoaderContext() {
this.templatesByHandler=Maps.newLinkedHashMap();
this.templatesById=Maps.newLinkedHashMap();
}
private boolean isRegistered(Class handlerClass) {
return this.templatesByHandler.containsKey(HandlerId.createId(handlerClass));
}
@Override
public ResourceTemplate resolve(Class targetClass) {
ResourceTemplate template = retrieve(targetClass);
if(template==null) {
template=MutableTemplateLibrary.this.loadTemplates(targetClass,this);
}
return template;
}
@Override
public void register(Class handlerClass, ResourceTemplate template) {
ResourceTemplate previousTemplate = retrieve(handlerClass);
if(previousTemplate!=null) {
if(template==previousTemplate) {
return;
}
throw new TemplateCreationException(template.id(), String.format("Cannot register two templates with the same handler (new: %s, registered: %s)",template,previousTemplate));
}
previousTemplate=this.templatesById.get(template.id());
if(previousTemplate!=null) {
throw new TemplateCreationException(template.id(), String.format("Cannot register two templates with the same identifier (new: %s, registered: %s)",template,previousTemplate));
}
this.templatesByHandler.put(HandlerId.createId(handlerClass), template);
this.templatesById.put(template.id(), template);
}
private ResourceTemplate retrieve(Class handlerClass) {
return this.templatesByHandler.get(HandlerId.createId(handlerClass));
}
private ResourceTemplate resolve(String templateId) {
return this.templatesById.get(templateId);
}
private Collection registeredTemplates() {
return ImmutableSet.copyOf(this.templatesByHandler.values());
}
}
private final TemplateLoaderContext context;
MutableTemplateLibrary() {
this.context=new TemplateLoaderContext();
}
private Class toResourceHandlerClass(Class targetClass) {
checkArgument(ResourceHandler.class.isAssignableFrom(targetClass),"Class '%s' does not implement '%s'",targetClass.getCanonicalName(),ResourceHandler.class.getCanonicalName());
return targetClass.asSubclass(ResourceHandler.class);
}
private ResourceTemplate loadTemplates(Class targetClass, TemplateLoaderContext resolver) {
ResourceTemplate template=createTemplate(targetClass,resolver);
resolver.register(targetClass,template);
return template;
}
private ResourceTemplate createTemplate(Class targetClass, final TemplateLoaderContext ctx) {
Map annotations=new LinkedHashMap();
SupportedAnnotation found=null;
for(Annotation annotation:targetClass.getDeclaredAnnotations()) {
SupportedAnnotation type=SupportedAnnotation.fromAnnotation(annotation);
if(type!=null) {
found=type;
annotations.put(type, annotation);
}
}
if(annotations.size()==0) {
throw new IllegalArgumentException(String.format("Class '%s' has not any of the required annotations (%s)",targetClass.getCanonicalName(),SupportedAnnotation.toString(SupportedAnnotation.values())));
} else if(annotations.size()>1) {
throw new IllegalArgumentException(String.format("Class '%s' is annotated with more than of the supported annotations (%s)",targetClass.getCanonicalName(),SupportedAnnotation.toString(annotations.keySet())));
} else if(found.isContainer() && !ContainerHandler.class.isAssignableFrom(targetClass)) {
throw new IllegalArgumentException(String.format("Not-container handler class '%s' is annotated as a container (%s)",targetClass.getCanonicalName(),SupportedAnnotation.toString(found)));
}
return found.toTemplate(annotations.get(found),targetClass,ctx,ctx);
}
boolean isHandlerRegistered(Class handlerClass) {
return findByHandler(toResourceHandlerClass(handlerClass))!=null;
}
ResourceTemplate registerHandler(Class targetClass) {
Class handlerClass=toResourceHandlerClass(targetClass);
checkArgument(!this.context.isRegistered(handlerClass),"Handler '%s' is already registered",handlerClass.getCanonicalName());
return loadTemplates(handlerClass,this.context);
}
@Override
public ResourceTemplate findByHandler(Class handlerClass) {
return this.context.retrieve(handlerClass);
}
@Override
public ResourceTemplate findById(String templateId) {
return this.context.resolve(templateId);
}
@Override
public boolean contains(ResourceTemplate template) {
checkNotNull(template,"Template cannot be null");
return findByHandler(template.handlerClass())!=null;
}
@Override
public void accept(TemplateVisitor visitor) {
checkNotNull(visitor,"Template visitor cannot be null");
for(ResourceTemplate template:this.context.registeredTemplates()) {
template.accept(visitor);
}
}
@Override
public String toString() {
return
MoreObjects.
toStringHelper(getClass()).
add("templates",this.context.registeredTemplates()).
toString();
}
}