blob: 75031b24306153395018b4e914d23fea448e3e3a [file] [log] [blame]
/**
* 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 -&gt;
* 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());
}
}
}