/* * Copyright 2011-2013 the original author or authors. * * 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 kr.debop4j.data.hibernate.tools; import com.google.common.collect.Sets; import kr.debop4j.core.Function1; import kr.debop4j.core.IDataObject; import kr.debop4j.core.json.GsonSerializer; import kr.debop4j.core.json.IJsonSerializer; import kr.debop4j.core.parallelism.Parallels; import kr.debop4j.core.tools.MapperTool; import kr.debop4j.core.tools.StringTool; import kr.debop4j.data.hibernate.HibernateParameter; import kr.debop4j.data.hibernate.repository.IHibernateDao; import kr.debop4j.data.hibernate.repository.impl.HibernateDao; import kr.debop4j.data.model.*; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.hibernate.Session; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; import org.hibernate.type.LocaleType; import org.hibernate.type.ObjectType; import org.hibernate.type.StringType; import javax.annotation.Nullable; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Locale; import static kr.debop4j.core.Guard.shouldNotBeNull; import static kr.debop4j.core.Guard.shouldNotBeWhiteSpace; /** * Hibernate 엔티티 정보를 관리하기 위한 Utility Class 입니다. * * @author 배성혁 ( sunghyouk.bae@gmail.com ) * @since 12. 9. 24 */ @Slf4j @SuppressWarnings("unchecked") public class EntityTool { private EntityTool() { } /** The constant PROPERTY_ANCESTORS. */ public static final String PROPERTY_ANCESTORS = "ancestors"; /** The constant PROPERTY_DESCENDENTS. */ public static final String PROPERTY_DESCENDENTS = "descendents"; @Getter(lazy = true) private static final IJsonSerializer gsonSerializer = new GsonSerializer(); /** * Entity to string. * * @param entity the entity * @return the string */ public static String entityToString(IDataObject entity) { return (entity != null) ? StringTool.objectToString(entity) : StringTool.NULL_STR; } /** * As gson text. * * @param entity the entity * @return the string * @throws Exception the exception */ public static String asGsonText(IDataObject entity) throws Exception { return getGsonSerializer().serializeToText(entity); } // region << Hierarchy >> /** * Assert not circular hierarchy. * * @param child the child * @param parent the parent */ public static <T extends IHierarchyEntity<T>> void assertNotCircularHierarchy(T child, T parent) { if (child == parent) throw new IllegalArgumentException("Child and Parent are same."); if (child.getDescendents().contains(parent)) throw new IllegalArgumentException("child 가 parent를 이미 자손으로 가지고 있습니다."); if (Sets.intersection(parent.getAncestors(), child.getDescendents()).size() > 0) throw new IllegalArgumentException("parent의 조상과 child의 조상이 같은 넘이 있으면 안됩니다."); } /** * Sets hierarchy. * * @param child the child * @param oldParent the old parent * @param newParent the new parent */ public static <T extends IHierarchyEntity<T>> void setHierarchy(T child, T oldParent, T newParent) { shouldNotBeNull(child, "child"); log.trace("현재 노드의 부모를 변경하고, 계층구조 정보를 변경합니다... child=[{}], oldParent=[{}], newParent=[{}]", child, oldParent, newParent); if (oldParent != null) removeHierarchy(child, oldParent); if (newParent != null) setHierarchy(child, newParent); } /** * Sets hierarchy. * * @param child the child * @param parent the parent */ public static <T extends IHierarchyEntity<T>> void setHierarchy(T child, T parent) { if (parent == null || child == null) return; log.trace("노드의 부모 및 조상을 설정합니다. child=[{}], parent=[{}]", child, parent); parent.getDescendents().add(child); parent.getDescendents().addAll(child.getDescendents()); for (T ancestor : parent.getAncestors()) { ancestor.getDescendents().add(child); ancestor.getDescendents().addAll(child.getDescendents()); } child.getAncestors().add(parent); child.getAncestors().addAll(parent.getAncestors()); } /** * Remove hierarchy. * * @param child the child * @param parent the parent */ public static <T extends IHierarchyEntity<T>> void removeHierarchy(T child, T parent) { if (parent == null) return; shouldNotBeNull(child, "child"); log.trace("노드의 부모 및 조상을 제거합니다. child=[{}], parent=[{}]", child, parent); child.getAncestors().remove(parent); child.getAncestors().removeAll(parent.getAncestors()); for (T ancestor : parent.getAncestors()) { ancestor.getDescendents().remove(child); ancestor.getDescendents().removeAll(child.getDescendents()); } for (T descendent : child.getDescendents()) { descendent.getAncestors().remove(parent); descendent.getAncestors().removeAll(parent.getAncestors()); } } /** * Get ancestors criteria. * * @param entity the entity * @param session the session * @param entityClass the entity class * @return the detached criteria */ public static <T extends IHierarchyEntity<T> & IEntity<TId>, TId extends Serializable> DetachedCriteria getAncestorsCriteria(T entity, Session session, Class<T> entityClass) { return DetachedCriteria .forClass(entityClass) .createAlias(PROPERTY_DESCENDENTS, "des") .add(Restrictions.eq("des.id", entity.getId())); } /** * Get descendents criteria. * * @param entity the entity * @param session the session * @param entityClass the entity class * @return the detached criteria */ public static <T extends IHierarchyEntity<T> & IEntity<TId>, TId extends Serializable> DetachedCriteria getDescendentsCriteria(T entity, Session session, Class<T> entityClass) { return DetachedCriteria.forClass(entityClass) .createAlias(PROPERTY_ANCESTORS, "ans") .add(Restrictions.eq("ans.id", entity.getId())); } /** * Get ancestors id criteria. * * @param entity the entity * @param session the session * @param entityClass the entity class * @return the detached criteria */ public static <T extends IHierarchyEntity<T> & IEntity<TId>, TId extends Serializable> DetachedCriteria getAncestorsIdCriteria(T entity, Session session, Class<T> entityClass) { return getAncestorsCriteria(entity, session, entityClass) .setProjection(Projections.distinct(Projections.id())); } /** * Get descendents id criteria. * * @param entity the entity * @param session the session * @param entityClass the entity class * @return the detached criteria */ public static <T extends IHierarchyEntity<T> & IEntity<TId>, TId extends Serializable> DetachedCriteria getDescendentsIdCriteria(T entity, Session session, Class<T> entityClass) { return getDescendentsCriteria(entity, session, entityClass) .setProjection(Projections.distinct(Projections.id())); } // endregion // region << ILocaleEntity >> /** 특정 로케일 키를 가지는 엔티티를 조회하는 HQL 문. */ public static final String GET_LIST_BY_LOCALE_KEY = "select distinct loen from %s loen where :key in indices (loen.localeMap)"; /** 특정 로케일 속성값에 따른 엔티티를 조회하는 HQL 문. */ public static final String GET_LIST_BY_LOCALE_PROPERTY = "select distinct loen from %s loen join loen.localeMap locale where locale.%s = :%s"; /** * Copy locale. * * @param source the source * @param destination the destination */ public static <T extends ILocaleEntity<TLocaleValue>, TLocaleValue extends ILocaleValue> void CopyLocale(T source, T destination) { for (Locale locale : source.getLocales()) destination.addLocaleValue(locale, source.getLocaleValue(locale)); } /** * Contains locale. * * @param entityClass the entity class * @param locale the locale * @return the list */ public static <T extends ILocaleEntity<TLocaleValue>, TLocaleValue extends ILocaleValue> List<T> containsLocale(Class<T> entityClass, Locale locale) { String hql = String.format(GET_LIST_BY_LOCALE_KEY, entityClass.getName()); if (log.isDebugEnabled()) log.debug("IEntity [{}] 의 Locale[{}]를 가지는 엔티티 조회 hql=[{}]", entityClass.getName(), locale, hql); IHibernateDao dao = new HibernateDao(); return dao.find(entityClass, hql, new HibernateParameter("key", locale, LocaleType.INSTANCE)); } /** * Contains locale. * * @param entityClass the entity class * @param propertyName the property name * @param value the value * @param type the type * @return the list */ public static <T extends ILocaleEntity<TLocaleValue>, TLocaleValue extends ILocaleValue> List<T> containsLocale(final Class<T> entityClass, final String propertyName, final Object value, final org.hibernate.type.Type type) { final String hql = String.format(GET_LIST_BY_LOCALE_PROPERTY, entityClass.getName(), propertyName, propertyName); if (log.isDebugEnabled()) log.debug("IEntity [{}] 에 Locale 속성[{}]의 값이 [{}] 인 엔티티를 조회합니다. hql=[{}]", entityClass.getName(), propertyName, value, hql); IHibernateDao dao = new HibernateDao(); return dao.find(entityClass, hql, new HibernateParameter(propertyName, value, ObjectType.INSTANCE)); } // endregion // region << IMetaEntity >> private static final String GET_LIST_BY_META_KEY = "select distinct me from %s me where :key in indices(me.metaMap)"; public static final String GET_LIST_BY_META_VALUE = "select distinct me from %s me join me.metaMap meta where meta.value = :value"; /** * Contains meta key. * * @param entityClass the entity class * @param key the key * @return the list */ public static <T extends IMetaEntity> List<T> containsMetaKey(Class<T> entityClass, String key) { shouldNotBeWhiteSpace(key, "key"); String hql = String.format(GET_LIST_BY_META_KEY, entityClass.getName()); if (log.isDebugEnabled()) log.debug("엔티티 [{}]의 메타데이타 키 [{}] 를 가지는 엔티티 조회 hql=[{}]", entityClass.getName(), key, hql); IHibernateDao dao = new HibernateDao(); return dao.find(entityClass, hql, new HibernateParameter("key", key, StringType.INSTANCE)); } /** * Contains meta value. * * @param entityClass the entity class * @param value the value * @return the list */ public static <T extends IMetaEntity> List<T> containsMetaValue(final Class<T> entityClass, final String value) { shouldNotBeWhiteSpace(value, "value"); String hql = String.format(GET_LIST_BY_META_VALUE, entityClass.getName()); if (log.isDebugEnabled()) log.debug("메타데이타 value[{}]를 가지는 엔티티 조회 hql=[{}]", value, hql); IHibernateDao dao = new HibernateDao(); return dao.find(entityClass, hql, new HibernateParameter("value", value, StringType.INSTANCE)); } // endregion // region << IEntity Mapper >> /** * 원본 엔티티의 속성정보를 대상 엔티티의 속성정보로 매핑시킵니다. * * @param source the source * @param target the target * @return target entity */ public static <S, T> T mapEntity(S source, T target) { MapperTool.map(source, target); return target; } /** * Map entity. * * @param source the source * @param targetClass the target class * @return target entity */ public static <S, T> T mapEntity(S source, Class<T> targetClass) { shouldNotBeNull(source, "source"); return MapperTool.map(source, targetClass); } /** * 원본 엔티티를 대상 엔티티로 매핑을 수행합니다. {@link kr.debop4j.core.tools.MapperTool} 을 사용합니다. * * @param sources the sources * @param targets the targets * @return the list */ public static <S, T> List<T> mapEntities(List<S> sources, List<T> targets) { shouldNotBeNull(sources, "sources"); shouldNotBeNull(targets, "targets"); int size = Math.min(sources.size(), targets.size()); for (int i = 0; i < size; i++) { MapperTool.map(sources.get(i), targets.get(i)); } return targets; } /** * 병렬 방식으로 원본으로부터 대상엔티티로 매핑합니다. 대용량 정보의 DTO 생성 시 유리합니다. * * @param sources the sources * @param targetClass the target class * @return the list */ public static <S, T> List<T> mapEntitiesAsParallel(final List<S> sources, final Class<T> targetClass) { if (sources == null || sources.size() == 0) return new ArrayList<>(); return Parallels.runEach(sources, new Function1<S, T>() { @Override public T execute(@Nullable S input) { return MapperTool.map(input, targetClass); } }); } // endregion // region << TreeNode >> /** * Update tree node position. * * @param entity the entity */ public static <T extends ITreeEntity<T>> void updateTreeNodePosition(T entity) { shouldNotBeNull(entity, "entity"); log.trace("update tree node position... entity=[{}]", entity); TreeNodePosition nodePosition = entity.getNodePosition(); if (entity.getParent() != null) { nodePosition.setLevel(entity.getParent().getNodePosition().getLevel() + 1); if (!entity.getParent().getChildren().contains(entity)) nodePosition.setOrder(entity.getParent().getChildren().size()); } else { nodePosition.setLevel(0); nodePosition.setOrder(0); } } /** * 트리구조를 가지는 엔티티의 자식 수를 계산합니다. * * @param entity the entity * @return the child count */ public static <T extends ITreeEntity<T>> Long getChildCount(T entity) { log.trace("tree entity의 자식 엔티티의 수를 구합니다. entity=[{}]", entity); DetachedCriteria dc = HibernateTool.createDetachedCriteria(entity.getClass()); dc.add(Restrictions.eq("parent", entity)); IHibernateDao dao = new HibernateDao(); return dao.count(entity.getClass(), dc); } /** * Has children. * * @param entity the entity * @return the boolean */ public static <T extends ITreeEntity<T>> Boolean hasChildren(T entity) { log.trace("tree entity 가 자식을 가지는지 확안합니다. entity=[{}]", entity); DetachedCriteria dc = HibernateTool.createDetachedCriteria(entity.getClass()); dc.add(Restrictions.eq("parent", entity)); IHibernateDao dao = new HibernateDao(); return dao.exists(entity.getClass(), dc); } // endregion }