/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.openjpa.util; import java.security.AccessController; import org.apache.openjpa.conf.Compatibility; import org.apache.openjpa.kernel.DetachedStateManager; import org.apache.openjpa.kernel.OpenJPAStateManager; import org.apache.openjpa.lib.util.J2DoPrivHelper; import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.meta.ClassMetaData; /** * Utility methods for managing proxies. * * @author Abe White */ public class Proxies { private static final Localizer _loc = Localizer.forPackage(Proxies.class); /** * Used by proxy types to check if the given owners and field names * are equivalent. */ public static boolean isOwner(Proxy proxy, OpenJPAStateManager sm, int field) { return proxy.getOwner() == sm && proxy.getOwnerField() == field; } /** * Used by proxy types to check that an attempt to add a new value is legal. */ public static void assertAllowedType(Object value, Class allowed) { if (value != null && allowed != null && !allowed.isInstance(value)) { throw new UserException(_loc.get("bad-elem-type", new Object[]{ AccessController.doPrivileged( J2DoPrivHelper.getClassLoaderAction(allowed)), allowed, AccessController.doPrivileged( J2DoPrivHelper.getClassLoaderAction(value.getClass())), value.getClass() })); } } /** * Used by proxy types to dirty their owner. */ public static void dirty(Proxy proxy, boolean stopTracking) { if (proxy.getOwner() != null) proxy.getOwner().dirty(proxy.getOwnerField()); if (stopTracking && proxy.getChangeTracker() != null) proxy.getChangeTracker().stopTracking(); } /** * Used by proxy types to notify collection owner on element removal. */ public static void removed(Proxy proxy, Object removed, boolean key) { if (proxy.getOwner() != null && removed != null) proxy.getOwner().removed(proxy.getOwnerField(), removed, key); } /** * Used by proxy types to serialize non-proxy versions. */ public static Object writeReplace(Proxy proxy, boolean detachable) { /* OPENJPA-1097 Remove $proxy classes during serialization based on: * 1) No Proxy, then return as-is * 2) Runtime created proxy (!detachable), then unproxy * 3) No StateManager (DetachedStateField==false), then return as-is * Get the new IgnoreDetachedStateFieldForProxySerialization * Compatibility flag from either the metadata/configuration if * this is a normal StateManager, otherwise use the new flag * added to the DetachedStateManager * 4) If new 2.0 behavior * 4a) If ClassMetaData exists and DetachedStateField == TRUE * then do not remove the proxy and return as-is * 4b) Else, using DetachedStateField of transient(default) or * false, so unproxy * 5) If 1.0 app or requested old 1.0 behavior * 5a) If detached, then do not unproxy and return as-is * 5b) Else, unproxy * * Original code - * 1) Runtime created proxy (!detachable), then unproxy * 2) No Proxy, then return as-is * 3) No StateManager (DetachedStateField==false), then return as-is * 4) If detached, then return as-is <--- ERROR as EM.clear() marks * entity as detached but doesn't remove any $proxy usage * 5) Else, unproxy * * if (detachable && (proxy == null || proxy.getOwner() == null * || proxy.getOwner().isDetached())) * return proxy; * */ if (proxy == null) { return proxy; } else if (!detachable) { // OPENJPA-1571 - using our runtime generated proxies, so remove any $proxy return proxy.copy(proxy); } else if (proxy.getOwner() == null) { // no StateManager (DetachedStateField==false), so no $proxy to remove return proxy; } else { // using a StateManager, so determine what DetachedState is being used OpenJPAStateManager sm = proxy.getOwner(); // null checked for above ClassMetaData meta = null; // if null, no proxies? boolean useDSFForUnproxy = false; // default to false for old 1.0 behavior // Don't rely on sm.isDetached() method because if we are serializing an attached Entity // the sm will still be a StateManagerImpl, but isDetached() will return true. // Using a DetachedStateManager, so use the new flag since there is no context or // metadata if (sm instanceof DetachedStateManager) { useDSFForUnproxy = ((DetachedStateManager) sm).getUseDSFForUnproxy(); } else{ // DetachedStateManager has no context or metadata, so we can't get configuration settings Compatibility compat = null; meta = sm.getMetaData(); if (meta != null) { compat = meta.getRepository().getConfiguration().getCompatibilityInstance(); } else if (sm.getContext() != null && sm.getContext().getConfiguration() != null) { compat = sm.getContext().getConfiguration().getCompatibilityInstance(); } else { // no-op - using a StateManager, but no Compatibility settings available } if (compat != null) { // new 2.0 behavior of using DetachedStateField to determine unproxy during serialization useDSFForUnproxy = !compat.getIgnoreDetachedStateFieldForProxySerialization(); } } if (useDSFForUnproxy) { // use new 2.0 behavior if ((meta != null) && (Boolean.TRUE.equals(meta.usesDetachedState()))) { // configured to always use and serialize a StateManger, so keep any $proxy return proxy; } else { // already detached or using DetachedStateField==false or transient, so remove any $proxy return proxy.copy(proxy); } } else { // use old 1.0 behavior if (proxy.getOwner().isDetached()) return proxy; else return proxy.copy(proxy); } } } }