/*
* Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved.
*
* This file is part of the Jspresso framework.
*
* Jspresso is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jspresso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Jspresso. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jspresso.framework.model.component.basic;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeListenerProxy;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import gnu.trove.map.hash.THashMap;
import gnu.trove.set.hash.THashSet;
import gnu.trove.set.hash.TLinkedHashSet;
import org.apache.commons.beanutils.MethodUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.jspresso.framework.model.component.ComponentException;
import org.jspresso.framework.model.component.IComponent;
import org.jspresso.framework.model.component.IComponentCollectionFactory;
import org.jspresso.framework.model.component.IComponentExtension;
import org.jspresso.framework.model.component.IComponentExtensionFactory;
import org.jspresso.framework.model.component.IComponentFactory;
import org.jspresso.framework.model.component.IComponentFactoryAware;
import org.jspresso.framework.model.component.ILifecycleCapable;
import org.jspresso.framework.model.component.IPropertyTranslation;
import org.jspresso.framework.model.component.service.AbstractComponentServiceDelegate;
import org.jspresso.framework.model.component.service.DependsOnHelper;
import org.jspresso.framework.model.component.service.IComponentService;
import org.jspresso.framework.model.component.service.ILifecycleInterceptor;
import org.jspresso.framework.model.descriptor.IBooleanPropertyDescriptor;
import org.jspresso.framework.model.descriptor.ICollectionPropertyDescriptor;
import org.jspresso.framework.model.descriptor.IComponentDescriptor;
import org.jspresso.framework.model.descriptor.IModelDescriptorAware;
import org.jspresso.framework.model.descriptor.IPropertyDescriptor;
import org.jspresso.framework.model.descriptor.IReferencePropertyDescriptor;
import org.jspresso.framework.model.descriptor.IRelationshipEndPropertyDescriptor;
import org.jspresso.framework.model.descriptor.IStringPropertyDescriptor;
import org.jspresso.framework.model.descriptor.MandatoryPropertyException;
import org.jspresso.framework.model.descriptor.basic.AbstractComponentDescriptor;
import org.jspresso.framework.model.entity.EntityHelper;
import org.jspresso.framework.model.entity.IEntity;
import org.jspresso.framework.model.entity.IEntityFactory;
import org.jspresso.framework.model.entity.IEntityLifecycleHandler;
import org.jspresso.framework.security.UserPrincipal;
import org.jspresso.framework.util.accessor.IAccessor;
import org.jspresso.framework.util.accessor.IAccessorFactory;
import org.jspresso.framework.util.accessor.ICollectionAccessor;
import org.jspresso.framework.util.bean.AccessorInfo;
import org.jspresso.framework.util.bean.BeanPropertyChangeRecorder;
import org.jspresso.framework.util.bean.EAccessorType;
import org.jspresso.framework.util.bean.IPropertyChangeCapable;
import org.jspresso.framework.util.bean.SinglePropertyChangeSupport;
import org.jspresso.framework.util.bean.SingleWeakPropertyChangeSupport;
import org.jspresso.framework.util.collection.CollectionHelper;
import org.jspresso.framework.util.lang.ObjectUtils;
/**
* This is the core implementation of all components in the application.
* Instances of this class serve as handlers for proxies representing the
* components.
*
* @author Vincent Vandenschrick
*/
public abstract class AbstractComponentInvocationHandler implements
InvocationHandler, Serializable {
// @formatter:off
private static final Logger LOG = LoggerFactory.getLogger(AbstractComponentInvocationHandler.class);
// @formatter:on
private static final long serialVersionUID = -8332414648339056836L;
private final IAccessorFactory accessorFactory;
private SinglePropertyChangeSupport propertyChangeSupport;
private SingleWeakPropertyChangeSupport weakPropertyChangeSupport;
private List<PropertyChangeEvent> delayedEvents;
private IComponentCollectionFactory collectionFactory;
private final IComponentDescriptor<? extends IComponent> componentDescriptor;
private Map<Class<IComponentExtension<IComponent>>, IComponentExtension<IComponent>> componentExtensions;
private final IComponentExtensionFactory extensionFactory;
private final IComponentFactory inlineComponentFactory;
private Set<String> modifierMonitors;
private boolean propertyProcessorsEnabled;
private boolean propertyChangeEnabled;
private boolean collectionSortEnabled;
private Map<String, NestedReferenceTracker> referenceTrackers;
private Map<String, Object> computedPropertiesCache;
private static final Collection<String> LIFECYCLE_METHOD_NAMES;
private IComponent owningComponent;
private IPropertyDescriptor owningPropertyDescriptor;
private Map<String, Set<String>> fakePclAttachements;
private Map<String, Set<String>> delayedFakePclAttachements;
// Fake PCL cannot be static, because there must be 1 registration on
// referent per owning instance, i.e. 2 different instances must not share
// the same fake PCL that will be removed when the referent is detached.
private PropertyChangeListener fakePcl;
static {
Collection<String> methodNames = new THashSet<>(6);
for (Method m : ILifecycleCapable.class.getMethods()) {
methodNames.add(m.getName());
}
LIFECYCLE_METHOD_NAMES = methodNames;
}
/**
* Constructs a new {@code BasicComponentInvocationHandler} instance.
*
* @param componentDescriptor
* The descriptor of the proxy component.
* @param inlineComponentFactory
* the factory used to create inline components.
* @param collectionFactory
* The factory used to create empty component collections from collection getters.
* @param accessorFactory
* The factory used to access proxy properties.
* @param extensionFactory
* The factory used to create component extensions based on their classes.
*/
protected AbstractComponentInvocationHandler(IComponentDescriptor<? extends IComponent> componentDescriptor,
IComponentFactory inlineComponentFactory,
IComponentCollectionFactory collectionFactory,
IAccessorFactory accessorFactory,
IComponentExtensionFactory extensionFactory) {
this.componentDescriptor = componentDescriptor;
this.inlineComponentFactory = inlineComponentFactory;
this.collectionFactory = collectionFactory;
this.accessorFactory = accessorFactory;
this.extensionFactory = extensionFactory;
this.propertyProcessorsEnabled = true;
this.propertyChangeEnabled = true;
this.collectionSortEnabled = true;
}
/**
* Gets the interface class being the contract of this component.
*
* @return the component interface contract.
*/
public Class<?> getComponentContract() {
return componentDescriptor.getComponentContract();
}
/**
* Handles methods invocations on the component proxy. Either : <li>delegates
* to one of its extension if the accessed property is registered as being
* part of an extension <li>handles property access internally
* <p/>
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public synchronized Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName()/* .intern() */;
switch (methodName) {
case "hashCode":
return computeHashCode((IComponent) proxy);
case "equals":
return computeEquals((IComponent) proxy, args[0]);
case "toString":
return toString(proxy);
case "getComponentContract":
return componentDescriptor.getComponentContract();
case "addPropertyChangeListener":
if (args.length == 1) {
addPropertyChangeListener(proxy, (PropertyChangeListener) args[0]);
return null;
}
addPropertyChangeListener(proxy, (String) args[0], (PropertyChangeListener) args[1]);
return null;
case "addWeakPropertyChangeListener":
if (args.length == 1) {
addWeakPropertyChangeListener(proxy, (PropertyChangeListener) args[0]);
return null;
}
addWeakPropertyChangeListener(proxy, (String) args[0], (PropertyChangeListener) args[1]);
return null;
case "removePropertyChangeListener":
if (args.length == 1) {
removePropertyChangeListener((PropertyChangeListener) args[0]);
return null;
}
removePropertyChangeListener((String) args[0], (PropertyChangeListener) args[1]);
return null;
case "hasListeners":
return hasListeners(proxy, (String) args[0]);
case "getPropertyChangeListeners":
if (args != null && args.length > 0) {
return getPropertyChangeListeners(proxy, (String) args[0]);
}
return getPropertyChangeListeners(proxy);
case "firePropertyChange":
firePropertyChange(proxy, (String) args[0], args[1], args[2]);
return null;
case "blockEvents":
return blockEvents();
case "releaseEvents":
releaseEvents();
return null;
case "straightSetProperty":
straightSetProperty(proxy, (String) args[0], args[1]);
return null;
case "straightGetProperty":
return straightGetProperty(proxy, (String) args[0]);
case "straightSetProperties":
straightSetProperties(proxy, (Map<String, Object>) args[0]);
return null;
case "straightGetProperties":
return straightGetProperties(proxy);
case "setPropertyProcessorsEnabled":
propertyProcessorsEnabled = (Boolean) args[0];
return null;
case "getOwningComponent":
return owningComponent;
case "getOwningPropertyDescriptor":
return owningPropertyDescriptor;
case "setOwningComponent":
owningComponent = (IComponent) args[0];
owningPropertyDescriptor = (IPropertyDescriptor) args[1];
return null;
case "checkIntegrity":
checkIntegrity(proxy);
return null;
case "checkMandatoryProperties":
checkMandatoryProperties(proxy);
return null;
default:
if (isLifecycleMethod(method)) {
return invokeLifecycleInterceptors(proxy, method, args);
}
AccessorInfo accessorInfo = getAccessorFactory().getAccessorInfo(method);
EAccessorType accessorType = accessorInfo.getAccessorType();
IPropertyDescriptor propertyDescriptor = null;
if (accessorType != EAccessorType.NONE) {
String accessedPropertyName = accessorInfo.getAccessedPropertyName();
if (accessedPropertyName != null) {
propertyDescriptor = componentDescriptor.getPropertyDescriptor(accessedPropertyName);
}
}
if (propertyDescriptor != null) {
Class<IComponentExtension<IComponent>> extensionClass = (Class<IComponentExtension<IComponent>>)
propertyDescriptor
.getDelegateClass();
if (extensionClass != null) {
return accessComputedProperty(propertyDescriptor, accessorInfo, extensionClass, proxy, method, args);
} else if (!propertyDescriptor.isComputed()) {
if (accessorInfo.isModifier()) {
if (modifierMonitors != null && modifierMonitors.contains(methodName)) {
return null;
}
if (modifierMonitors == null) {
modifierMonitors = new THashSet<>(1);
}
modifierMonitors.add(methodName);
}
try {
Object param;
switch (accessorType) {
case GETTER:
return getProperty(proxy, propertyDescriptor);
case SETTER:
param = sanitizeModifierParam(proxy, propertyDescriptor, args[0]);
setProperty(proxy, propertyDescriptor, param);
return null;
case ADDER:
if (args.length == 2) {
param = sanitizeModifierParam(proxy, propertyDescriptor, args[1]);
addToProperty(proxy, (ICollectionPropertyDescriptor<?>) propertyDescriptor, (Integer) args[0],
param);
} else {
param = sanitizeModifierParam(proxy, propertyDescriptor, args[0]);
addToProperty(proxy, (ICollectionPropertyDescriptor<?>) propertyDescriptor, param);
}
return null;
case REMOVER:
param = sanitizeModifierParam(proxy, propertyDescriptor, args[0]);
removeFromProperty(proxy, (ICollectionPropertyDescriptor<?>) propertyDescriptor, param);
return null;
default:
break;
}
} finally {
if (modifierMonitors != null && accessorInfo.isModifier()) {
modifierMonitors.remove(methodName);
}
}
} else {
String propertyName = propertyDescriptor.getName();
if (propertyDescriptor.isFilterOnly()) {
throw new ComponentException("The '" + propertyName
+ "' property is meant to be used only on filters for the following component : \n"
+ componentDescriptor.getComponentContract().getName());
} else if (propertyDescriptor instanceof IStringPropertyDescriptor
&& ((IStringPropertyDescriptor) propertyDescriptor).isTranslatable()) {
if (accessorInfo.isModifier()) {
if (propertyName.endsWith(IComponentDescriptor.NLS_SUFFIX)) {
invokeNlsSetter(proxy, (IStringPropertyDescriptor) propertyDescriptor, (String) args[0]);
} else {
invokeNlsOrRawSetter(proxy, (IStringPropertyDescriptor) propertyDescriptor, (String) args[0]);
}
return null;
} else {
if (propertyName.endsWith(IComponentDescriptor.NLS_SUFFIX)) {
return invokeNlsGetter(proxy, (IStringPropertyDescriptor) propertyDescriptor);
} else {
return invokeNlsOrRawGetter(proxy, (IStringPropertyDescriptor) propertyDescriptor);
}
}
} else {
try {
return invokeServiceMethod(proxy, method, args);
} catch (NoSuchMethodException ignored) {
// it will fall back in the general case.
}
throw new ComponentException("The '" + propertyName
+ "' property is described as computed but we couldn't determine a way to compute it,"
+ " either through an extension or a service delegate on the following component : \n"
+ componentDescriptor.getComponentContract().getName());
}
}
} else {
try {
return invokeServiceMethod(proxy, method, args);
} catch (NoSuchMethodException ignored) {
// it will fall back in the general case.
}
}
break;
}
throw new ComponentException(method.toString()
+ " is not supported on the component "
+ componentDescriptor.getComponentContract().getName());
}
/**
* Invoke nls getter.
*
* @param proxy
* the proxy
* @param propertyDescriptor
* the property descriptor
* @return the translated value
*/
protected String invokeNlsGetter(Object proxy, IStringPropertyDescriptor propertyDescriptor) {
return (String) straightGetProperty(proxy, propertyDescriptor.getName() + IComponentDescriptor.RAW_SUFFIX);
}
/**
* Invoke nls or raw getter.
*
* @param proxy
* the proxy
* @param propertyDescriptor
* the property descriptor
* @return the translated value or raw if non-existent.
*/
protected String invokeNlsOrRawGetter(Object proxy, IStringPropertyDescriptor propertyDescriptor) {
String nlsOrRawValue = invokeNlsGetter(proxy, propertyDescriptor);
if (nlsOrRawValue == null) {
nlsOrRawValue = (String) straightGetProperty(proxy, propertyDescriptor.getName() + IComponentDescriptor
.RAW_SUFFIX);
}
return nlsOrRawValue;
}
/**
* Invoke nls setter.
*
* @param proxy
* the proxy
* @param propertyDescriptor
* the property descriptor
* @param translatedValue
* the translated value
*/
protected void invokeNlsSetter(Object proxy, IStringPropertyDescriptor propertyDescriptor, String translatedValue) {
straightSetProperty(proxy, propertyDescriptor.getName() + IComponentDescriptor.RAW_SUFFIX, translatedValue);
}
/**
* Invoke nls or raw setter.
*
* @param proxy
* the proxy
* @param propertyDescriptor
* the property descriptor
* @param translatedValue
* the translated value
*/
protected void invokeNlsOrRawSetter(Object proxy, IStringPropertyDescriptor propertyDescriptor,
String translatedValue) {
String oldTranslation = invokeNlsOrRawGetter(proxy, propertyDescriptor);
invokeNlsSetter(proxy, propertyDescriptor, translatedValue);
String propertyName = propertyDescriptor.getName();
storeProperty(propertyName, translatedValue);
firePropertyChange(proxy, propertyName, oldTranslation, translatedValue);
}
private boolean isLifecycleMethod(Method method) {
String methodName = method.getName();
if (LIFECYCLE_METHOD_NAMES.contains(methodName)) {
try {
return ILifecycleCapable.class.getMethod(methodName,
method.getParameterTypes()) != null;
} catch (NoSuchMethodException ignored) {
// this is certainly normal.
}
}
return false;
}
/**
* Gives chance to subclasses to perform sanity checks and eventually
* substitute the passed param by an other one when it's technically
* necessary.
*
* @param target
* the target being modified.
* @param propertyDescriptor
* the descriptor of the property being modified.
* @param param
* the modifier parameter.
* @return the parameter to actually pass to the modifier
*/
protected Object sanitizeModifierParam(Object target,
IPropertyDescriptor propertyDescriptor, Object param) {
return param;
}
/**
* Sets the collectionFactory.
*
* @param collectionFactory
* the collectionFactory to set.
*/
public void setCollectionFactory(IComponentCollectionFactory collectionFactory) {
this.collectionFactory = collectionFactory;
}
/**
* Delegate method to compute object equality.
*
* @param proxy
* the target component to compute equality of.
* @param another
* the object to compute equality against.
* @return the computed equality.
*/
protected abstract boolean computeEquals(IComponent proxy, Object another);
/**
* Delegate method to compute hashcode.
*
* @param proxy
* the target component to compute hashcode for.
* @return the computed hashcode.
*/
protected abstract int computeHashCode(IComponent proxy);
/**
* Gives a chance to configure created extensions.
*
* @param extension
* the extension to configure.
*/
protected void configureExtension(IComponentExtension<IComponent> extension) {
if (extension instanceof IComponentFactoryAware) {
((IComponentFactoryAware) extension)
.setComponentFactory(getInlineComponentFactory());
}
extension.postCreate();
}
/**
* Gives a chance to the implementor to decorate a component reference before
* returning it when fetching association ends.
*
* @param referent
* the component reference to decorate.
* @param referentDescriptor
* the component descriptor of the referent.
* @return the decorated component.
*/
protected abstract IComponent decorateReferent(IComponent referent,
IComponentDescriptor<? extends IComponent> referentDescriptor);
/**
* An empty hook that gets called whenever an entity is detached from a parent
* one.
*
* @param parent
* the parent entity.
* @param child
* the child entity.
* @param propertyDescriptor
* the property descriptor this entity was detached from.
*/
@SuppressWarnings("UnusedParameters")
protected void entityDetached(IEntity parent, IEntity child,
IRelationshipEndPropertyDescriptor propertyDescriptor) {
// defaults to no-op.
}
/**
* Gets the accessorFactory.
*
* @return the accessorFactory.
*/
protected IAccessorFactory getAccessorFactory() {
return accessorFactory;
}
/**
* Gets a collection property value.
*
* @param proxy
* the proxy to get the property of.
* @param propertyDescriptor
* the property descriptor to get the value for.
* @return the property value.
*/
@SuppressWarnings({"unchecked", "ConstantConditions"})
protected Object getCollectionProperty(Object proxy,
ICollectionPropertyDescriptor<? extends IComponent> propertyDescriptor) {
String propertyName = propertyDescriptor.getName();
try {
Object property = straightGetProperty(proxy, propertyName);
if (property == null) {
property = collectionFactory
.createComponentCollection(propertyDescriptor.getReferencedDescriptor().getCollectionInterface());
storeProperty(propertyName, property);
}
if (property instanceof List<?>) {
List<IComponent> propertyAsList = (List<IComponent>) property;
for (int i = 0; i < propertyAsList.size(); i++) {
IComponent referent = propertyAsList.get(i);
IComponent decorated = decorateReferent(referent, propertyDescriptor
.getReferencedDescriptor().getElementDescriptor()
.getComponentDescriptor());
if (decorated != referent) {
propertyAsList.set(i, decorated);
}
if (referent == null) {
if (proxy instanceof IEntity) {
LOG.warn(
"A null element was detected in indexed list [{}] on {}, id {} at index {}", propertyName,
((IEntity) proxy).getComponentContract().getName(), ((IEntity) proxy).getId(), i);
LOG.warn("This might be normal but sometimes it reveals a mis-use of indexed collection property accessors.");
}
} else if(EntityHelper.isInlineComponentReference(
propertyDescriptor.getReferencedDescriptor().getElementDescriptor())) {
if (decorated != null) {
decorated.setOwningComponent((IComponent) proxy, propertyDescriptor);
}
}
}
} else if (property instanceof Set<?>) {
Set<IComponent> propertyAsSet = (Set<IComponent>) property;
for (IComponent referent : new THashSet<>(propertyAsSet)) {
IComponent decorated = decorateReferent(referent, propertyDescriptor
.getReferencedDescriptor().getElementDescriptor()
.getComponentDescriptor());
if (decorated != referent) {
propertyAsSet.add(decorated);
}
if (EntityHelper.isInlineComponentReference(
propertyDescriptor.getReferencedDescriptor().getElementDescriptor())) {
if (decorated != null) {
decorated.setOwningComponent((IComponent) proxy, propertyDescriptor);
}
}
}
}
if (isCollectionSortOnReadEnabled() && collectionSortEnabled) {
inlineComponentFactory.sortCollectionProperty((IComponent) proxy,
propertyName);
}
if (property instanceof ICollectionWrapper<?>) {
return property;
}
List<Class<?>> implementedInterfaces = new ArrayList<>();
implementedInterfaces.add(ICollectionWrapper.class);
implementedInterfaces.addAll(Arrays.asList(property.getClass().getInterfaces()));
return Proxy.newProxyInstance(AbstractComponentInvocationHandler.class.getClassLoader(),
implementedInterfaces.toArray(new Class[implementedInterfaces.size()]),
new PersistentCollectionWrapper<>((Collection<IComponent>) property, (IComponent) proxy, propertyName,
propertyDescriptor.getCollectionDescriptor().getElementDescriptor().getComponentContract(),
accessorFactory));
} catch (RuntimeException re) {
LOG.error("Error when retrieving [{}] collection property on {}",
propertyName, proxy);
throw (re);
}
}
/**
* Allow to disable collection property sorting on read.
*
* @return true if collection sorting is enabled on read access.
*/
protected boolean isCollectionSortOnReadEnabled() {
return true;
}
/**
* Is dirty tracking enabled.
*
* @return {@code true} if dirty tracking is enabled.
*/
protected boolean isDirtyTrackingEnabled() {
return true;
}
/**
* Sets dirty tracking enabled.
*
* @param enabled
* {@code true} if enabled, {@code false} otherwise.
*/
protected void setDirtyTrackingEnabled(boolean enabled) {
// NO-OP;
}
/**
* Creates and registers an extension instance.
*
* @param extensionClass
* the extension class.
* @param proxy
* the proxy to register the extension on.
* @return the component extension.
*/
protected synchronized IComponentExtension<? extends IComponent> getExtensionInstance(
Class<IComponentExtension<IComponent>> extensionClass, IComponent proxy) {
IComponentExtension<IComponent> extension;
if (componentExtensions == null) {
componentExtensions = new THashMap<>(1, 1.0f);
extension = null;
} else {
extension = componentExtensions.get(extensionClass);
}
if (extension == null) {
extension = extensionFactory.createComponentExtension(extensionClass,
componentDescriptor.getComponentContract(), proxy);
componentExtensions.put(extensionClass, extension);
configureExtension(extension);
}
return extension;
}
/**
* Gets the inlineComponentFactory.
*
* @return the inlineComponentFactory.
*/
protected IComponentFactory getInlineComponentFactory() {
return inlineComponentFactory;
}
/**
* Gets a property value.
*
* @param proxy
* the proxy to get the property of.
* @param propertyDescriptor
* the property descriptor to get the value for.
* @return the property value.
*/
@SuppressWarnings("unchecked")
protected Object getProperty(Object proxy,
IPropertyDescriptor propertyDescriptor) {
if (propertyDescriptor instanceof ICollectionPropertyDescriptor) {
return getCollectionProperty(
proxy,
(ICollectionPropertyDescriptor<? extends IComponent>) propertyDescriptor);
}
if (propertyDescriptor instanceof IReferencePropertyDescriptor) {
return getReferenceProperty(proxy,
(IReferencePropertyDescriptor<IComponent>) propertyDescriptor);
}
Object propertyValue = straightGetProperty(proxy,
propertyDescriptor.getName());
return propertyValue;
}
/**
* Gets a reference property value.
*
* @param proxy
* the proxy to get the property of.
* @param propertyDescriptor
* the property descriptor to get the value for.
* @return the property value.
*/
@SuppressWarnings("unchecked")
protected Object getReferenceProperty(Object proxy,
final IReferencePropertyDescriptor<IComponent> propertyDescriptor) {
String propertyName = propertyDescriptor.getName();
Object referent = straightGetProperty(proxy, propertyName);
if (referent instanceof IPropertyChangeCapable) {
initializeInlineTrackerIfNeeded((IPropertyChangeCapable) referent, propertyName, true);
Set<String> delayedNestedPropertyListening = null;
if (delayedFakePclAttachements != null) {
delayedNestedPropertyListening = delayedFakePclAttachements
.remove(propertyName);
}
if (delayedNestedPropertyListening != null) {
Set<String> nestedPropertyListening = null;
if (fakePclAttachements != null) {
nestedPropertyListening = fakePclAttachements
.get(propertyName);
}
if (nestedPropertyListening == null) {
nestedPropertyListening = new THashSet<>(1);
if (fakePclAttachements == null) {
fakePclAttachements = new THashMap<>(1, 1.0f);
}
fakePclAttachements.put(propertyName, nestedPropertyListening);
}
for (String nestedPropertyName : delayedNestedPropertyListening) {
((IPropertyChangeCapable) referent).addWeakPropertyChangeListener(nestedPropertyName, createOrGetFakePcl());
nestedPropertyListening.add(nestedPropertyName);
}
}
}
IComponentDescriptor<IComponent> referencedDescriptor = (IComponentDescriptor<IComponent>) propertyDescriptor
.getReferencedDescriptor();
if (referent == null
&& EntityHelper.isInlineComponentReference(propertyDescriptor)
&& !propertyDescriptor.isComputed() && propertyDescriptor.isMandatory()) {
boolean wasDirtyTrackingEnabled = isDirtyTrackingEnabled();
try {
setDirtyTrackingEnabled(false);
referent = inlineComponentFactory
.createComponentInstance(referencedDescriptor.getComponentContract());
storeReferenceProperty(proxy, propertyDescriptor, null, referent);
} finally {
setDirtyTrackingEnabled(wasDirtyTrackingEnabled);
}
}
if (referent instanceof IComponent) {
return decorateReferent((IComponent) referent, referencedDescriptor);
}
return referent;
}
/**
* Invokes a service method on the component.
*
* @param proxy
* the component to invoke the service on.
* @param method
* the method implemented by the component.
* @param args
* the arguments of the method implemented by the component.
* @return the value returned by the method execution if any.
*
* @throws NoSuchMethodException
* if no mean could be found to service the method.
*/
@SuppressWarnings("unchecked")
protected Object invokeServiceMethod(Object proxy, Method method,
Object... args) throws NoSuchMethodException {
IComponentService service = componentDescriptor.getServiceDelegate(method);
if (service != null) {
try {
if (service instanceof AbstractComponentServiceDelegate<?>) {
Method refinedMethod = service.getClass().getMethod(method.getName(),
method.getParameterTypes());
if (refinedMethod != null) {
return ((AbstractComponentServiceDelegate<Object>) service)
.executeWith(proxy, refinedMethod, args);
}
}
int signatureSize = method.getParameterTypes().length + 1;
Class<?>[] parameterTypes = new Class<?>[signatureSize];
Object[] parameters = new Object[signatureSize];
parameterTypes[0] = componentDescriptor.getComponentContract();
parameters[0] = proxy;
for (int i = 1; i < signatureSize; i++) {
parameterTypes[i] = method.getParameterTypes()[i - 1];
parameters[i] = args[i - 1];
}
return MethodUtils.invokeMethod(service, method.getName(), parameters,
parameterTypes);
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new ComponentException(ex);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new ComponentException(ex.getCause());
}
}
throw new NoSuchMethodException(method.toString());
}
/**
* Whether the object is fully initialized.
*
* @param objectOrProxy
* the object to test.
* @return true if the object is fully initialized.
*/
protected boolean isInitialized(Object objectOrProxy) {
return true;
}
/**
* An empty hook that gets called when an component is created (still transient).
*
* @param proxy
* the proxy
* @param entityFactory
* an entity factory instance which can be used to complete the lifecycle step.
* @param principal
* the principal triggering the action.
* @param entityLifecycleHandler
* entityLifecycleHandler.
*/
protected void onCreate(Object proxy, IEntityFactory entityFactory,
UserPrincipal principal, IEntityLifecycleHandler entityLifecycleHandler) {
registerServicesForwardingListenersIfNecessary(proxy);
}
/**
* An empty hook that gets called whenever an entity is to be persisted.
*
* @param proxy
* the proxy
* @param entityFactory
* an entity factory instance which can be used to complete the lifecycle step.
* @param principal
* the principal triggering the action.
* @param entityLifecycleHandler
* entityLifecycleHandler.
*/
protected void onPersist(Object proxy, IEntityFactory entityFactory,
UserPrincipal principal, IEntityLifecycleHandler entityLifecycleHandler) {
// defaults to no-op.
}
/**
* An empty hook that gets called whenever an entity is to be updated.
*
* @param proxy
* the proxy
* @param entityFactory
* an entity factory instance which can be used to complete the lifecycle step.
* @param principal
* the principal triggering the action.
* @param entityLifecycleHandler
* entityLifecycleHandler.
*/
protected void onUpdate(Object proxy, IEntityFactory entityFactory,
UserPrincipal principal, IEntityLifecycleHandler entityLifecycleHandler) {
// defaults to no-op.
}
/**
* An empty hook that gets called just before an component is deleted (delete).
*
* @param proxy
* the proxy
* @param entityFactory
* an entity factory instance which can be used to complete the lifecycle step.
* @param principal
* the principal triggering the action.
* @param entityLifecycleHandler
* entityLifecycleHandler.
* @return true if the state of the component has been updated.
*/
protected boolean onDelete(Object proxy, IEntityFactory entityFactory, UserPrincipal principal,
IEntityLifecycleHandler entityLifecycleHandler) {
// defaults to no-op.
return false;
}
/**
* An empty hook that gets called when an component is loaded from the persistent store or merged back
* from the unit of work.
*
* @param proxy
* the proxy
*/
protected void onLoad(Object proxy) {
registerServicesForwardingListenersIfNecessary(proxy);
}
/**
* An empty hook that gets called whenever an entity is cloned to the unit of work.
*
* @param <E>
* tha actual component type.
* @param proxy
* the proxy
* @param sourceComponent
* the component that is the source of the cloning.
*/
protected <E extends IComponent> void onClone(Object proxy, E sourceComponent) {
registerServicesForwardingListenersIfNecessary(proxy);
}
private boolean servicesForwardingListenersRegistered = false;
private void registerServicesForwardingListenersIfNecessary(Object proxy) {
if (!servicesForwardingListenersRegistered) {
servicesForwardingListenersRegistered = true;
Collection<Class<?>> serviceContracts = componentDescriptor.getServiceContracts();
if (serviceContracts != null) {
for (Class<?> serviceContract : serviceContracts) {
DependsOnHelper.registerDependsOnListeners(serviceContract, (IPropertyChangeCapable) proxy, accessorFactory);
}
}
}
}
/**
* Direct read access to the properties map without any other operation. Use
* with caution only in subclasses.
*
* @param propertyName
* the property name.
* @return the property value.
*/
protected abstract Object retrievePropertyValue(String propertyName);
/**
* Refine property to store object.
*
* @param propertyValue
* the property value
* @return the object
*/
protected Object refinePropertyToStore(Object propertyValue) {
if (propertyValue instanceof ICollectionWrapper<?>) {
return ((ICollectionWrapper) propertyValue).getWrappedCollection();
}
return propertyValue;
}
/**
* Direct write access to the properties map without any other operation. Use
* with caution only in subclasses.
*
* @param propertyName
* the property name.
* @param propertyValue
* the property value.
*/
protected abstract void storeProperty(String propertyName, Object propertyValue);
/**
* Store collection property.
*
* @param proxy
* the proxy
* @param propertyDescriptor
* the property descriptor
* @param oldPropertyValue
* the old property value
* @param newPropertyValue
* the new property value
*/
@SuppressWarnings("unchecked")
protected void storeCollectionProperty(Object proxy, ICollectionPropertyDescriptor<?> propertyDescriptor,
Object oldPropertyValue, Object newPropertyValue) {
String propertyName = propertyDescriptor.getName();
if (EntityHelper.isInlineComponentReference(propertyDescriptor.getReferencedDescriptor().getElementDescriptor())) {
if (oldPropertyValue instanceof Collection<?> && isInitialized(oldPropertyValue)) {
for (IComponent component : (Collection<IComponent>) oldPropertyValue) {
if (component != null) {
component.setOwningComponent(null, null);
}
}
}
if (newPropertyValue instanceof Collection<?> && isInitialized(newPropertyValue)) {
for (IComponent component : (Collection<IComponent>) newPropertyValue) {
if (component != null) {
component.setOwningComponent((IComponent) proxy, propertyDescriptor);
}
}
}
}
storeProperty(propertyName, newPropertyValue);
}
/**
* Performs necessary registration on inline components before actually
* storing them.
*
* @param proxy
* the proxy to store the reference property for.
* @param propertyDescriptor
* the reference property descriptor.
* @param oldPropertyValue
* the old reference property value.
* @param newPropertyValue
* the new reference property value.
*/
protected void storeReferenceProperty(Object proxy, IReferencePropertyDescriptor<?> propertyDescriptor, Object
oldPropertyValue, Object newPropertyValue) {
String propertyName = propertyDescriptor.getName();
NestedReferenceTracker referenceTracker = null;
if (referenceTrackers != null) {
referenceTracker = referenceTrackers.get(propertyName);
}
// Handle owning component.
if (oldPropertyValue instanceof IComponent && EntityHelper.isInlineComponentReference(propertyDescriptor)
&& isInitialized(oldPropertyValue)) {
((IComponent) oldPropertyValue).setOwningComponent(null, null);
}
if (newPropertyValue instanceof IComponent && EntityHelper.isInlineComponentReference(propertyDescriptor)
&& isInitialized(newPropertyValue)) {
((IComponent) newPropertyValue).setOwningComponent((IComponent) proxy, propertyDescriptor);
}
if (oldPropertyValue instanceof IPropertyChangeCapable) {
if (isInitialized(oldPropertyValue)) {
if (referenceTracker != null) {
((IPropertyChangeCapable) oldPropertyValue).removePropertyChangeListener(referenceTracker);
}
Set<String> nestedPropertyListening = null;
if (fakePclAttachements != null) {
nestedPropertyListening = fakePclAttachements.get(propertyName);
}
if (nestedPropertyListening != null) {
for (String nestedPropertyName : nestedPropertyListening) {
((IPropertyChangeCapable) oldPropertyValue).removePropertyChangeListener(nestedPropertyName,
createOrGetFakePcl());
}
}
if (delayedFakePclAttachements != null) {
delayedFakePclAttachements.remove(propertyName);
}
}
}
storeProperty(propertyName, newPropertyValue);
if (newPropertyValue instanceof IPropertyChangeCapable) {
Set<String> nestedPropertyListening = null;
if (fakePclAttachements != null) {
nestedPropertyListening = fakePclAttachements.get(propertyName);
}
if (nestedPropertyListening != null) {
if (isInitialized(newPropertyValue)) {
for (String nestedPropertyName : nestedPropertyListening) {
((IPropertyChangeCapable) newPropertyValue).addWeakPropertyChangeListener(nestedPropertyName,
createOrGetFakePcl());
}
} else {
if (delayedFakePclAttachements == null) {
delayedFakePclAttachements = new THashMap<>(1, 1.0f);
}
delayedFakePclAttachements.put(propertyName, nestedPropertyListening);
}
}
if (referenceTracker == null) {
referenceTracker = new NestedReferenceTracker(proxy, propertyName, EntityHelper.isInlineComponentReference(
propertyDescriptor) && !propertyDescriptor.isComputed());
if (referenceTrackers == null) {
referenceTrackers = new THashMap<>(1, 1.0f);
}
referenceTrackers.put(propertyName, referenceTracker);
}
referenceTracker.setInitialized(false);
initializeInlineTrackerIfNeeded((IPropertyChangeCapable) newPropertyValue, propertyName,
// To avoid breaking lazy initialization of oldPropertyValue
!isInitialized(oldPropertyValue) || (isInitialized(newPropertyValue) && !ObjectUtils.equals(oldPropertyValue,
newPropertyValue)));
} else if (referenceTracker != null) {
if (oldPropertyValue instanceof IComponent
// To avoid breaking lazy initialization optimisation
&& isInitialized(oldPropertyValue)) {
for (Map.Entry<String, Object> property : ((IComponent) oldPropertyValue).straightGetProperties().entrySet()) {
referenceTracker.propertyChange(new PropertyChangeEvent(oldPropertyValue, property.getKey(), property.getValue(), null));
}
}
}
}
/**
* Performs (potentially delayed due to lazy initialization) inline tracker
* attachment.
*
* @param referenceProperty
* the reference to link the tracker to.
* @param propertyName
* the property name of the tracker.
* @param fireNestedPropertyChange
* Whenever the initialization is performed, does a first set of
* property change events be fired ?
*/
private void initializeInlineTrackerIfNeeded(IPropertyChangeCapable referenceProperty, String propertyName, boolean fireNestedPropertyChange) {
if (referenceProperty != null && isInitialized(referenceProperty)) {
NestedReferenceTracker storedTracker = null;
if (referenceTrackers != null) {
storedTracker = referenceTrackers.get(propertyName);
}
if (storedTracker != null && !storedTracker.isInitialized()) {
storedTracker.setInitialized(true);
referenceProperty.addWeakPropertyChangeListener(storedTracker);
if (fireNestedPropertyChange && referenceProperty instanceof IComponent) {
for (Map.Entry<String, Object> property : ((IComponent) referenceProperty).straightGetProperties()
.entrySet()) {
storedTracker.propertyChange(new PropertyChangeEvent(referenceProperty, property.getKey(),
IPropertyChangeCapable.UNKNOWN, property.getValue()));
}
}
}
}
}
/**
* Directly gets all property values out of the property store without any
* other operation.
*
* @param proxy
* the proxy to straight get the properties from.
* @return The map of properties.
*/
protected Map<String, Object> straightGetProperties(Object proxy) {
Map<String, Object> allProperties = new HashMap<>();
for (IPropertyDescriptor propertyDescriptor : componentDescriptor.getPropertyDescriptors()) {
String propertyName = propertyDescriptor.getName();
if (!(propertyDescriptor.isComputed() && propertyDescriptor.getPersistenceFormula() == null)) {
allProperties.put(propertyName, straightGetProperty(proxy, propertyName));
}
}
return allProperties;
}
/**
* Directly get a property value out of the property store without any other
* operation.
*
* @param proxy
* the proxy to straight get the property from.
* @param propertyName
* the name of the property.
* @return the property value or null.
*/
protected Object straightGetProperty(Object proxy, String propertyName) {
IPropertyDescriptor propertyDescriptor = componentDescriptor.getPropertyDescriptor(propertyName);
if (propertyDescriptor == null || (propertyDescriptor.isComputed() && propertyDescriptor.getPersistenceFormula() == null)) {
return null;
}
Object propertyValue = retrievePropertyValue(propertyName);
if (propertyValue == null && propertyDescriptor instanceof IBooleanPropertyDescriptor) {
return Boolean.FALSE;
}
return propertyValue;
}
/**
* Directly set a property value to the property store without any other
* operation.
*
* @param proxy
* the proxy to straight set the property to.
* @param propertyName
* the name of the property.
* @param newPropertyValue
* the property value or null.
*/
protected void straightSetProperty(Object proxy, String propertyName, Object newPropertyValue) {
IPropertyDescriptor propertyDescriptor = componentDescriptor.getPropertyDescriptor(propertyName);
if (propertyDescriptor == null || (propertyDescriptor.isComputed() && propertyDescriptor.getPersistenceFormula() == null)) {
return;
}
Object currentPropertyValue = straightGetProperty(proxy, propertyName);
if (propertyDescriptor instanceof IReferencePropertyDescriptor) {
// reference must change sometimes even if entities are equal.
if (/* !ObjectUtils.equals(currentPropertyValue, newPropertyValue) */currentPropertyValue != newPropertyValue) {
storeReferenceProperty(proxy, (IReferencePropertyDescriptor<?>) propertyDescriptor, currentPropertyValue,
newPropertyValue);
}
} else if (propertyDescriptor instanceof ICollectionPropertyDescriptor) {
storeCollectionProperty(proxy, (ICollectionPropertyDescriptor<?>) propertyDescriptor, currentPropertyValue,
newPropertyValue );
if (currentPropertyValue != null && currentPropertyValue == newPropertyValue && isInitialized(currentPropertyValue)) {
currentPropertyValue = Proxy.newProxyInstance
(Thread.currentThread().getContextClassLoader(), new Class<?>[]{
((ICollectionPropertyDescriptor<?>) propertyDescriptor).getReferencedDescriptor().getCollectionInterface()},
new NeverEqualsInvocationHandler(CollectionHelper.cloneCollection((Collection<?>) currentPropertyValue)));
}
} else {
storeProperty(propertyName, newPropertyValue);
}
doFirePropertyChange(proxy, propertyName, currentPropertyValue, newPropertyValue);
}
private synchronized void addPropertyChangeListener(Object proxy, PropertyChangeListener listener) {
if (listener == null) {
return;
}
if (propertyChangeSupport == null) {
propertyChangeSupport = new SinglePropertyChangeSupport(proxy);
}
propertyChangeSupport.addPropertyChangeListener(listener);
}
private synchronized void addWeakPropertyChangeListener(Object proxy, PropertyChangeListener listener) {
if (listener == null) {
return;
}
if (weakPropertyChangeSupport == null) {
weakPropertyChangeSupport = new SingleWeakPropertyChangeSupport(proxy);
}
weakPropertyChangeSupport.addPropertyChangeListener(listener);
}
private synchronized void addPropertyChangeListener(Object proxy, String propertyName, PropertyChangeListener listener) {
if (listener == null) {
return;
}
if (propertyChangeSupport == null) {
propertyChangeSupport = new SinglePropertyChangeSupport(proxy);
}
handleNestedPropertyChangeListening(proxy, propertyName);
propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
}
private synchronized void addWeakPropertyChangeListener(Object proxy, String propertyName, PropertyChangeListener listener) {
if (listener == null) {
return;
}
if (weakPropertyChangeSupport == null) {
weakPropertyChangeSupport = new SingleWeakPropertyChangeSupport(proxy);
}
handleNestedPropertyChangeListening(proxy, propertyName);
weakPropertyChangeSupport.addPropertyChangeListener(propertyName, listener);
}
private void handleNestedPropertyChangeListening(Object proxy, String propertyName) {
int nestedDelimIndex = propertyName.indexOf(IAccessor.NESTED_DELIM);
if (nestedDelimIndex >= 0) {
String rootProperty = propertyName.substring(0, nestedDelimIndex);
String nestedPropertyName = propertyName.substring(nestedDelimIndex + 1);
NestedReferenceTracker referenceTracker = null;
if (referenceTrackers != null) {
referenceTracker = referenceTrackers.get(rootProperty);
}
if (referenceTracker == null) {
IReferencePropertyDescriptor<?> rootPropertyDescriptor = (IReferencePropertyDescriptor<?>) componentDescriptor
.getPropertyDescriptor(rootProperty);
referenceTracker = new NestedReferenceTracker(proxy, rootProperty, EntityHelper.isInlineComponentReference(
rootPropertyDescriptor) && !rootPropertyDescriptor.isComputed());
if (referenceTrackers == null) {
referenceTrackers = new THashMap<>(1, 1.0f);
}
referenceTrackers.put(rootProperty, referenceTracker);
}
Object currentRootProperty = straightGetProperty(proxy, rootProperty);
if (currentRootProperty instanceof IPropertyChangeCapable) {
if (isInitialized(currentRootProperty)) {
((IPropertyChangeCapable) currentRootProperty).addWeakPropertyChangeListener(nestedPropertyName,
createOrGetFakePcl());
Set<String> nestedPropertyListening = null;
if (fakePclAttachements != null) {
nestedPropertyListening = fakePclAttachements.get(propertyName);
}
if (nestedPropertyListening == null) {
nestedPropertyListening = new THashSet<>(1);
if (fakePclAttachements == null) {
fakePclAttachements = new THashMap<>(1, 1.0f);
}
fakePclAttachements.put(rootProperty, nestedPropertyListening);
}
nestedPropertyListening.add(nestedPropertyName);
} else {
Set<String> delayedNestedPropertyListening = null;
if (delayedFakePclAttachements != null) {
delayedNestedPropertyListening = delayedFakePclAttachements.get(propertyName);
}
if (delayedNestedPropertyListening == null) {
delayedNestedPropertyListening = new THashSet<>(1);
if (delayedFakePclAttachements == null) {
delayedFakePclAttachements = new THashMap<>(1, 1.0f);
}
delayedFakePclAttachements.put(rootProperty, delayedNestedPropertyListening);
}
delayedNestedPropertyListening.add(nestedPropertyName);
}
}
referenceTracker.addToTrackedProperties(nestedPropertyName);
}
}
@SuppressWarnings("unchecked")
protected void addToProperty(Object proxy, ICollectionPropertyDescriptor<?> propertyDescriptor, int index, Object value) {
String propertyName = propertyDescriptor.getName();
Collection<Object> collectionProperty = (Collection<Object>) straightGetProperty(proxy, propertyName);
if (value instanceof IEntity && collectionProperty.contains(value)) {
if (collectionProperty instanceof Set<?>) {
LOG.warn(
"You have added twice the same element to the following collection property : {}.{}" + componentDescriptor
.getComponentContract().getName(), propertyName);
} else {
throw new ComponentException(
"Collection property does not allow duplicates : " + componentDescriptor.getComponentContract().getName() + "."
+ propertyName);
}
}
try {
if (propertyProcessorsEnabled) {
propertyDescriptor.preprocessAdder(proxy, collectionProperty, value);
}
IRelationshipEndPropertyDescriptor reversePropertyDescriptor = propertyDescriptor.getReverseRelationEnd();
if (reversePropertyDescriptor != null) {
if (reversePropertyDescriptor instanceof IReferencePropertyDescriptor<?>) {
accessorFactory.createPropertyAccessor(reversePropertyDescriptor.getName(),
propertyDescriptor.getReferencedDescriptor().getElementDescriptor().getComponentContract()).setValue(
value, proxy);
} else if (reversePropertyDescriptor instanceof ICollectionPropertyDescriptor<?>) {
ICollectionAccessor collectionAccessor = accessorFactory.createCollectionPropertyAccessor(
reversePropertyDescriptor.getName(),
propertyDescriptor.getReferencedDescriptor().getElementDescriptor().getComponentContract(),
((ICollectionPropertyDescriptor<?>) reversePropertyDescriptor).getCollectionDescriptor()
.getElementDescriptor()
.getComponentContract());
if (collectionAccessor instanceof IModelDescriptorAware) {
((IModelDescriptorAware) collectionAccessor).setModelDescriptor(reversePropertyDescriptor);
}
collectionAccessor.addToValue(value, proxy);
}
}
Collection<?> oldCollectionSnapshot = CollectionHelper.cloneCollection((Collection<?>) collectionProperty);
boolean inserted;
if (collectionProperty instanceof List<?> && index >= 0 && index < collectionProperty.size()) {
((List<Object>) collectionProperty).add(index, value);
inserted = true;
} else {
inserted = collectionProperty.add(value);
}
if (inserted) {
if (EntityHelper.isInlineComponentReference(propertyDescriptor.getReferencedDescriptor().getElementDescriptor())) {
if (value != null) {
((IComponent) value).setOwningComponent((IComponent) proxy, propertyDescriptor);
}
}
if (collectionSortEnabled) {
inlineComponentFactory.sortCollectionProperty((IComponent) proxy, propertyName);
}
// This will be a no-op. Only for allowing to put a breakpoint on the state collection property setter.
storeProperty(propertyName, collectionProperty);
doFirePropertyChange(proxy, propertyName, oldCollectionSnapshot, collectionProperty);
if (propertyProcessorsEnabled) {
propertyDescriptor.postprocessAdder(proxy, collectionProperty, value);
}
}
} catch (RuntimeException ex) {
rollbackProperty(proxy, propertyDescriptor, collectionProperty);
throw ex;
} catch (InvocationTargetException ex) {
rollbackProperty(proxy, propertyDescriptor, collectionProperty);
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new ComponentException(ex.getCause());
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new ComponentException(ex);
}
}
private void addToProperty(Object proxy, ICollectionPropertyDescriptor<?> propertyDescriptor, Object value) {
addToProperty(proxy, propertyDescriptor, -1, value);
}
private void checkIntegrity(Object proxy) {
checkMandatoryProperties(proxy);
if (propertyProcessorsEnabled) {
for (IPropertyDescriptor propertyDescriptor : componentDescriptor.getPropertyDescriptors()) {
if (!propertyDescriptor.isComputed()) {
propertyDescriptor.preprocessSetter(proxy, straightGetProperty(proxy, propertyDescriptor.getName()));
}
}
}
}
private void checkMandatoryProperties(Object proxy) {
if (propertyProcessorsEnabled) {
for (IPropertyDescriptor propertyDescriptor : componentDescriptor.getPropertyDescriptors()) {
if (!propertyDescriptor.isComputed()) {
if (propertyDescriptor.isMandatory()) {
Object newValue = straightGetProperty(proxy, propertyDescriptor.getName());
if (newValue == null || (isInitialized(newValue) && newValue instanceof Collection<?>
&& ((Collection<?>) newValue).isEmpty())) {
throw new MandatoryPropertyException(propertyDescriptor, proxy);
}
}
if (propertyDescriptor instanceof ICollectionPropertyDescriptor<?>
&& !((ICollectionPropertyDescriptor<?>) propertyDescriptor).getReferencedDescriptor()
.isNullElementAllowed()) {
Object newValue = straightGetProperty(proxy, propertyDescriptor.getName());
if (isInitialized(newValue) && newValue instanceof Collection<?>) {
for (Object element : ((Collection<?>) newValue)) {
if (element == null) {
throw new MandatoryPropertyException(propertyDescriptor, proxy);
}
}
}
}
}
}
}
}
@SuppressWarnings({"unused", "UnusedParameters"})
private boolean hasListeners(Object proxy, String propertyName) {
if (computedPropertiesCache != null && computedPropertiesCache.containsKey(propertyName)) {
// this is necessary in order to force cache re-computation
computedPropertiesCache.remove(propertyName);
}
if (propertyChangeSupport != null && propertyChangeSupport.hasListeners(propertyName)) {
PropertyChangeListener[] listeners = propertyChangeSupport.getPropertyChangeListeners(propertyName);
if (listeners != null && listeners.length > 0) {
return true;
}
listeners = propertyChangeSupport.getPropertyChangeListeners();
for (PropertyChangeListener listener : listeners) {
// Avoid single property change listeners and dirt trackers
if (!(listener instanceof PropertyChangeListenerProxy || listener instanceof BeanPropertyChangeRecorder)) {
return true;
}
}
}
if (weakPropertyChangeSupport != null && weakPropertyChangeSupport.hasListeners(propertyName)) {
PropertyChangeListener[] listeners = weakPropertyChangeSupport.getPropertyChangeListeners(propertyName);
if (listeners != null && listeners.length > 0) {
return true;
}
listeners = weakPropertyChangeSupport.getPropertyChangeListeners();
for (PropertyChangeListener listener : listeners) {
if (listener instanceof NestedReferenceTracker
&& ((NestedReferenceTracker) listener).source instanceof IComponent) {
if (!propertyName.contains(((NestedReferenceTracker) listener).referencePropertyName)
&& ((IComponent) ((NestedReferenceTracker) listener).source).hasListeners(
((NestedReferenceTracker) listener).referencePropertyName + "." + propertyName)) {
// Query nested component but prevent
// stack overflows with 1-1 relationships
return true;
}
} else {
return true;
}
}
}
return false;
}
@SuppressWarnings({"unused", "UnusedParameters"})
private PropertyChangeListener[] getPropertyChangeListeners(Object proxy) {
List<PropertyChangeListener> listeners = new ArrayList<>();
if (propertyChangeSupport != null) {
for (PropertyChangeListener pcl : propertyChangeSupport.getPropertyChangeListeners()) {
// Avoid single property change listeners
if (!(pcl instanceof PropertyChangeListenerProxy)) {
listeners.add(pcl);
}
}
}
if (weakPropertyChangeSupport != null) {
Collections.addAll(listeners, weakPropertyChangeSupport.getPropertyChangeListeners());
}
return listeners.toArray(new PropertyChangeListener[listeners.size()]);
}
@SuppressWarnings({"unused", "UnusedParameters"})
private PropertyChangeListener[] getPropertyChangeListeners(Object proxy, String propertyName) {
List<PropertyChangeListener> listeners = new ArrayList<>();
if (propertyChangeSupport != null) {
Collections.addAll(listeners, propertyChangeSupport.getPropertyChangeListeners(propertyName));
}
if (weakPropertyChangeSupport != null) {
Collections.addAll(listeners, weakPropertyChangeSupport.getPropertyChangeListeners(propertyName));
}
return listeners.toArray(new PropertyChangeListener[listeners.size()]);
}
/**
* Fire property change.
*
* @param proxy
* the proxy
* @param propertyName
* the property name
* @param oldValue
* the old value
* @param newValue
* the new value
*/
protected void firePropertyChange(Object proxy, String propertyName, Object oldValue, Object newValue) {
Object actualNewValue = newValue;
if (computedPropertiesCache != null && computedPropertiesCache.containsKey(propertyName)
&& oldValue != IPropertyChangeCapable.UNKNOWN) {
computedPropertiesCache.remove(propertyName);
actualNewValue = IPropertyChangeCapable.UNKNOWN;
}
doFirePropertyChange(proxy, propertyName, oldValue, actualNewValue);
// This method supports firing nested property changes
if (propertyName != null) {
int lastIndexOfDelim = propertyName.lastIndexOf(IAccessor.NESTED_DELIM);
if (lastIndexOfDelim > 0) {
Object propertyHolder;
try {
propertyHolder = getAccessorFactory().createPropertyAccessor(propertyName.substring(0, lastIndexOfDelim),
getComponentContract()).getValue(proxy);
if (propertyHolder != null && propertyHolder instanceof IComponent) {
((IComponent) propertyHolder).firePropertyChange(propertyName.substring(lastIndexOfDelim + 1), oldValue,
actualNewValue);
}
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new ComponentException(ex);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new ComponentException(ex.getCause());
}
}
}
}
private void doFirePropertyChange(Object proxy, String propertyName, Object oldValue, Object newValue) {
if (propertyChangeEnabled) {
if ((oldValue == null && newValue == null) || (oldValue == newValue)) {
return;
}
if (!isInitialized(oldValue) || !isInitialized(newValue)) {
doFirePropertyChange(new PropertyChangeEvent(proxy, propertyName, IPropertyChangeCapable.UNKNOWN, newValue));
} else {
doFirePropertyChange(new PropertyChangeEvent(proxy, propertyName, oldValue, newValue));
}
}
}
private void doFirePropertyChange(PropertyChangeEvent evt) {
if (propertyChangeEnabled) {
if (delayedEvents != null) {
if (isDirtyTrackingEnabled()) {
delayedEvents.add(evt);
} else {
delayedEvents.add(new DirtyFreePropertyChangeEvent(evt));
}
} else {
if (propertyChangeSupport != null) {
propertyChangeSupport.firePropertyChange(evt);
}
if (weakPropertyChangeSupport != null) {
weakPropertyChangeSupport.firePropertyChange(evt);
}
}
}
}
private static class DirtyFreePropertyChangeEvent extends PropertyChangeEvent {
private static final long serialVersionUID = -3661229785535176973L;
/**
* Instantiates a new Dirty free property change event.
*
* @param pce
* the pce
*/
public DirtyFreePropertyChangeEvent(PropertyChangeEvent pce) {
super(pce.getSource(), pce.getPropertyName(), pce.getOldValue(), pce.getNewValue());
}
}
private boolean blockEvents() {
if (delayedEvents == null) {
delayedEvents = new ArrayList<>();
return true;
}
return false;
}
private void releaseEvents() {
if (delayedEvents != null) {
List<PropertyChangeEvent> delayedEventsCopy = new ArrayList<>(delayedEvents);
delayedEvents = null;
for (PropertyChangeEvent evt : delayedEventsCopy) {
boolean wasDirtyTrackingEnabled = isDirtyTrackingEnabled();
try {
if (evt instanceof DirtyFreePropertyChangeEvent) {
setDirtyTrackingEnabled(false);
}
doFirePropertyChange(evt);
} finally {
setDirtyTrackingEnabled(wasDirtyTrackingEnabled);
}
}
}
}
@SuppressWarnings("unchecked")
private synchronized Object accessComputedProperty(IPropertyDescriptor propertyDescriptor, AccessorInfo accessorInfo,
Class<IComponentExtension<IComponent>> extensionClass, Object proxy,
Method method, Object... args) {
try {
String propertyName = propertyDescriptor.getName();
Object computedPropertyValue = null;
if (accessorInfo.isModifier()) {
computedPropertyValue = getAccessorFactory().createPropertyAccessor(propertyDescriptor.getName(),
getComponentContract()).getValue(proxy);
Object interceptedValue = args[args.length - 1];
if (propertyProcessorsEnabled) {
switch (accessorInfo.getAccessorType()) {
case SETTER:
interceptedValue = propertyDescriptor.interceptSetter(proxy, interceptedValue);
propertyDescriptor.preprocessSetter(proxy, interceptedValue);
break;
case ADDER:
((ICollectionPropertyDescriptor<?>) propertyDescriptor).preprocessAdder(proxy,
(Collection<Object>) computedPropertyValue, interceptedValue);
break;
case REMOVER:
((ICollectionPropertyDescriptor<?>) propertyDescriptor).preprocessRemover(proxy,
(Collection<Object>) computedPropertyValue, interceptedValue);
break;
default:
break;
}
args[args.length - 1] = interceptedValue;
}
} else if (propertyDescriptor.isCacheable()) {
if (computedPropertiesCache != null && computedPropertiesCache.containsKey(propertyName)) {
computedPropertyValue = computedPropertiesCache.get(propertyName);
return computedPropertyValue;
}
}
IComponentExtension<? extends IComponent> extensionDelegate = getExtensionInstance(extensionClass, (IComponent) proxy);
if (accessorInfo.isModifier()) {
// do not change computed property value
invokeExtensionMethod(extensionDelegate, method, args);
} else {
computedPropertyValue = invokeExtensionMethod(extensionDelegate, method, args);
}
if (accessorInfo.isModifier()) {
Object newComputedPropertyValue = getAccessorFactory().createPropertyAccessor(propertyDescriptor.getName(),
getComponentContract()).getValue(proxy);
switch (accessorInfo.getAccessorType()) {
case SETTER:
propertyDescriptor.postprocessSetter(proxy, computedPropertyValue, newComputedPropertyValue);
break;
case ADDER:
((ICollectionPropertyDescriptor<?>) propertyDescriptor).postprocessAdder(proxy,
(Collection<Object>) newComputedPropertyValue, args[args.length - 1]);
break;
case REMOVER:
((ICollectionPropertyDescriptor<?>) propertyDescriptor).postprocessRemover(proxy,
(Collection<Object>) newComputedPropertyValue, args[args.length - 1]);
break;
default:
break;
}
} else if (propertyDescriptor.isCacheable()) {
if (computedPropertiesCache == null) {
computedPropertiesCache = new THashMap<>(1, 1.0f);
}
computedPropertiesCache.put(propertyName, computedPropertyValue);
}
return computedPropertyValue;
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new ComponentException(ex);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new ComponentException(ex.getCause());
}
}
private Object invokeExtensionMethod(IComponentExtension<? extends IComponent> componentExtension, Method method,
Object... args)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
return MethodUtils.invokeMethod(componentExtension, method.getName(), args, method.getParameterTypes());
}
@SuppressWarnings({"unchecked", "ConstantConditions"})
private boolean invokeLifecycleInterceptors(Object proxy, Method lifecycleMethod, Object... args) {
String methodName = lifecycleMethod.getName()/* .intern() */;
if (ILifecycleCapable.ON_CREATE_METHOD_NAME.equals(methodName)) {
onCreate(proxy, (IEntityFactory) args[0], (UserPrincipal) args[1], (IEntityLifecycleHandler) args[2]);
} else if (ILifecycleCapable.ON_PERSIST_METHOD_NAME.equals(methodName)) {
onPersist(proxy, (IEntityFactory) args[0], (UserPrincipal) args[1], (IEntityLifecycleHandler) args[2]);
} else if (ILifecycleCapable.ON_UPDATE_METHOD_NAME.equals(methodName)) {
onUpdate(proxy, (IEntityFactory) args[0], (UserPrincipal) args[1], (IEntityLifecycleHandler) args[2]);
} else if (ILifecycleCapable.ON_LOAD_METHOD_NAME.equals(methodName)) {
onLoad(proxy);
} else if (ILifecycleCapable.ON_CLONE_METHOD_NAME.equals(methodName)) {
onClone(proxy, (IComponent) args[0]);
} else if (ILifecycleCapable.ON_DELETE_METHOD_NAME.equals(methodName)) {
onDelete(proxy, (IEntityFactory) args[0], (UserPrincipal) args[1], (IEntityLifecycleHandler) args[2]);
}
boolean interceptorResults = false;
for (ILifecycleInterceptor<?> lifecycleInterceptor : componentDescriptor.getLifecycleInterceptors()) {
int signatureSize = lifecycleMethod.getParameterTypes().length + 1;
Class<?>[] parameterTypes = new Class<?>[signatureSize];
Object[] parameters = new Object[signatureSize];
parameterTypes[0] = componentDescriptor.getComponentContract();
parameters[0] = proxy;
for (int i = 1; i < signatureSize; i++) {
parameterTypes[i] = lifecycleMethod.getParameterTypes()[i - 1];
parameters[i] = args[i - 1];
}
try {
Object interceptorResult = MethodUtils.invokeMethod(lifecycleInterceptor, methodName, parameters, parameterTypes);
if (interceptorResult instanceof Boolean) {
interceptorResults = interceptorResults || (Boolean) interceptorResult;
}
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new ComponentException(ex);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new ComponentException(ex.getCause());
}
}
// invoke lifecycle method on inline components
for (IPropertyDescriptor propertyDescriptor : componentDescriptor
.getPropertyDescriptors()) {
if (propertyDescriptor instanceof IReferencePropertyDescriptor<?>
&& EntityHelper
.isInlineComponentReference((IReferencePropertyDescriptor<IComponent>) propertyDescriptor)
&& !propertyDescriptor.isComputed()) {
Object inlineComponent = getProperty(proxy, propertyDescriptor);
if (inlineComponent instanceof ILifecycleCapable) {
try {
Object[] compArgs = null;
if (args != null) {
compArgs = new Object[args.length];
for (int j = 0; j < args.length; j++) {
// certainly onClone argument
if (args[j] != null
&& ((IComponent) proxy).getComponentContract().isAssignableFrom(args[j].getClass())) {
// we must retrieve the original inline component
compArgs[j] = ((IComponent) args[j]).straightGetProperty(propertyDescriptor.getName());
} else {
compArgs[j] = args[j];
}
}
}
Object interceptorResult = MethodUtils.invokeMethod(
inlineComponent, methodName, compArgs,
lifecycleMethod.getParameterTypes());
if (interceptorResult instanceof Boolean) {
interceptorResults = interceptorResults
|| (Boolean) interceptorResult;
}
} catch (NoSuchMethodException | IllegalAccessException ex) {
throw new ComponentException(ex);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new ComponentException(ex.getCause());
}
}
}
}
switch (methodName) {
case ILifecycleCapable.ON_PERSIST_METHOD_NAME:
// Important to check for not null values.
checkIntegrity(proxy);
break;
case ILifecycleCapable.ON_UPDATE_METHOD_NAME:
// Important to check for not null values
// since the checking has been delayed until persistence.
checkMandatoryProperties(proxy);
break;
case ILifecycleCapable.ON_DELETE_METHOD_NAME:
// Performs any necessary operation on internal state to mark the proxy
// deleted and thus unusable.
markDeleted(proxy);
break;
default:
break;
}
return interceptorResults;
}
/**
* Performs any necessary operation on internal state to mark the proxy
* deleted and thus unusable.
*
* @param proxy
* the proxy.
*/
protected void markDeleted(Object proxy) {
// NO-OP.
}
@SuppressWarnings("unchecked")
protected void removeFromProperty(Object proxy,
ICollectionPropertyDescriptor<?> propertyDescriptor, Object value) {
String propertyName = propertyDescriptor.getName();
// The following optimization breaks bidirectional N-N relationship persistence
// if (!isInitialized(straightGetProperty(proxy, propertyName))) {
// return;
// }
Collection<Object> collectionProperty = (Collection<Object>) straightGetProperty(proxy, propertyName);
try {
if (propertyProcessorsEnabled) {
propertyDescriptor.preprocessRemover(proxy, collectionProperty, value);
}
if (collectionProperty.contains(value)) {
IRelationshipEndPropertyDescriptor reversePropertyDescriptor = propertyDescriptor
.getReverseRelationEnd();
if (reversePropertyDescriptor != null) {
if (reversePropertyDescriptor instanceof IReferencePropertyDescriptor<?>) {
accessorFactory.createPropertyAccessor(
reversePropertyDescriptor.getName(),
propertyDescriptor.getReferencedDescriptor()
.getElementDescriptor().getComponentContract()).setValue(
value, null);
} else if (reversePropertyDescriptor instanceof ICollectionPropertyDescriptor<?>) {
ICollectionAccessor collectionAccessor = accessorFactory
.createCollectionPropertyAccessor(reversePropertyDescriptor.getName(),
propertyDescriptor.getReferencedDescriptor().getElementDescriptor().getComponentContract(),
((ICollectionPropertyDescriptor<?>) reversePropertyDescriptor).getCollectionDescriptor()
.getElementDescriptor()
.getComponentContract());
if (collectionAccessor instanceof IModelDescriptorAware) {
((IModelDescriptorAware) collectionAccessor)
.setModelDescriptor(reversePropertyDescriptor);
}
collectionAccessor.removeFromValue(value, proxy);
}
}
Collection<?> oldCollectionSnapshot = CollectionHelper
.cloneCollection((Collection<?>) collectionProperty);
if (collectionProperty.remove(value)) {
if (EntityHelper.isInlineComponentReference(propertyDescriptor.getReferencedDescriptor().getElementDescriptor())) {
if (value != null) {
((IComponent) value).setOwningComponent(null, null);
}
}
// This will be a no-op. Only for allowing to put a breakpoint on the state collection property setter.
storeProperty(propertyName, collectionProperty);
doFirePropertyChange(proxy, propertyName, oldCollectionSnapshot,
collectionProperty);
if (propertyProcessorsEnabled) {
propertyDescriptor.postprocessRemover(proxy, collectionProperty,
value);
}
if (proxy instanceof IEntity && value instanceof IEntity) {
entityDetached((IEntity) proxy, (IEntity) value, propertyDescriptor);
}
}
}
} catch (RuntimeException ex) {
rollbackProperty(proxy, propertyDescriptor, collectionProperty);
throw ex;
} catch (InvocationTargetException ex) {
rollbackProperty(proxy, propertyDescriptor, collectionProperty);
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new ComponentException(ex.getCause());
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new ComponentException(ex);
}
}
private synchronized void removePropertyChangeListener(
PropertyChangeListener listener) {
if (listener == null) {
return;
}
if (propertyChangeSupport != null) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
if (weakPropertyChangeSupport != null) {
weakPropertyChangeSupport.removePropertyChangeListener(listener);
}
}
private synchronized void removePropertyChangeListener(String propertyName,
PropertyChangeListener listener) {
if (listener == null) {
return;
}
if (propertyChangeSupport != null) {
propertyChangeSupport
.removePropertyChangeListener(propertyName, listener);
}
if (weakPropertyChangeSupport != null) {
weakPropertyChangeSupport.removePropertyChangeListener(propertyName,
listener);
}
}
private void rollbackProperty(Object proxy,
IPropertyDescriptor propertyDescriptor, Object oldProperty) {
// boolean wasPropertyProcessorsEnabled = propertyProcessorsEnabled;
// try {
// propertyProcessorsEnabled = false;
// setProperty(proxy, propertyDescriptor, oldProperty);
// } finally {
// propertyProcessorsEnabled = wasPropertyProcessorsEnabled;
// }
straightSetProperty(proxy, propertyDescriptor.getName(), oldProperty);
}
@SuppressWarnings("unchecked")
private void setProperty(Object proxy,
IPropertyDescriptor propertyDescriptor, Object newProperty) {
String propertyName = propertyDescriptor.getName();
Object oldProperty;
try {
oldProperty = accessorFactory.createPropertyAccessor(propertyName,
componentDescriptor.getComponentContract()).getValue(proxy);
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new ComponentException(ex);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new ComponentException(ex.getCause());
}
Object actualNewProperty;
if (propertyProcessorsEnabled) {
actualNewProperty = propertyDescriptor
.interceptSetter(proxy, newProperty);
} else {
actualNewProperty = newProperty;
}
if (isInitialized(oldProperty) && isInitialized(actualNewProperty)
&& ObjectUtils.equals(oldProperty, actualNewProperty)) {
return;
}
if (propertyProcessorsEnabled) {
propertyDescriptor.preprocessSetter(proxy, actualNewProperty);
}
if (propertyDescriptor instanceof IRelationshipEndPropertyDescriptor) {
// It's a relation end
IRelationshipEndPropertyDescriptor reversePropertyDescriptor = ((IRelationshipEndPropertyDescriptor) propertyDescriptor)
.getReverseRelationEnd();
try {
if (propertyDescriptor instanceof IReferencePropertyDescriptor) {
// It's a 'one' relation end
storeReferenceProperty(proxy,
(IReferencePropertyDescriptor<?>) propertyDescriptor,
oldProperty, actualNewProperty);
if (reversePropertyDescriptor != null) {
// It is bidirectional, so we are going to update the other end.
if (reversePropertyDescriptor instanceof IReferencePropertyDescriptor) {
// It's a one-to-one relationship
if (proxy instanceof IEntity && oldProperty instanceof IEntity) {
entityDetached((IEntity) proxy, (IEntity) oldProperty,
((IRelationshipEndPropertyDescriptor) propertyDescriptor));
}
IAccessor reversePropertyAccessor = accessorFactory
.createPropertyAccessor(reversePropertyDescriptor.getName(),
((IReferencePropertyDescriptor<?>) propertyDescriptor).getReferencedDescriptor()
.getComponentContract());
if (oldProperty != null) {
reversePropertyAccessor.setValue(oldProperty, null);
}
if (actualNewProperty != null) {
reversePropertyAccessor.setValue(actualNewProperty, proxy);
}
} else if (reversePropertyDescriptor instanceof ICollectionPropertyDescriptor) {
// It's a one-to-many relationship
ICollectionAccessor reversePropertyAccessor = accessorFactory
.createCollectionPropertyAccessor(reversePropertyDescriptor.getName(),
((IReferencePropertyDescriptor<?>) propertyDescriptor).getReferencedDescriptor()
.getComponentContract(),
((ICollectionPropertyDescriptor<?>) reversePropertyDescriptor).getCollectionDescriptor()
.getElementDescriptor()
.getComponentContract());
if (reversePropertyAccessor instanceof IModelDescriptorAware) {
((IModelDescriptorAware) reversePropertyAccessor)
.setModelDescriptor(reversePropertyDescriptor);
}
if (oldProperty != null) {
reversePropertyAccessor.removeFromValue(oldProperty, proxy);
}
if (actualNewProperty != null) {
reversePropertyAccessor.addToValue(actualNewProperty, proxy);
}
}
}
} else if (propertyDescriptor instanceof ICollectionPropertyDescriptor) {
Collection<?> oldCollectionSnapshot = CollectionHelper
.cloneCollection((Collection<?>) oldProperty);
// It's a 'many' relation end
Collection<Object> oldPropertyElementsToRemove = new THashSet<>(1);
Collection<Object> newPropertyElementsToAdd = new TLinkedHashSet<>(1);
Collection<Object> propertyElementsToKeep = new THashSet<>(1);
if (oldProperty != null) {
oldPropertyElementsToRemove.addAll((Collection<?>) oldProperty);
propertyElementsToKeep.addAll((Collection<?>) oldProperty);
}
if (actualNewProperty != null) {
newPropertyElementsToAdd.addAll((Collection<?>) actualNewProperty);
}
propertyElementsToKeep.retainAll(newPropertyElementsToAdd);
oldPropertyElementsToRemove.removeAll(propertyElementsToKeep);
newPropertyElementsToAdd.removeAll(propertyElementsToKeep);
ICollectionAccessor propertyAccessor = accessorFactory
.createCollectionPropertyAccessor(propertyName, componentDescriptor.getComponentContract(),
((ICollectionPropertyDescriptor<?>) propertyDescriptor).getCollectionDescriptor()
.getElementDescriptor().getComponentContract());
boolean oldCollectionSortEnabled = collectionSortEnabled;
boolean oldPropertyChangeEnabled = propertyChangeEnabled;
boolean oldPropertyProcessorsEnabled = propertyProcessorsEnabled;
try {
// Delay sorting for performance reasons.
collectionSortEnabled = false;
// Block property changes for performance reasons;
propertyChangeEnabled = false;
// Block property processors
propertyProcessorsEnabled = false;
for (Object element : oldPropertyElementsToRemove) {
propertyAccessor.removeFromValue(proxy, element);
}
for (Object element : newPropertyElementsToAdd) {
propertyAccessor.addToValue(proxy, element);
}
inlineComponentFactory.sortCollectionProperty((IComponent) proxy,
propertyName);
} finally {
collectionSortEnabled = oldCollectionSortEnabled;
propertyChangeEnabled = oldPropertyChangeEnabled;
propertyProcessorsEnabled = oldPropertyProcessorsEnabled;
}
// if the property is a list we may restore the element order and be
// careful not to miss one...
if (actualNewProperty instanceof List) {
Collection<Object> currentProperty = (Collection<Object>) oldProperty;
if (currentProperty instanceof List) {
// Just check that only order differs
Set<Object> temp = new THashSet<>(currentProperty);
temp.removeAll((List<?>) actualNewProperty);
if (currentProperty instanceof ICollectionWrapper) {
currentProperty = ((ICollectionWrapper) currentProperty).getWrappedCollection();
}
currentProperty.clear();
currentProperty.addAll((List<?>) actualNewProperty);
currentProperty.addAll(temp);
}
}
oldProperty = oldCollectionSnapshot;
}
} catch (RuntimeException ex) {
rollbackProperty(proxy, propertyDescriptor, oldProperty);
throw ex;
} catch (InvocationTargetException ex) {
rollbackProperty(proxy, propertyDescriptor, oldProperty);
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new ComponentException(ex.getCause());
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new ComponentException(ex);
}
} else {
storeProperty(propertyName, actualNewProperty);
}
doFirePropertyChange(proxy, propertyName, oldProperty, actualNewProperty);
if (propertyProcessorsEnabled) {
propertyDescriptor.postprocessSetter(proxy, oldProperty,
actualNewProperty);
}
}
private void straightSetProperties(Object proxy,
Map<String, Object> backendProperties) {
boolean eventsBlocked = blockEvents();
try {
for (Map.Entry<String, Object> propertyEntry : backendProperties
.entrySet()) {
straightSetProperty(proxy, propertyEntry.getKey(),
propertyEntry.getValue());
}
} finally {
if (eventsBlocked) {
releaseEvents();
}
}
}
private PropertyChangeListener createOrGetFakePcl() {
if (fakePcl == null) {
fakePcl = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
// It's fake
}
};
}
return fakePcl;
}
/**
* To string.
*
* @param proxy
* the proxy
* @return the to string
*/
protected String toString(Object proxy) {
try {
String toStringPropertyName = componentDescriptor.getToStringProperty();
Object toStringValue = accessorFactory.createPropertyAccessor(
toStringPropertyName, componentDescriptor.getComponentContract())
.getValue(proxy);
if (toStringValue == null) {
return "";
}
return toStringValue.toString();
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new ComponentException(ex);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new ComponentException(ex.getCause());
}
}
/**
* Gets component descriptor.
*
* @return the component descriptor
*/
protected IComponentDescriptor<? extends IComponent> getComponentDescriptor() {
return componentDescriptor;
}
/**
* Gets translated property value.
*
* @param proxy
* the proxy
* @param propertyDescriptor
* the property descriptor
* @param locale
* the locale
* @return the translated property value
*/
@SuppressWarnings("unchecked")
protected String getNlsPropertyValue(Object proxy, IStringPropertyDescriptor propertyDescriptor, Locale locale) {
if (locale != null) {
String barePropertyName = propertyDescriptor.getName();
if (barePropertyName.endsWith(IComponentDescriptor.NLS_SUFFIX)) {
barePropertyName = barePropertyName.substring(0,
barePropertyName.length() - IComponentDescriptor.NLS_SUFFIX.length());
}
String nlsPropertyName = barePropertyName + IComponentDescriptor.NLS_SUFFIX;
Set<IPropertyTranslation> translations = (Set<IPropertyTranslation>) straightGetProperty(proxy,
AbstractComponentDescriptor.getComponentTranslationsDescriptorTemplate().getName());
if (translations != null && isInitialized(translations)) {
String sessionLanguage = locale.getLanguage();
if (sessionLanguage != null) {
for (IPropertyTranslation translation : translations) {
if (sessionLanguage.equalsIgnoreCase(translation.getLanguage()) && barePropertyName.equals(
translation.getPropertyName())) {
return translation.getTranslatedValue();
}
}
}
return null;
}
return (String) straightGetProperty(proxy, nlsPropertyName);
}
return null;
}
/**
* Sets translated property value.
*
* @param proxy
* the proxy
* @param propertyDescriptor
* the property descriptor
* @param translatedValue
* the translated value
* @param entityFactory
* the entity factory
* @param locale
* the locale
*/
@SuppressWarnings("unchecked")
protected void setNlsPropertyValue(Object proxy, IStringPropertyDescriptor propertyDescriptor,
String translatedValue,
IEntityFactory entityFactory, Locale locale) {
if (locale != null) {
String actualTranslatedValue = (String) propertyDescriptor.interceptSetter(proxy, translatedValue);
// manually trigger interceptors
propertyDescriptor.preprocessSetter(proxy, translatedValue);
String barePropertyName = propertyDescriptor.getName();
if (barePropertyName.endsWith(IComponentDescriptor.NLS_SUFFIX)) {
barePropertyName = barePropertyName.substring(0,
barePropertyName.length() - IComponentDescriptor.NLS_SUFFIX.length());
}
String nlsPropertyName = barePropertyName + IComponentDescriptor.NLS_SUFFIX;
String oldTranslation = invokeNlsGetter(proxy, propertyDescriptor);
Set<IPropertyTranslation> translations;
String translationsPropertyName = AbstractComponentDescriptor.getComponentTranslationsDescriptorTemplate().getName();
Class<? extends IComponent> translationContract = ((ICollectionPropertyDescriptor<IComponent>)
getComponentDescriptor()
.getPropertyDescriptor(translationsPropertyName)).getReferencedDescriptor().getElementDescriptor()
.getComponentContract();
ICollectionAccessor translationsAccessor = getAccessorFactory().
createCollectionPropertyAccessor(translationsPropertyName, getComponentContract(), translationContract);
try {
translations = translationsAccessor.getValue(proxy);
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new ComponentException(ex);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new ComponentException(ex.getCause());
}
String sessionLanguage = locale.getLanguage();
IPropertyTranslation oldSessionTranslation = null;
if (sessionLanguage != null) {
for (IPropertyTranslation translation : translations) {
if (sessionLanguage.equalsIgnoreCase(translation.getLanguage()) && barePropertyName.equals(
translation.getPropertyName())) {
oldSessionTranslation = translation;
}
}
}
// Cannot simply update the old session translation or Hibernate will not manage persistence correctly.
if (oldSessionTranslation != null) {
try {
translationsAccessor.removeFromValue(proxy, oldSessionTranslation);
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new ComponentException(ex);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new ComponentException(ex.getCause());
}
}
IPropertyTranslation sessionTranslation = (IPropertyTranslation) entityFactory.createComponentInstance(
translationContract);
sessionTranslation.setLanguage(locale.getLanguage());
sessionTranslation.setPropertyName(barePropertyName);
sessionTranslation.setTranslatedValue(actualTranslatedValue);
try {
translationsAccessor.addToValue(proxy, sessionTranslation);
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new ComponentException(ex);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new ComponentException(ex.getCause());
}
firePropertyChange(proxy, nlsPropertyName, oldTranslation, actualTranslatedValue);
propertyDescriptor.postprocessSetter(proxy, oldTranslation, actualTranslatedValue);
}
}
private class NestedReferenceTracker implements PropertyChangeListener {
private final Object source;
private final String referencePropertyName;
private boolean enabled;
private boolean initialized;
private final boolean referencesInlinedComponent;
private final Set<String> trackedProperties;
/**
* Constructs a new {@code InlineReferenceTracker} instance.
*
* @param source
* the proxy holding the reference tracker.
* @param referencePropertyName
* the name of the component to track the properties.
* @param referencesInlineComponent
* is it tracking an inline component or an entity ref ?
*/
public NestedReferenceTracker(Object source, String referencePropertyName,
boolean referencesInlineComponent) {
this.source = source;
this.referencePropertyName = referencePropertyName;
this.referencesInlinedComponent = referencesInlineComponent;
this.enabled = true;
this.initialized = false;
this.trackedProperties = new THashSet<>(1);
}
/**
* Adds a property to the list of tracked nested properties for this nested
* reference tracker instance.
*
* @param nestedPropertyName
* the nested property name.
*/
public void addToTrackedProperties(String nestedPropertyName) {
trackedProperties.add(nestedPropertyName);
}
/**
* {@inheritDoc}
*/
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (enabled) {
try {
enabled = false;
String evtPropertyName = evt.getPropertyName();
Object evtOldValue = evt.getOldValue();
Object evtNewValue = evt.getNewValue();
if (source instanceof IEntity && referencesInlinedComponent) {
// for dirtiness notification.
// must check if the actual property change does not come from a
// nested entity. In that case, the persistent state has not
// changed.
boolean chainHasEntity = false;
String[] chain = evtPropertyName.split(
"\\" + IAccessor.NESTED_DELIM);
IComponent sourceComponent = (IComponent) evt.getSource();
if (chain.length > 1) {
StringBuilder chainPart = new StringBuilder();
for (int i = 0; i < chain.length - 1 && !chainHasEntity; i++) {
if (chainPart.length() > 0) {
chainPart.append(IAccessor.NESTED_DELIM);
}
chainPart.append(chain[i]);
if (sourceComponent
.straightGetProperty(chainPart.toString()) instanceof IEntity) {
chainHasEntity = true;
}
}
}
if (!chainHasEntity) {
IComponent oldComponentValue = getInlineComponentFactory().createComponentInstance(
sourceComponent.getComponentContract());
oldComponentValue.straightSetProperties(sourceComponent.straightGetProperties());
oldComponentValue.straightSetProperty(evtPropertyName, evtOldValue == IComponent.UNKNOWN ? null : evtOldValue);
doFirePropertyChange(source, referencePropertyName, oldComponentValue, evt.getSource());
}
}
// for ui notification
for (String trackedProperty : trackedProperties) {
if (trackedProperty.equals(evtPropertyName)) {
if (!(evtOldValue instanceof IComponent) // FAKE OLD COMPONENT VALUE
|| AbstractComponentInvocationHandler.this.isInitialized(evtOldValue)
|| ((IComponent) evtOldValue).getOwningComponent() == null) {
doFirePropertyChange(source, referencePropertyName + IAccessor.NESTED_DELIM + trackedProperty,
evtOldValue, evtNewValue);
}
} else if (trackedProperty.startsWith(evtPropertyName)) {
String remainderProperty = trackedProperty.substring(evtPropertyName.length() + 1);
if (remainderProperty.indexOf(IAccessor.NESTED_DELIM) >= 0) {
// If the remainder is a nested property, we have to take
// care of manually firing.
if (evtNewValue != null) {
try {
Object newValue = getAccessorFactory().createPropertyAccessor(remainderProperty, evtNewValue
.getClass()).getValue(evtNewValue);
doFirePropertyChange(source, referencePropertyName + IAccessor.NESTED_DELIM + trackedProperty,
IPropertyChangeCapable.UNKNOWN, newValue);
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new ComponentException(ex);
} catch (InvocationTargetException ex) {
if (ex.getTargetException() instanceof RuntimeException) {
throw (RuntimeException) ex.getTargetException();
}
throw new ComponentException(ex);
}
} else {
doFirePropertyChange(source, referencePropertyName + IAccessor.NESTED_DELIM + trackedProperty,
IPropertyChangeCapable.UNKNOWN, null);
}
}
}
}
} finally {
enabled = true;
}
}
}
/**
* Gets the initialized.
*
* @return the initialized.
*/
public boolean isInitialized() {
return initialized;
}
/**
* Sets the initialized.
*
* @param initialized
* the initialized to set.
*/
public void setInitialized(boolean initialized) {
this.initialized = initialized;
}
}
private static final class NeverEqualsInvocationHandler implements
InvocationHandler {
private final Object delegate;
private NeverEqualsInvocationHandler(Object delegate) {
this.delegate = delegate;
}
/**
* Just 'overrides' the equals method to always return false.
* <p/>
* {@inheritDoc}
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (method.getName().equals("equals") && args.length == 1) {
return false;
}
return method.invoke(delegate, args);
}
}
}