| /** |
| * Copyright (c) 2011, 2015 - Lunifera GmbH (Gross Enzersdorf, Austria), Loetz GmbH&Co.KG (69115 Heidelberg, Germany) |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Florian Pirchner - Initial implementation |
| */ |
| package org.eclipse.osbp.dsl.dto.lib; |
| |
| import java.io.Serializable; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| import javax.persistence.EntityManager; |
| |
| import org.eclipse.osbp.dsl.common.datatypes.IDto; |
| import org.eclipse.osbp.runtime.common.annotations.DirtyStateAdapter; |
| import org.eclipse.osbp.runtime.common.annotations.DtoUtils; |
| import org.eclipse.osbp.runtime.common.session.IMappingContext; |
| import org.eclipse.osbp.runtime.common.state.ISharedStateContext; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * A common context for mappings. The registered entries will be reused. If an |
| * object for a given key is available, this instance will be returned. |
| * <p> |
| * At the end of the mapping process, the dirty adapter will be activated. The |
| * user needs to call {@link #flush()}. Calls to {@link #flush()} during the |
| * mapping process has no effect, if the current level is greater zero. |
| * <p> |
| * The option {@link #isRefresh} indicates, that existing objects should be |
| * remapped and their value beeing refreshed. |
| * <p> |
| * The option {@link #isUseDirtyAdapter()} indicates, that a |
| * {@link DirtyStateAdapter} is added to any registered dtos. |
| */ |
| public class MappingContext implements IMappingContext, Serializable { |
| |
| private static final long serialVersionUID = 8983308985143365450L; |
| @SuppressWarnings("unused") |
| private static final Logger LOGGER = LoggerFactory.getLogger(MappingContext.class); |
| |
| private static final ThreadLocal<MappingContext> current = new ThreadLocal<>(); |
| |
| /** |
| * Returns the current instance of the mapping context, if running in the |
| * proper thread. It is used to initialize several parts of the Entity -> |
| * Dto mapping process. |
| * |
| * @return the current |
| */ |
| public static MappingContext getCurrent() { |
| return current.get(); |
| } |
| |
| public static boolean isMappingMode() { |
| return getCurrent() != null; |
| } |
| |
| private int level = 0; |
| |
| private boolean isRefresh; |
| private Map<Object, Object> cache; |
| private Map<Object, Object> mappingRootCache; |
| private Map<Object, Object> refreshingCache; |
| |
| private Map<Object, Integer> typesByLevel = new HashMap<>(); |
| |
| private boolean useDirtyAdapter = true; |
| private DirtyStateAdapter dirtyAdapter; |
| |
| private boolean noCollectionResolving; |
| |
| private boolean noContainerMapping; |
| |
| /** |
| * Counts the calls to makeCurrent. And subs the calls to unmakeCurrent. |
| */ |
| private int currentCounter; |
| |
| // em is only available if mapping is done from DTO --> Entity |
| private transient EntityManager em; |
| |
| public MappingContext() { |
| cache = new HashMap<Object, Object>(); |
| // makeCurrent(); |
| } |
| |
| /** |
| * Associates this MappingContext with the current thread. You also need to |
| * call #unmakeCurrent() in a finally block; |
| */ |
| public void makeCurrent() { |
| currentCounter++; |
| MappingContext.current.set(this); |
| } |
| |
| /** |
| * @See {@link #makeCurrent()} |
| */ |
| public void unmakeCurrent() { |
| currentCounter--; |
| if (currentCounter == 0) { |
| MappingContext.current.set(null); |
| } else if (currentCounter < 0) { |
| throw new IllegalStateException("CurrentCounter must never be negative"); |
| } |
| } |
| |
| /** |
| * @return the isRefresh |
| */ |
| public boolean isRefresh() { |
| return isRefresh; |
| } |
| |
| /** |
| * @param isRefresh |
| * the isRefresh to set |
| */ |
| public void setRefresh(boolean isRefresh) { |
| this.isRefresh = isRefresh; |
| if (isRefresh) { |
| refreshingCache = new HashMap<Object, Object>(); |
| } |
| } |
| |
| /** |
| * True if the dirty adapter should be used. |
| * |
| * @return |
| */ |
| public boolean isUseDirtyAdapter() { |
| return useDirtyAdapter; |
| } |
| |
| /** |
| * True if the dirty adapter should be used. |
| * |
| * @param useDirtyAdapter |
| */ |
| public void setUseDirtyAdapter(boolean useDirtyAdapter) { |
| this.useDirtyAdapter = useDirtyAdapter; |
| } |
| |
| /** |
| * Increases the level of the mapping process. |
| */ |
| public void increaseLevel() { |
| level++; |
| } |
| |
| /** |
| * Decreases the level of the mapping process. |
| */ |
| public void decreaseLevel() { |
| level--; |
| } |
| |
| /** |
| * For later use. |
| * |
| * @return |
| */ |
| public boolean isMaxLevel() { |
| return false; |
| } |
| |
| /** |
| * Returns true, if a recursion was detected and mapping should be stopped |
| * now. |
| * |
| * @param in |
| * @return |
| */ |
| public boolean isDetectRecursion(Object in) { |
| if (level > 2) { |
| return true; |
| } |
| if (in instanceof Class) { |
| if (typesByLevel |
| .containsKey(in) /* |
| * && typesByLevel.get(in) < level |
| */) { |
| return true; |
| } |
| } else { |
| if (typesByLevel |
| .containsKey(in.getClass()) /* |
| * && typesByLevel.get(in |
| * .getClass()) < level |
| */) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Registers the object in the temporary internal cache. If {@link #flush()} |
| * is called, the internal cache will be cleared and objects are forwarded |
| * to the {@link ISharedStateContext}. |
| * |
| * @param key |
| * @param target |
| */ |
| public void register(Object key, Object target) { |
| if (cache.containsKey(key)) { |
| if (cache.get(key) != target) { |
| throw new IllegalStateException(); |
| } |
| } |
| cache.put(key, target); |
| |
| // register the type of the target with the highest level |
| typesByLevel.put(target.getClass(), level); |
| |
| if (isUseDirtyAdapter()) { |
| if (target instanceof IDto) { |
| if (DtoUtils.getAdapter(DirtyStateAdapter.class, target) == null) { |
| DtoUtils.registerAdapter(getDirtyStateAdapter(), target); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Creates or returns a new dirty state adapter. |
| * |
| * @return |
| */ |
| protected DirtyStateAdapter getDirtyStateAdapter() { |
| if (dirtyAdapter == null) { |
| dirtyAdapter = new DirtyStateAdapter(); |
| } |
| return dirtyAdapter; |
| } |
| |
| /** |
| * Registers the object in the a internal cache. If {@link #flush()} is |
| * called, the internal cache will be cleared. |
| * <p> |
| * Throws {@link IllegalArgumentException} if this method is called, but |
| * {@link #isRefresh() refresh mode} not active. |
| * |
| * @param key |
| * @param target |
| */ |
| public void registerRefreshed(Object key, Object target) throws IllegalArgumentException { |
| if (!isRefresh()) { |
| throw new IllegalArgumentException("Only allowed in refresh mode!"); |
| } |
| refreshingCache.put(key, target); |
| } |
| |
| /** |
| * Registers the object that was used as a template to map the cached |
| * object. |
| * |
| * @param key |
| * @param target |
| */ |
| public void registerMappingRoot(Object key, Object target) { |
| if (mappingRootCache == null) { |
| mappingRootCache = new HashMap<Object, Object>(); |
| } |
| if (mappingRootCache.containsKey(key)) { |
| if (mappingRootCache.get(key) != target) { |
| throw new IllegalStateException(); |
| } |
| } |
| mappingRootCache.put(key, target); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public <A> A get(Object key) { |
| A result = (A) cache.get(key); |
| |
| if (isRefresh() && result != null) { |
| refreshingCache.put(key, result); |
| } |
| |
| return result; |
| } |
| |
| @SuppressWarnings("unchecked") |
| public <A> A getMappingRoot(Object key) { |
| return (A) mappingRootCache.get(key); |
| } |
| |
| /** |
| * Tries to find the entity by the entity manager. |
| * |
| * @param clazz |
| * @param id |
| */ |
| public Object findEntityByEntityManager(Class<?> clazz, Object id) { |
| return em.find(clazz, id); |
| } |
| |
| public void setEntityManager(EntityManager em) { |
| this.em = em; |
| } |
| |
| /** |
| * The cache will only become flushed, if the level is zero. The |
| * dirtyAdapter will become activated. |
| */ |
| public void flush() { |
| if (level > 0) { |
| return; |
| } |
| |
| if (dirtyAdapter != null) { |
| dirtyAdapter.setActive(true); |
| } |
| } |
| |
| public void setDirtyAdapterActive(boolean value) { |
| getDirtyStateAdapter().setActive(value); |
| } |
| |
| public boolean isDirtyAdapterActive() { |
| return getDirtyStateAdapter().isActive(); |
| } |
| |
| /** |
| * This flag is used by LazyResolvingLists to ensure, that no resolving is |
| * done right now. For instance if DTOs are mapped back to Entities, a |
| * LazyLoadingDtoList does not need to become resolved. |
| * |
| * @return |
| */ |
| public boolean isNoCollectionResolving() { |
| return noCollectionResolving; |
| } |
| |
| /** |
| * @see MappingContext#isNoCollectionResolving() |
| * @param value |
| */ |
| public void setNoCollectionResolving(boolean value) { |
| noCollectionResolving = value; |
| } |
| |
| /** |
| * If true, then the opposite reference (parent or container) will not be |
| * applied during the mapping process. Otherwise, list entries are being |
| * added twice. |
| * |
| * @return |
| */ |
| public boolean isNoContainerMapping() { |
| return noContainerMapping; |
| } |
| |
| /** |
| * If true, then the opposite reference (parent or container) will not be |
| * applied during the mapping process. Otherwise, list entries are being |
| * added twice. |
| * |
| * @param noContainerMapping |
| */ |
| public void setNoContainerMapping(boolean noContainerMapping) { |
| this.noContainerMapping = noContainerMapping; |
| } |
| |
| public void printCache() { |
| for (Entry<Object, Object> entry : cache.entrySet()) { |
| System.out.println(entry.getKey() + " : " + entry.getValue() + " # " + entry.getValue().hashCode()); |
| } |
| } |
| |
| public void printCacheCsv() { |
| for (Entry<Object, Object> entry : cache.entrySet()) { |
| System.out.println(entry.getKey() + "; " + entry.getValue() + "; " + entry.getValue().hashCode()); |
| } |
| } |
| |
| } |