/* * #%L * BroadleafCommerce Common Libraries * %% * Copyright (C) 2009 - 2014 Broadleaf Commerce * %% * 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. * #L% */ package org.broadleafcommerce.common.copy; import org.apache.commons.lang.ArrayUtils; import org.broadleafcommerce.common.exception.ExceptionHelper; import org.broadleafcommerce.common.persistence.Status; import org.broadleafcommerce.common.service.GenericEntityService; import org.broadleafcommerce.common.site.domain.Catalog; import org.broadleafcommerce.common.site.domain.Site; import org.broadleafcommerce.common.util.tenant.IdentityExecutionUtils; import org.broadleafcommerce.common.util.tenant.IdentityOperation; import org.broadleafcommerce.common.web.BroadleafRequestContext; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import javax.persistence.Embeddable; public class MultiTenantCopyContext { public static final String[] BROADLEAF_PACKAGE_PREFIXES = {"org.broadleafcommerce","com.broadleafcommerce"}; protected Catalog fromCatalog; protected Catalog toCatalog; protected Site fromSite; protected Site toSite; protected MultiTenantCopierExtensionManager extensionManager; protected BiMap<Integer, String> currentEquivalentMap = HashBiMap.create(); protected Map<Integer, Object> currentCloneMap = new HashMap<Integer, Object>(); protected Map<String, Map<Object, Object>> equivalentsMap; protected GenericEntityService genericEntityService; public MultiTenantCopyContext(Catalog fromCatalog, Catalog toCatalog, Site fromSite, Site toSite, GenericEntityService genericEntityService, MultiTenantCopierExtensionManager extensionManager) { equivalentsMap = new HashMap<String, Map<Object, Object>>(); this.fromCatalog = fromCatalog; this.toCatalog = toCatalog; this.fromSite = fromSite; this.toSite = toSite; this.genericEntityService = genericEntityService; this.extensionManager = extensionManager; } public <T> T getClonedVersion(final Class<T> clazz, final Object originalId) { return IdentityExecutionUtils.runOperationByIdentifier(new IdentityOperation<T, RuntimeException>() { @Override @SuppressWarnings("unchecked") public T execute() { Object cloneId = getEquivalentId(clazz.getName(), originalId); if (cloneId == null) { return null; } return (T) genericEntityService.readGenericEntity(clazz.getName(), cloneId); } }, getToSite(), getToSite(), getToCatalog()); } public Object getEquivalentId(String className, Object fromId) { String ceilingImpl = genericEntityService.getCeilingImplClass(className).getName(); Map<Object, Object> keys = equivalentsMap.get(ceilingImpl); return keys == null ? null : keys.get(fromId); } public void storeEquivalentMapping(String className, Object fromId, Object toId) { String ceilingImpl = genericEntityService.getCeilingImplClass(className).getName(); Map<Object, Object> keys = equivalentsMap.get(ceilingImpl); if (keys == null) { keys = new HashMap<Object, Object>(); equivalentsMap.put(ceilingImpl, keys); } if (keys.containsKey(fromId)) { throw new IllegalArgumentException("Object [" + className + ":" + fromId + "] has already been cloned."); } keys.put(fromId, toId); } public Long getIdentifier(Object entity) { return (Long) genericEntityService.getIdentifier(entity); } public Catalog getFromCatalog() { return fromCatalog; } public Catalog getToCatalog() { return toCatalog; } public Site getFromSite() { return fromSite; } public Site getToSite() { return toSite; } /** * Detects whether or not the current cloned entity is an extension of an entity in Broadleaf, and if so, if the * extension itself does not implement clone. * * @param cloned the cloned entity instance * @throws CloneNotSupportedException thrown if the entity is an extension and is does not implement clone */ public void checkCloneable(Object cloned) throws CloneNotSupportedException { Method cloneMethod; try { cloneMethod = cloned.getClass().getMethod("createOrRetrieveCopyInstance", new Class[]{MultiTenantCopyContext.class}); } catch (NoSuchMethodException e) { throw ExceptionHelper.refineException(e); } boolean cloneMethodLocal = false; for (String prefix : BROADLEAF_PACKAGE_PREFIXES) { if (cloneMethod.getDeclaringClass().getName().startsWith(prefix)) { cloneMethodLocal = true; break; } } boolean cloneClassLocal = false; for (String prefix : BROADLEAF_PACKAGE_PREFIXES) { if (cloned.getClass().getName().startsWith(prefix)) { cloneClassLocal = true; break; } } if (cloneMethodLocal && !cloneClassLocal) { //subclass is not implementing the clone method throw new CloneNotSupportedException("The system is attempting to clone " + cloned.getClass().getName() + " and has determined the custom extension does not implement clone. This class should implement " + "clone, and inside first call super.clone() to get back an instance of your class (" + cloned.getClass().getName() + "), and then finish populating this instance with your custom fields before passing back the finished object."); } } /** * Create a new instance of the polymorphic entity type - could be an extended type outside of Broadleaf. * * @param instance the object instance for the actual entity type (could be extended) * @param <G> * @return the new, empty instance of the entity * @throws java.lang.CloneNotSupportedException */ public <G> CreateResponse<G> createOrRetrieveCopyInstance(Object instance) throws CloneNotSupportedException { BroadleafRequestContext context = BroadleafRequestContext.getBroadleafRequestContext(); context.setCurrentCatalog(getToCatalog()); context.setCurrentProfile(getToSite()); context.setSite(getToSite()); if (instance instanceof Status && 'Y' == ((Status) instance).getArchived()) { throw new CloneNotSupportedException("Attempting to clone an archived instance"); } Class<?> instanceClass = instance.getClass(); if (instanceClass.getAnnotation(Embeddable.class) != null) { G response; try { response = (G) instanceClass.newInstance(); } catch (InstantiationException e) { throw ExceptionHelper.refineException(e); } catch (IllegalAccessException e) { throw ExceptionHelper.refineException(e); } return new CreateResponse<G>(response, false); } Long originalId = getIdentifier(instance); Object previousClone; if (currentEquivalentMap.inverse().containsKey(instanceClass.getName() + "_" + originalId)) { previousClone = currentCloneMap.get(currentEquivalentMap.inverse().get(instanceClass.getName() + "_" + originalId)); } else { previousClone = getClonedVersion(instanceClass, originalId); } G response; boolean alreadyPopulate; if (previousClone != null) { response = (G) previousClone; alreadyPopulate = true; } else { try { response = (G) instanceClass.newInstance(); } catch (InstantiationException e) { throw ExceptionHelper.refineException(e); } catch (IllegalAccessException e) { throw ExceptionHelper.refineException(e); } checkCloneable(response); alreadyPopulate = false; currentEquivalentMap.put(System.identityHashCode(response), instanceClass.getName() + "_" + originalId); currentCloneMap.put(System.identityHashCode(response), response); try { for (Field field : getAllFields(instanceClass)) { if (field.getType().getAnnotation(Embeddable.class) != null && MultiTenantCloneable.class.isAssignableFrom(field.getType())) { field.setAccessible(true); Object embeddable = field.get(instance); if (embeddable != null) { field.set(response, ((MultiTenantCloneable) embeddable).createOrRetrieveCopyInstance(this).getClone()); } } } } catch (IllegalAccessException e) { throw ExceptionHelper.refineException(e); } } context.setCurrentCatalog(getFromCatalog()); context.setCurrentProfile(getFromSite()); context.setSite(getFromSite()); return new CreateResponse<G>(response, alreadyPopulate); } public void clearOriginalIdentifiers() { currentEquivalentMap.clear(); currentCloneMap.clear(); } public Object removeOriginalIdentifier(Object copy) { if (currentEquivalentMap.containsKey(System.identityHashCode(copy))) { currentCloneMap.remove(System.identityHashCode(copy)); String valKey = currentEquivalentMap.remove(System.identityHashCode(copy)); return Long.parseLong(valKey.substring(valKey.indexOf("_") + 1, valKey.length())); } return null; } public Field[] getAllFields(Class<?> targetClass) { Field[] allFields = new Field[]{}; boolean eof = false; Class<?> currentClass = targetClass; while (!eof) { Field[] fields = currentClass.getDeclaredFields(); allFields = (Field[]) ArrayUtils.addAll(allFields, fields); if (currentClass.getSuperclass() != null) { currentClass = currentClass.getSuperclass(); } else { eof = true; } } return allFields; } }