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.google.common.base.Splitter; import com.haulmont.bali.util.Dom4j; import com.haulmont.bali.util.Preconditions; import com.haulmont.bali.util.ReflectionHelper; import com.haulmont.chile.core.model.MetaClass; import com.haulmont.chile.core.model.MetaProperty; import com.haulmont.chile.core.model.Range; import com.haulmont.cuba.core.entity.Entity; import com.haulmont.cuba.core.global.*; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.text.StrTokenizer; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.perf4j.StopWatch; import org.perf4j.slf4j.Slf4JStopWatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import javax.inject.Inject; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.apache.commons.lang.StringUtils.isNotBlank; /** * Base implementation of the {@link ViewRepository}. Contains methods to store {@link View} objects and deploy * them from XML. <br> * <br> Don't replace this class completely, because the framework uses it directly. */ public class AbstractViewRepository implements ViewRepository { private final Logger log = LoggerFactory.getLogger(AbstractViewRepository.class); protected List<String> readFileNames = new LinkedList<>(); protected Map<MetaClass, Map<String, View>> storage = new ConcurrentHashMap<>(); @Inject protected Metadata metadata; @Inject protected Resources resources; protected volatile boolean initialized; protected ReadWriteLock lock = new ReentrantReadWriteLock(); protected void checkInitialized() { if (!initialized) { lock.readLock().unlock(); lock.writeLock().lock(); try { if (!initialized) { log.info("Initializing views"); init(); initialized = true; } } finally { lock.readLock().lock(); lock.writeLock().unlock(); } } } protected void init() { StopWatch initTiming = new Slf4JStopWatch("ViewRepository.init." + getClass().getSimpleName()); storage.clear(); readFileNames.clear(); String configName = AppContext.getProperty("cuba.viewsConfig"); if (!StringUtils.isBlank(configName)) { Element rootElem = DocumentHelper.createDocument().addElement("views"); StrTokenizer tokenizer = new StrTokenizer(configName); for (String fileName : tokenizer.getTokenArray()) { addFile(rootElem, fileName); } checkDuplicates(rootElem); for (Element viewElem : Dom4j.elements(rootElem, "view")) { deployView(rootElem, viewElem, new HashSet<>()); } } initTiming.stop(); } protected void checkDuplicates(Element rootElem) { Set<String> checked = new HashSet<>(); for (Element viewElem : Dom4j.elements(rootElem, "view")) { String viewName = getViewName(viewElem); String key = getMetaClass(viewElem) + "/" + viewName; if (!Boolean.parseBoolean(viewElem.attributeValue("overwrite"))) { String extend = viewElem.attributeValue("extends"); if (extend != null) { List<String> ancestors = splitExtends(extend); if (!ancestors.contains(viewName) && checked.contains(key)) { log.warn( "Duplicate view definition without 'overwrite' attribute and not extending parent view: " + key); } } } checked.add(key); } } protected List<String> splitExtends(String extend) { return Splitter.on(',').omitEmptyStrings().trimResults().splitToList(extend); } protected void addFile(Element commonRootElem, String fileName) { if (readFileNames.contains(fileName)) return; log.debug("Deploying views config: " + fileName); readFileNames.add(fileName); InputStream stream = null; try { stream = resources.getResourceAsStream(fileName); if (stream == null) { throw new IllegalStateException("Resource is not found: " + fileName); } SAXReader reader = new SAXReader(); Document doc; try { doc = reader.read(new InputStreamReader(stream, StandardCharsets.UTF_8)); } catch (DocumentException e) { throw new RuntimeException("Unable to parse view file " + fileName, e); } Element rootElem = doc.getRootElement(); for (Element includeElem : Dom4j.elements(rootElem, "include")) { String incFile = includeElem.attributeValue("file"); if (!StringUtils.isBlank(incFile)) addFile(commonRootElem, incFile); } for (Element viewElem : Dom4j.elements(rootElem, "view")) { commonRootElem.add(viewElem.createCopy()); } } finally { IOUtils.closeQuietly(stream); } } public void reset() { initialized = false; } /** * Get View for an entity. * * @param entityClass entity class * @param name view name * @return view instance. Throws {@link com.haulmont.cuba.core.global.ViewNotFoundException} if not found. */ @Override public View getView(Class<? extends Entity> entityClass, String name) { return getView(metadata.getClassNN(entityClass), name); } /** * Get View for an entity. * * @param metaClass entity class * @param name view name * @return view instance. Throws {@link com.haulmont.cuba.core.global.ViewNotFoundException} if not found. */ @Override public View getView(MetaClass metaClass, String name) { Preconditions.checkNotNullArgument(metaClass, "MetaClass is null"); View view = findView(metaClass, name); if (view == null) { throw new ViewNotFoundException(String.format("View %s/%s not found", metaClass.getName(), name)); } return view; } /** * Searches for a View for an entity * * @param metaClass entity class * @param name view name * @return view instance or null if no view found */ @Override @Nullable public View findView(MetaClass metaClass, @Nullable String name) { if (metaClass == null) { throw new IllegalArgumentException("Passed metaClass should not be null"); } if (name == null) { return null; } lock.readLock().lock(); try { checkInitialized(); View view = retrieveView(metaClass, name, new HashSet<>()); return copyView(view); } finally { lock.readLock().unlock(); } } protected View copyView(@Nullable View view) { if (view == null) { return null; } View.ViewParams viewParams = new View.ViewParams().entityClass(view.getEntityClass()).name(view.getName()); View copy = new View(viewParams); for (ViewProperty property : view.getProperties()) { copy.addProperty(property.getName(), copyView(property.getView()), property.getFetchMode()); } return copy; } @Override public Collection<String> getViewNames(MetaClass metaClass) { Preconditions.checkNotNullArgument(metaClass, "MetaClass is null"); lock.readLock().lock(); try { checkInitialized(); Map<String, View> viewMap = storage.get(metaClass); if (viewMap != null && !viewMap.isEmpty()) { Set<String> keySet = new HashSet<>(viewMap.keySet()); keySet.remove(View.LOCAL); keySet.remove(View.MINIMAL); return keySet; } else { return Collections.emptyList(); } } finally { lock.readLock().unlock(); } } @Override public Collection<String> getViewNames(Class<? extends Entity> entityClass) { Preconditions.checkNotNullArgument(entityClass, "entityClass is null"); MetaClass metaClass = metadata.getClassNN(entityClass); return getViewNames(metaClass); } @SuppressWarnings("unchecked") protected View deployDefaultView(MetaClass metaClass, String name, Set<ViewInfo> visited) { Class<? extends Entity> javaClass = metaClass.getJavaClass(); ViewInfo info = new ViewInfo(javaClass, name); if (visited.contains(info)) { throw new DevelopmentException(String.format( "Views cannot have cyclic references. View %s for class %s", name, metaClass.getName())); } View view; if (View.LOCAL.equals(name)) { view = new View(javaClass, name, false); addAttributesToLocalView(metaClass, view); } else if (View.MINIMAL.equals(name)) { view = new View(javaClass, name, false); addAttributesToMinimalView(metaClass, view, info, visited); } else if (View.BASE.equals(name)) { view = new View(javaClass, name, false); addAttributesToMinimalView(metaClass, view, info, visited); addAttributesToLocalView(metaClass, view); } else { throw new UnsupportedOperationException("Unsupported default view: " + name); } storeView(metaClass, view); return view; } protected void addAttributesToLocalView(MetaClass metaClass, View view) { for (MetaProperty property : metaClass.getProperties()) { if (!property.getRange().isClass() && !metadata.getTools().isSystem(property) && metadata.getTools().isPersistent(property)) { view.addProperty(property.getName()); } } } protected void addAttributesToMinimalView(MetaClass metaClass, View view, ViewInfo info, Set<ViewInfo> visited) { Collection<MetaProperty> metaProperties = metadata.getTools().getNamePatternProperties(metaClass, true); for (MetaProperty metaProperty : metaProperties) { if (metadata.getTools().isPersistent(metaProperty)) { addPersistentAttributeToMinimalView(metaClass, visited, info, view, metaProperty); } else { List<String> relatedProperties = metadata.getTools().getRelatedProperties(metaProperty); for (String relatedPropertyName : relatedProperties) { MetaProperty relatedProperty = metaClass.getPropertyNN(relatedPropertyName); if (metadata.getTools().isPersistent(relatedProperty)) { addPersistentAttributeToMinimalView(metaClass, visited, info, view, relatedProperty); } else { log.warn("Transient attribute '" + relatedPropertyName + "' is listed in 'related' properties of another transient attribute '" + metaProperty.getName() + "'"); } } } } } protected void addPersistentAttributeToMinimalView(MetaClass metaClass, Set<ViewInfo> visited, ViewInfo info, View view, MetaProperty metaProperty) { if (metaProperty.getRange().isClass() && !metaProperty.getRange().getCardinality().isMany()) { Map<String, View> views = storage.get(metaProperty.getRange().asClass()); View refMinimalView = (views == null ? null : views.get(View.MINIMAL)); if (refMinimalView != null) { view.addProperty(metaProperty.getName(), refMinimalView); } else { visited.add(info); View referenceMinimalView = deployDefaultView(metaProperty.getRange().asClass(), View.MINIMAL, visited); visited.remove(info); view.addProperty(metaProperty.getName(), referenceMinimalView); } } else { view.addProperty(metaProperty.getName()); } } public void deployViews(String resourceUrl) { lock.readLock().lock(); try { checkInitialized(); } finally { lock.readLock().unlock(); } Element rootElem = DocumentHelper.createDocument().addElement("views"); lock.writeLock().lock(); try { addFile(rootElem, resourceUrl); for (Element viewElem : Dom4j.elements(rootElem, "view")) { deployView(rootElem, viewElem, new HashSet<>()); } } finally { lock.writeLock().unlock(); } } public void deployViews(InputStream xml) { deployViews(new InputStreamReader(xml, StandardCharsets.UTF_8)); } public void deployViews(Reader xml) { lock.readLock().lock(); try { checkInitialized(); } finally { lock.readLock().unlock(); } SAXReader reader = new SAXReader(); Document doc; try { doc = reader.read(xml); } catch (DocumentException e) { throw new RuntimeException("Unable to read views xml", e); } Element rootElem = doc.getRootElement(); for (Element includeElem : Dom4j.elements(rootElem, "include")) { String file = includeElem.attributeValue("file"); if (!StringUtils.isBlank(file)) deployViews(file); } for (Element viewElem : Dom4j.elements(rootElem, "view")) { deployView(rootElem, viewElem); } } protected View retrieveView(MetaClass metaClass, String name, Set<ViewInfo> visited) { Map<String, View> views = storage.get(metaClass); View view = (views == null ? null : views.get(name)); if (view == null && (name.equals(View.LOCAL) || name.equals(View.MINIMAL) || name.equals(View.BASE))) { view = deployDefaultView(metaClass, name, visited); } return view; } public View deployView(Element rootElem, Element viewElem) { lock.writeLock().lock(); try { return deployView(rootElem, viewElem, new HashSet<>()); } finally { lock.writeLock().unlock(); } } protected View deployView(Element rootElem, Element viewElem, Set<ViewInfo> visited) { String viewName = getViewName(viewElem); MetaClass metaClass = getMetaClass(viewElem); ViewInfo info = new ViewInfo(metaClass.getJavaClass(), viewName); if (visited.contains(info)) { throw new DevelopmentException(String.format( "Views cannot have cyclic references. View %s for class %s", viewName, metaClass.getName())); } View v = retrieveView(metaClass, viewName, visited); boolean overwrite = Boolean.parseBoolean(viewElem.attributeValue("overwrite")); String extended = viewElem.attributeValue("extends"); List<String> ancestors = null; if (isNotBlank(extended)) { ancestors = splitExtends(extended); } if (!overwrite && ancestors != null) { overwrite = ancestors.contains(viewName); } if (v != null && !overwrite) { return v; } boolean systemProperties = Boolean.valueOf(viewElem.attributeValue("systemProperties")); View.ViewParams viewParam = new View.ViewParams().entityClass(metaClass.getJavaClass()).name(viewName); if (isNotEmpty(ancestors)) { List<View> ancestorsViews = ancestors.stream().map(a -> getAncestorView(metaClass, a, visited)) .collect(Collectors.toList()); viewParam.src(ancestorsViews); } viewParam.includeSystemProperties(systemProperties); View view = new View(viewParam); visited.add(info); loadView(rootElem, viewElem, view, systemProperties, visited); visited.remove(info); storeView(metaClass, view); if (overwrite) { replaceOverridden(view); } return view; } protected void replaceOverridden(View replacementView) { StopWatch replaceTiming = new Slf4JStopWatch("ViewRepository.replaceOverridden"); HashSet<View> checked = new HashSet<>(); for (View view : getAllInitialized()) { if (!checked.contains(view)) { replaceOverridden(view, replacementView, checked); } } replaceTiming.stop(); } protected void replaceOverridden(View root, View replacementView, HashSet<View> checked) { checked.add(root); List<ViewProperty> replacements = null; for (ViewProperty property : root.getProperties()) { View propertyView = property.getView(); if (propertyView != null) { if (Objects.equals(propertyView.getName(), replacementView.getName()) && replacementView.getEntityClass() == propertyView.getEntityClass()) { if (replacements == null) { replacements = new LinkedList<>(); } replacements .add(new ViewProperty(property.getName(), replacementView, property.getFetchMode())); } else if (propertyView.getEntityClass() != null && !checked.contains(propertyView)) { replaceOverridden(propertyView, replacementView, checked); } } } if (replacements != null) { for (ViewProperty replacement : replacements) { root.addProperty(replacement.getName(), replacement.getView(), replacement.getFetchMode()); } } } protected View getAncestorView(MetaClass metaClass, String ancestor, Set<ViewInfo> visited) { View ancestorView = retrieveView(metaClass, ancestor, visited); if (ancestorView == null) { ExtendedEntities extendedEntities = metadata.getExtendedEntities(); MetaClass originalMetaClass = extendedEntities.getOriginalMetaClass(metaClass); if (originalMetaClass != null) { ancestorView = retrieveView(originalMetaClass, ancestor, visited); } if (ancestorView == null) { // Last resort - search for all ancestors for (MetaClass ancestorMetaClass : metaClass.getAncestors()) { if (ancestorMetaClass.equals(metaClass)) { ancestorView = retrieveView(ancestorMetaClass, ancestor, visited); if (ancestorView != null) break; } } } if (ancestorView == null) { throw new DevelopmentException( "No ancestor view found: " + ancestor + " for " + metaClass.getName()); } } return ancestorView; } protected void loadView(Element rootElem, Element viewElem, View view, boolean systemProperties, Set<ViewInfo> visited) { final MetaClass metaClass = metadata.getClassNN(view.getEntityClass()); final String viewName = view.getName(); Set<String> propertyNames = new HashSet<>(); for (Element propElem : (List<Element>) viewElem.elements("property")) { String propertyName = propElem.attributeValue("name"); if (propertyNames.contains(propertyName)) { throw new DevelopmentException( String.format("View %s/%s definition error: view declared property %s twice", metaClass.getName(), viewName, propertyName)); } propertyNames.add(propertyName); MetaProperty metaProperty = metaClass.getProperty(propertyName); if (metaProperty == null) { throw new DevelopmentException( String.format("View %s/%s definition error: property %s doesn't exists", metaClass.getName(), viewName, propertyName)); } View refView = null; String refViewName = propElem.attributeValue("view"); MetaClass refMetaClass; Range range = metaProperty.getRange(); if (range == null) { throw new RuntimeException("cannot find range for meta property: " + metaProperty); } final List<Element> propertyElements = Dom4j.elements(propElem, "property"); boolean inlineView = !propertyElements.isEmpty(); if (!range.isClass() && (refViewName != null || inlineView)) { throw new DevelopmentException( String.format("View %s/%s definition error: property %s is not an entity", metaClass.getName(), viewName, propertyName)); } if (refViewName != null) { refMetaClass = getMetaClass(propElem, range); refView = retrieveView(refMetaClass, refViewName, visited); if (refView == null) { for (Element e : Dom4j.elements(rootElem, "view")) { if (refMetaClass.equals(getMetaClass(e.attributeValue("entity"), e.attributeValue("class"))) && refViewName.equals(e.attributeValue("name"))) { refView = deployView(rootElem, e, visited); break; } } if (refView == null) { MetaClass originalMetaClass = metadata.getExtendedEntities() .getOriginalMetaClass(refMetaClass); if (originalMetaClass != null) { refView = retrieveView(originalMetaClass, refViewName, visited); } } if (refView == null) { throw new DevelopmentException(String.format( "View %s/%s definition error: unable to find/deploy referenced view %s/%s", metaClass.getName(), viewName, range.asClass().getName(), refViewName)); } } } if (inlineView) { // try to import anonymous views Class rangeClass = range.asClass().getJavaClass(); if (refView != null) { refView = new View(refView, rangeClass, "", false); // system properties are already in the source view } else { ViewProperty existingProperty = view.getProperty(propertyName); if (existingProperty != null && existingProperty.getView() != null) { refView = new View(existingProperty.getView(), rangeClass, "", systemProperties); } else { refView = new View(rangeClass, systemProperties); } } loadView(rootElem, propElem, refView, systemProperties, visited); } FetchMode fetchMode = FetchMode.AUTO; String fetch = propElem.attributeValue("fetch"); if (fetch != null) fetchMode = FetchMode.valueOf(fetch); view.addProperty(propertyName, refView, fetchMode); } } protected String getViewName(Element viewElem) { String viewName = viewElem.attributeValue("name"); if (StringUtils.isBlank(viewName)) throw new DevelopmentException("Invalid view definition: no 'name' attribute present"); return viewName; } protected MetaClass getMetaClass(Element viewElem) { MetaClass metaClass; String entity = viewElem.attributeValue("entity"); if (StringUtils.isBlank(entity)) { String className = viewElem.attributeValue("class"); if (StringUtils.isBlank(className)) throw new DevelopmentException("Invalid view definition: no 'entity' or 'class' attribute present"); Class entityClass = ReflectionHelper.getClass(className); metaClass = metadata.getClassNN(entityClass); } else { metaClass = metadata.getClassNN(entity); } return metaClass; } protected MetaClass getMetaClass(String entityName, String entityClass) { if (entityName != null) { return metadata.getClassNN(entityName); } else { return metadata.getClassNN(ReflectionHelper.getClass(entityClass)); } } protected MetaClass getMetaClass(Element propElem, Range range) { MetaClass refMetaClass; String refEntityName = propElem.attributeValue("entity"); // this attribute is deprecated if (refEntityName == null) { refMetaClass = range.asClass(); } else { refMetaClass = metadata.getClassNN(refEntityName); } return refMetaClass; } protected void storeView(MetaClass metaClass, View view) { Map<String, View> views = storage.get(metaClass); if (views == null) { views = new ConcurrentHashMap<>(); } views.put(view.getName(), view); storage.put(metaClass, views); } protected List<View> getAllInitialized() { List<View> list = new ArrayList<>(); for (Map<String, View> viewMap : storage.values()) { list.addAll(viewMap.values()); } return list; } public List<View> getAll() { lock.readLock().lock(); try { checkInitialized(); List<View> list = new ArrayList<>(); for (Map<String, View> viewMap : storage.values()) { list.addAll(viewMap.values()); } return list; } finally { lock.readLock().unlock(); } } protected static class ViewInfo { protected Class javaClass; protected String name; public ViewInfo(Class javaClass, String name) { this.javaClass = javaClass; this.name = name; } public Class getJavaClass() { return javaClass; } public void setJavaClass(Class javaClass) { this.javaClass = javaClass; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public boolean equals(Object obj) { if (!(obj instanceof ViewInfo)) { return false; } ViewInfo that = (ViewInfo) obj; return this.javaClass == that.javaClass && Objects.equals(this.name, that.name); } @Override public int hashCode() { int result = javaClass.hashCode(); result = 31 * result + name.hashCode(); return result; } } }