blob: 26fa2355e0007baaab29e2cf124fc5617f00a42e [file] [log] [blame]
/*
* 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);
}
}
}
}