Java tutorial
/* * Copyright (c) 2008-2016 Haulmont. * * 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 com.haulmont.cuba.core.sys; import com.haulmont.bali.datastruct.Pair; import com.haulmont.bali.util.ReflectionHelper; import com.haulmont.chile.core.annotations.NamePattern; import com.haulmont.chile.core.loader.MetadataLoader; import com.haulmont.chile.core.model.MetaClass; import com.haulmont.chile.core.model.MetaModel; import com.haulmont.chile.core.model.MetaProperty; import com.haulmont.chile.core.model.Session; import com.haulmont.chile.core.model.impl.*; import com.haulmont.cuba.core.entity.BaseGenericIdEntity; import com.haulmont.cuba.core.entity.BaseIntegerIdEntity; import com.haulmont.cuba.core.entity.BaseLongIdEntity; import com.haulmont.cuba.core.entity.Entity; import com.haulmont.cuba.core.entity.annotation.*; import com.haulmont.cuba.core.global.*; import com.haulmont.cuba.core.sys.MetadataBuildSupport.EntityClassInfo; import org.apache.commons.lang.StringUtils; import org.perf4j.StopWatch; import org.perf4j.log4j.Log4JStopWatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.inject.Inject; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; @Component(Metadata.NAME) public class MetadataImpl implements Metadata { protected Logger log = LoggerFactory.getLogger(getClass()); protected volatile Session session; @Inject protected ViewRepository viewRepository; @Inject protected ExtendedEntities extendedEntities; @Inject protected MetadataTools tools; @Inject protected PersistentEntitiesMetadataLoader metadataLoader; @Inject protected Resources resources; @Inject protected MetadataBuildSupport metadataBuildSupport; @Inject protected NumberIdSource numberIdSource; private static final Pattern JAVA_CLASS_PATTERN = Pattern .compile("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)*[a-zA-Z_$][a-zA-Z\\d_$]*"); @Override public Session getSession() { if (session == null) { synchronized (this) { if (session == null) { initMetadata(); } } } return session; } @Override public ViewRepository getViewRepository() { return viewRepository; } @Override public ExtendedEntities getExtendedEntities() { return extendedEntities; } @Override public MetadataTools getTools() { return tools; } protected void loadMetadata(MetadataLoader loader, Map<String, List<EntityClassInfo>> packages) { for (Map.Entry<String, List<EntityClassInfo>> entry : packages.entrySet()) { List<String> classNames = entry.getValue().stream().map(entityClassInfo -> entityClassInfo.name) .collect(Collectors.toList()); loader.loadModel(entry.getKey(), classNames); } } protected <T> T __create(Class<T> entityClass) { @SuppressWarnings("unchecked") Class<T> extClass = extendedEntities.getEffectiveClass(entityClass); try { T obj = extClass.newInstance(); assignIdentifier((Entity) obj); invokePostConstructMethods((Entity) obj); return obj; } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) { throw new RuntimeException(e); } } @SuppressWarnings("unchecked") protected void assignIdentifier(Entity entity) { if (!(entity instanceof BaseGenericIdEntity)) return; MetaClass metaClass = getClassNN(entity.getClass()); // assign ID only if the entity is stored to the main database if (!Stores.MAIN.equals(tools.getStoreName(metaClass))) return; if (entity instanceof BaseLongIdEntity) { ((BaseGenericIdEntity<Long>) entity).setId(numberIdSource.createLongId(metaClass.getName())); } else if (entity instanceof BaseIntegerIdEntity) { ((BaseGenericIdEntity<Integer>) entity).setId(numberIdSource.createIntegerId(metaClass.getName())); } } protected void invokePostConstructMethods(Entity entity) throws InvocationTargetException, IllegalAccessException { List<Method> postConstructMethods = new ArrayList<>(4); List<String> methodNames = new ArrayList<>(4); Class clazz = entity.getClass(); while (clazz != Object.class) { Method[] classMethods = clazz.getDeclaredMethods(); for (Method method : classMethods) { if (method.isAnnotationPresent(PostConstruct.class) && !methodNames.contains(method.getName())) { postConstructMethods.add(method); methodNames.add(method.getName()); } } clazz = clazz.getSuperclass(); } ListIterator<Method> iterator = postConstructMethods.listIterator(postConstructMethods.size()); while (iterator.hasPrevious()) { Method method = iterator.previous(); if (!method.isAccessible()) { method.setAccessible(true); } method.invoke(entity); } } @Override public <T> T create(Class<T> entityClass) { return __create(entityClass); } @SuppressWarnings("unchecked") @Override public Entity create(MetaClass metaClass) { return (Entity) __create(metaClass.getJavaClass()); } @SuppressWarnings("unchecked") @Override public Entity create(String entityName) { MetaClass metaClass = getSession().getClassNN(entityName); return (Entity) __create(metaClass.getJavaClass()); } protected void initMetadata() { log.info("Initializing metadata"); long startTime = System.currentTimeMillis(); Map<String, List<EntityClassInfo>> entityPackages = metadataBuildSupport.getEntityPackages(); loadMetadata(metadataLoader, entityPackages); metadataLoader.postProcess(); Session session = metadataLoader.getSession(); initStoreMetaAnnotations(session, entityPackages); initExtensionMetaAnnotations(session); List<MetadataBuildSupport.XmlAnnotations> xmlAnnotations = metadataBuildSupport.getEntityAnnotations(); for (MetaClass metaClass : session.getClasses()) { initMetaAnnotations(session, metaClass); addMetaAnnotationsFromXml(xmlAnnotations, metaClass); } replaceExtendedMetaClasses(session); this.session = new CachingMetadataSession(session); SessionImpl.setSerializationSupportSession(this.session); log.info("Metadata initialized in " + (System.currentTimeMillis() - startTime) + "ms"); } protected void replaceExtendedMetaClasses(Session session) { StopWatch sw = new Log4JStopWatch("Metadata.replaceExtendedMetaClasses"); for (MetaModel model : session.getModels()) { MetaModelImpl modelImpl = (MetaModelImpl) model; List<Pair<MetaClass, MetaClass>> replaceMap = new ArrayList<>(); for (MetaClass metaClass : modelImpl.getClasses()) { MetaClass effectiveMetaClass = session.getClass(extendedEntities.getEffectiveClass(metaClass)); if (effectiveMetaClass != metaClass) { replaceMap.add(new Pair<>(metaClass, effectiveMetaClass)); } for (MetaProperty metaProperty : metaClass.getOwnProperties()) { MetaPropertyImpl propertyImpl = (MetaPropertyImpl) metaProperty; // replace domain Class effectiveDomainClass = extendedEntities.getEffectiveClass(metaProperty.getDomain()); MetaClass effectiveDomainMeta = session.getClass(effectiveDomainClass); if (metaProperty.getDomain() != effectiveDomainMeta) { propertyImpl.setDomain(effectiveDomainMeta); } if (metaProperty.getRange().isClass()) { // replace range class ClassRange range = (ClassRange) metaProperty.getRange(); Class effectiveRangeClass = extendedEntities.getEffectiveClass(range.asClass()); MetaClass effectiveRangeMeta = session.getClass(effectiveRangeClass); if (effectiveRangeMeta != range.asClass()) { ClassRange newRange = new ClassRange(effectiveRangeMeta); newRange.setCardinality(range.getCardinality()); newRange.setOrdered(range.isOrdered()); ((MetaPropertyImpl) metaProperty).setRange(newRange); } } } } for (Pair<MetaClass, MetaClass> replace : replaceMap) { MetaClass replacedMetaClass = replace.getFirst(); extendedEntities.registerReplacedMetaClass(replacedMetaClass); MetaClassImpl effectiveMetaClass = (MetaClassImpl) replace.getSecond(); modelImpl.registerClass(replacedMetaClass.getName(), replacedMetaClass.getJavaClass(), effectiveMetaClass); } } sw.stop(); } protected void initStoreMetaAnnotations(Session session, Map<String, List<EntityClassInfo>> entityPackages) { if (Stores.getAdditional().isEmpty()) return; Map<String, String> nameToStoreMap = new HashMap<>(); for (List<EntityClassInfo> list : entityPackages.values()) { for (EntityClassInfo entityClassInfo : list) { if (nameToStoreMap.containsKey(entityClassInfo.name)) { throw new IllegalStateException( "Entity cannot belong to more than one store: " + entityClassInfo.name); } nameToStoreMap.put(entityClassInfo.name, entityClassInfo.store); } } for (MetaClass metaClass : session.getClasses()) { String className = metaClass.getJavaClass().getName(); String store = nameToStoreMap.get(className); if (store != null) metaClass.getAnnotations().put(Stores.PROP_NAME, store); } } /** * Initialize connections between extended and base entities. * * @param session metadata session which is being initialized */ protected void initExtensionMetaAnnotations(Session session) { for (MetaClass metaClass : session.getClasses()) { Class<?> javaClass = metaClass.getJavaClass(); List<Class> superClasses = new ArrayList<>(); Extends extendsAnnotation = javaClass.getAnnotation(Extends.class); while (extendsAnnotation != null) { Class<? extends Entity> superClass = extendsAnnotation.value(); superClasses.add(superClass); extendsAnnotation = superClass.getAnnotation(Extends.class); } for (Class superClass : superClasses) { metaClass.getAnnotations().put(Extends.class.getName(), superClass); MetaClass superMetaClass = session.getClassNN(superClass); Class<?> extendedByClass = (Class) superMetaClass.getAnnotations().get(ExtendedBy.class.getName()); if (extendedByClass != null && !javaClass.equals(extendedByClass)) { if (javaClass.isAssignableFrom(extendedByClass)) continue; else if (!extendedByClass.isAssignableFrom(javaClass)) throw new IllegalStateException(superClass + " is already extended by " + extendedByClass); } superMetaClass.getAnnotations().put(ExtendedBy.class.getName(), javaClass); } } } /** * Initialize entity annotations from class-level Java annotations. * <p>Can be overridden in application projects to handle application-specific annotations.</p> * * @param session metadata session which is being initialized * @param metaClass MetaClass instance to assign annotations */ protected void initMetaAnnotations(Session session, MetaClass metaClass) { addMetaAnnotation(metaClass, NamePattern.class.getName(), new AnnotationValue() { @Override public Object get(Class<?> javaClass) { NamePattern annotation = javaClass.getAnnotation(NamePattern.class); return annotation == null ? null : annotation.value(); } }); addMetaAnnotation(metaClass, EnableRestore.class.getName(), new AnnotationValue() { @Override public Object get(Class<?> javaClass) { EnableRestore annotation = javaClass.getAnnotation(EnableRestore.class); return annotation == null ? null : annotation.value(); } }); addMetaAnnotation(metaClass, TrackEditScreenHistory.class.getName(), new AnnotationValue() { @Override public Object get(Class<?> javaClass) { TrackEditScreenHistory annotation = javaClass.getAnnotation(TrackEditScreenHistory.class); return annotation == null ? null : annotation.value(); } }); // @SystemLevel is not propagated down to the hierarchy Class<?> javaClass = metaClass.getJavaClass(); SystemLevel annotation = javaClass.getAnnotation(SystemLevel.class); if (annotation != null) { metaClass.getAnnotations().put(SystemLevel.class.getName(), annotation.value()); metaClass.getAnnotations().put(SystemLevel.class.getName() + SystemLevel.PROPAGATE, annotation.propagate()); } } /** * Add a meta-annotation from class annotation. * * @param metaClass entity's meta-class * @param name meta-annotation name * @param annotationValue annotation value extractor instance */ protected void addMetaAnnotation(MetaClass metaClass, String name, AnnotationValue annotationValue) { Object value = annotationValue.get(metaClass.getJavaClass()); if (value == null) { for (MetaClass ancestor : metaClass.getAncestors()) { value = annotationValue.get(ancestor.getJavaClass()); if (value != null) break; } } if (value != null) { metaClass.getAnnotations().put(name, value); } } /** * Initialize entity annotations from definition in <code>metadata.xml</code>. * <p>Can be overridden in application projects to handle application-specific annotations.</p> * * @param xmlAnnotations map of class name to annotations map * @param metaClass MetaClass instance to assign annotations */ protected void addMetaAnnotationsFromXml(List<MetadataBuildSupport.XmlAnnotations> xmlAnnotations, MetaClass metaClass) { for (MetadataBuildSupport.XmlAnnotations xmlAnnotation : xmlAnnotations) { if (xmlAnnotation.name.equals(metaClass.getJavaClass().getName())) { for (Map.Entry<String, String> annEntry : xmlAnnotation.annotations.entrySet()) { metaClass.getAnnotations().put(annEntry.getKey(), inferMetaAnnotationType(annEntry.getValue())); } for (MetadataBuildSupport.XmlAnnotations attributeAnnotation : xmlAnnotation.attributeAnnotations) { MetaProperty property = metaClass.getPropertyNN(attributeAnnotation.name); for (Map.Entry<String, String> entry : attributeAnnotation.annotations.entrySet()) { property.getAnnotations().put(entry.getKey(), inferMetaAnnotationType(entry.getValue())); } } break; } } } protected Object inferMetaAnnotationType(String str) { Object val; if (str != null && (str.equalsIgnoreCase("true") || str.equalsIgnoreCase("false"))) val = Boolean.valueOf(str); else if (str != null && JAVA_CLASS_PATTERN.matcher(str).matches()) { try { val = ReflectionHelper.loadClass(str); } catch (ClassNotFoundException e) { val = str; } } else if (!"".equals(str) && StringUtils.isNumeric(str)) { val = Integer.valueOf(str); } else val = str; return val; } @Override public MetaModel getModel(String name) { return getSession().getModel(name); } @Override public Collection<MetaModel> getModels() { return getSession().getModels(); } @Nullable @Override public MetaClass getClass(String name) { return getSession().getClass(name); } @Override public MetaClass getClassNN(String name) { return getSession().getClassNN(name); } @Nullable @Override public MetaClass getClass(Class<?> clazz) { return getSession().getClass(clazz); } @Override public MetaClass getClassNN(Class<?> clazz) { return getSession().getClassNN(clazz); } @Override public Collection<MetaClass> getClasses() { return getSession().getClasses(); } /** * Annotation value extractor. * <p/> Implementations are supposed to be passed to {@link #addMetaAnnotation(MetaClass, String, AnnotationValue)} * method. */ protected interface AnnotationValue { Object get(Class<?> javaClass); } }