Java tutorial
/* * Copyright [2015] [Christian Loehnert] * * 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. */ package de.ks.flatadocdb.metamodel; import com.fasterxml.jackson.databind.introspect.AnnotatedField; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import de.ks.flatadocdb.annotation.Child; import de.ks.flatadocdb.annotation.Children; import de.ks.flatadocdb.annotation.ToMany; import de.ks.flatadocdb.annotation.ToOne; import de.ks.flatadocdb.annotation.lifecycle.LifeCycle; import de.ks.flatadocdb.ifc.*; import de.ks.flatadocdb.metamodel.relation.*; import de.ks.flatadocdb.query.Query; import de.ks.flatadocdb.session.relation.LazyEntity; import javassist.util.proxy.MethodHandler; import javassist.util.proxy.ProxyObject; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import java.io.Serializable; import java.lang.invoke.MethodHandle; import java.lang.reflect.Field; import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; /** * Contains all meta inforamtion needed to store/load a given entity. */ @Immutable public class EntityDescriptor { public static class Builder { public static Builder create() { return new Builder(); } private MethodHandle versionGetterAccess; private MethodHandle versionSetterAccess; private MethodHandle naturalIdFieldAccess; private MethodHandle idGetterAccess; private MethodHandle idSetterAccess; private MethodHandle pathInRepoGetterAccess; private MethodHandle pathInRepoSetterAccess; private EntityPersister persister; private FolderGenerator folderGenerator; private FileGenerator fileGenerator; private LuceneDocumentExtractor extractor; private Class<?> entityClass; private Map<LifeCycle, Set<MethodHandle>> lifecycleMethods; private final Map<Field, PropertyPersister> propertyPersisters = new HashMap<>(); private final Set<ToOneRelation> toOneRelations = new HashSet<>(); private final Set<ToManyRelation> toManyRelations = new HashSet<>(); private final Set<ToOneChildRelation> toOneChildRelations = new HashSet<>(); private final Set<ToManyChildRelation> toManyChildRelations = new HashSet<>(); private final Set<Query<?, ?>> queries = new HashSet<>(); private Builder() { // } public Builder entity(Class<?> entity) { entityClass = entity; return this; } public Builder version(MethodHandle getter, MethodHandle setter) { versionSetterAccess = setter; versionGetterAccess = getter; return this; } public Builder id(MethodHandle getter, MethodHandle setter) { idGetterAccess = getter; idSetterAccess = setter; return this; } public Builder pathInRepo(MethodHandle getter, MethodHandle setter) { pathInRepoGetterAccess = getter; pathInRepoSetterAccess = setter; return this; } public Builder natural(MethodHandle h) { naturalIdFieldAccess = h; return this; } public Builder persister(EntityPersister p) { persister = p; return this; } public Builder fileGenerator(FileGenerator f) { fileGenerator = f; return this; } public Builder folderGenerator(FolderGenerator f) { folderGenerator = f; return this; } public Builder property(Field field, PropertyPersister persister) { propertyPersisters.put(field, persister); return this; } public Builder properties(Map<Field, PropertyPersister> persisters) { propertyPersisters.putAll(persisters); return this; } public Builder extractor(LuceneDocumentExtractor extractor) { this.extractor = extractor; return this; } public Builder lifecycle(Map<LifeCycle, Set<MethodHandle>> methods) { this.lifecycleMethods = methods; return this; } public Builder toOnes(Set<ToOneRelation> relations) { this.toOneRelations.addAll(relations); return this; } public Builder toMany(Set<ToManyRelation> relations) { this.toManyRelations.addAll(relations); return this; } public Builder toOneChild(Set<ToOneChildRelation> relations) { this.toOneChildRelations.addAll(relations); return this; } public Builder toManyChild(Set<ToManyChildRelation> relations) { this.toManyChildRelations.addAll(relations); return this; } public Builder queries(Set<Query<?, ?>> relations) { this.queries.addAll(relations); return this; } public EntityDescriptor build() { return new EntityDescriptor(this); } } protected final Class<?> entityClass; @Nullable protected final MethodHandle naturalIdFieldAccess; protected final MethodHandle versionGetterAccess; protected final MethodHandle versionSetterAccess; protected final MethodHandle idGetterAccess; protected final MethodHandle idSetterAccess; protected final MethodHandle pathInRepoGetterAccess; protected final MethodHandle pathInRepoSetterAccess; protected final EntityPersister persister; protected final FolderGenerator folderGenerator; protected final FileGenerator fileGenerator; protected final LuceneDocumentExtractor luceneExtractor; protected final Map<LifeCycle, Set<MethodHandle>> lifecycleMethods; protected final Map<Field, PropertyPersister> propertyPersisters; protected final Set<ToOneRelation> toOneRelations; protected final Set<ToManyRelation> toManyRelations; protected final Set<ToOneChildRelation> toOneChildRelations; protected final Set<ToManyChildRelation> toManyChildRelations; protected final Set<Relation> allRelations; protected final Set<Relation> childRelations; protected final Set<Relation> normalRelations; protected final Set<Query<?, ?>> queries; public EntityDescriptor(Builder b) { this.entityClass = b.entityClass; this.persister = b.persister; this.idGetterAccess = b.idGetterAccess; this.idSetterAccess = b.idSetterAccess; this.pathInRepoGetterAccess = b.pathInRepoGetterAccess; this.pathInRepoSetterAccess = b.pathInRepoSetterAccess; this.naturalIdFieldAccess = b.naturalIdFieldAccess; this.versionGetterAccess = b.versionGetterAccess; this.versionSetterAccess = b.versionSetterAccess; this.lifecycleMethods = Collections.unmodifiableMap(b.lifecycleMethods); this.propertyPersisters = Collections.unmodifiableMap(b.propertyPersisters); this.toOneRelations = Collections.unmodifiableSet(b.toOneRelations); this.toManyRelations = Collections.unmodifiableSet(b.toManyRelations); this.toOneChildRelations = Collections.unmodifiableSet(b.toOneChildRelations); this.toManyChildRelations = Collections.unmodifiableSet(b.toManyChildRelations); this.folderGenerator = b.folderGenerator; this.fileGenerator = b.fileGenerator; this.luceneExtractor = b.extractor; this.queries = Collections.unmodifiableSet(b.queries); HashSet<Relation> allRels = new HashSet<>(); allRels.addAll(toManyChildRelations); allRels.addAll(toManyRelations); allRels.addAll(toOneChildRelations); allRels.addAll(toOneRelations); this.allRelations = Collections.unmodifiableSet(allRels); HashSet<Relation> childRelations = new HashSet<>(); childRelations.addAll(toManyChildRelations); childRelations.addAll(toOneChildRelations); this.childRelations = Collections.unmodifiableSet(childRelations); HashSet<Relation> normalRelations = new HashSet<>(); normalRelations.addAll(toManyRelations); normalRelations.addAll(toOneRelations); this.normalRelations = Collections.unmodifiableSet(normalRelations); } public Class<?> getEntityClass() { return entityClass; } public EntityPersister getPersister() { return persister; } public FileGenerator getFileGenerator() { return fileGenerator; } public FolderGenerator getFolderGenerator() { return folderGenerator; } public boolean isVersioned() { return versionGetterAccess != null; } public boolean hasNaturalId() { return naturalIdFieldAccess != null; } public Optional<PropertyPersister> getPropertyPersister(Field field) { return Optional.ofNullable(propertyPersisters.get(field)); } public MethodHandle getIdGetterAccess() { return idGetterAccess; } public MethodHandle getIdSetterAccess() { return idSetterAccess; } public Set<ToOneRelation> getToOneRelations() { return toOneRelations; } public Set<ToManyRelation> getToManyRelations() { return toManyRelations; } public Set<ToOneChildRelation> getToOneChildRelations() { return toOneChildRelations; } public Set<ToManyChildRelation> getToManyChildRelations() { return toManyChildRelations; } public Set<Relation> getNormalRelations() { return normalRelations; } public Set<Relation> getChildRelations() { return childRelations; } public String getId(Object entity) { if (entity instanceof ProxyObject) { MethodHandler handler = ((ProxyObject) entity).getHandler(); if (handler instanceof LazyEntity) { return ((LazyEntity) handler).getId(); } } return invokeGetter(idGetterAccess, entity); } public long getVersion(Object entity) { return invokeGetter(versionGetterAccess, entity); } public Path getPathInRepo(Object entity) { return invokeGetter(pathInRepoGetterAccess, entity); } public void writetId(Object entity, String id) { invokeSetter(idSetterAccess, entity, id); } public void writeVersion(Object entity, long version) { invokeSetter(versionSetterAccess, entity, version); } public void writePathInRepo(Object entity, Path path) { invokeSetter(pathInRepoSetterAccess, entity, (Path) path); } @Nullable public Serializable getNaturalId(Object entity) { if (hasNaturalId()) { return invokeGetter(naturalIdFieldAccess, entity); } else { return null; } } @SuppressWarnings("unchecked") private <T> T invokeGetter(MethodHandle handle, Object instance) { try { return (T) handle.invoke(instance); } catch (Throwable t) { throw new RuntimeException(t); } } @SuppressWarnings("unchecked") private <T> T invokeSetter(MethodHandle handle, Object instance, Object param) { try { return (T) handle.invoke(instance, param); } catch (Throwable t) { throw new RuntimeException(t); } } public boolean hasIdAccess() { return idGetterAccess != null; } public boolean isCollectionRelation(AnnotatedMember member) { if (member instanceof AnnotatedField) { Field field = ((AnnotatedField) member).getAnnotated(); return field.isAnnotationPresent(Children.class) || field.isAnnotationPresent(ToMany.class); } return false; } public boolean isRelation(AnnotatedMember member) { if (member instanceof AnnotatedField) { Field field = ((AnnotatedField) member).getAnnotated(); return field.isAnnotationPresent(Child.class) || field.isAnnotationPresent(Children.class) || field.isAnnotationPresent(ToOne.class) || field.isAnnotationPresent(ToMany.class); } return false; } /** * @param property field name * @return the persister if present * @throws IllegalStateException when 2 fields have the same name. */ public Optional<PropertyPersister> getPropertyPersister(String property) throws IllegalStateException { Set<PropertyPersister> persisters = this.propertyPersisters.entrySet().stream() .filter(e -> e.getKey().getName().equals(property)).map(Map.Entry::getValue) .collect(Collectors.toSet()); if (persisters.size() > 1) { throw new IllegalStateException( "Found multiple property persisters for property " + property + " on " + entityClass.getName()); } else if (persisters.size() == 1) { return Optional.of(persisters.iterator().next()); } else { return Optional.empty(); } } public Map<Field, PropertyPersister> getPropertyPersisters() { return propertyPersisters; } public Set<MethodHandle> getLifeCycleMethods(LifeCycle lifeCycle) { Set<MethodHandle> methodHandles = this.lifecycleMethods.get(lifeCycle); methodHandles = methodHandles == null ? Collections.emptySet() : methodHandles; return methodHandles; } public Set<Relation> getAllRelations() { return allRelations; } public LuceneDocumentExtractor getLuceneExtractor() { return luceneExtractor; } public Set<Query<?, ?>> getQueries() { return queries; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof EntityDescriptor)) { return false; } EntityDescriptor entityDescriptor = (EntityDescriptor) o; return !(entityClass != null ? !entityClass.equals(entityDescriptor.entityClass) : entityDescriptor.entityClass != null); } @Override public int hashCode() { return entityClass != null ? entityClass.hashCode() : 0; } }