blob: d0779be29df73dd14de227f12bfd9e21c4a805f3 [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.brooklyn.core.objs.proxy;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.collect.Sets;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.objs.BrooklynObject;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.mgmt.internal.EntityManagerInternal;
import org.apache.brooklyn.core.mgmt.internal.ManagementTransitionMode;
import org.apache.brooklyn.core.mgmt.rebind.RebindManagerImpl.RebindTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Set;
import java.util.WeakHashMap;
/**
* Methods lifted from EntityProxyImpl to support an AdjunctProxyImpl
*/
public abstract class AbstractBrooklynObjectProxyImpl<T extends BrooklynObject> implements java.lang.reflect.InvocationHandler {
// TODO Currently the proxy references the real entity and invokes methods on it directly.
// As we work on remoting/distribution, this will be replaced by RPC.
private static final Logger LOG = LoggerFactory.getLogger(AbstractBrooklynObjectProxyImpl.class);
String id;
protected T delegate;
protected Boolean isMaster;
private WeakHashMap<T,Void> temporaryProxies = new WeakHashMap<T, Void>();
private static final Set<MethodSignature> OBJECT_METHODS = Sets.newLinkedHashSet();
static {
for (Method m : Object.class.getMethods()) {
OBJECT_METHODS.add(new MethodSignature(m));
}
}
protected AbstractBrooklynObjectProxyImpl(T delegate) {
this.delegate = delegate;
}
protected abstract T getProxy(T instance, boolean requireNonProxy);
protected abstract void resetProxy(T instance, T preferredProxy);
/** invoked to specify that a different underlying delegate should be used,
* e.g. because we are switching copy impls or switching primary/copy*/
public synchronized void resetDelegate(T thisProxy, T preferredProxy, T newDelegate) {
if (LOG.isTraceEnabled()) {
LOG.trace("updating "+Integer.toHexString(System.identityHashCode(thisProxy))
+" to be the same as "+Integer.toHexString(System.identityHashCode(preferredProxy))
+" pointing at "+Integer.toHexString(System.identityHashCode(newDelegate))
+" ("+temporaryProxies.size()+" temporary proxies)");
}
T oldDelegate = delegate;
this.delegate = newDelegate;
this.isMaster = null;
if (id!=null && newDelegate!=null && !newDelegate.getId().equals(id)) {
LOG.warn("Change of ID when delegate "+delegate+" assigned to proxy for ID "+id+" (ignoring, but indicates something odd occurring)", new Throwable("source of ID change"));
}
if (newDelegate==oldDelegate)
return;
/* we have to make sure that any newly created proxy of the newDelegate
* which have leaked (eg by being set as a child) also get repointed to this new delegate */
if (oldDelegate!=null) {
T temporaryProxy = getProxy(oldDelegate, true);
if (temporaryProxy!=null) temporaryProxies.put(temporaryProxy, null);
resetProxy(oldDelegate, preferredProxy);
}
if (newDelegate!=null) {
T temporaryProxy = getProxy(newDelegate, true);
if (temporaryProxy!=null) temporaryProxies.put(temporaryProxy, null);
resetProxy(newDelegate, preferredProxy);
}
// update any proxies which might be in use
for (T tp: temporaryProxies.keySet()) {
if (tp==thisProxy || tp==preferredProxy) continue;
((AbstractBrooklynObjectProxyImpl)(Proxy.getInvocationHandler(tp))).resetDelegate(tp, preferredProxy, newDelegate);
}
}
@Override
public String toString() {
return delegate==null ? getClass().getName()+"["+getId()+"]" : delegate.toString();
}
protected boolean isMaster() {
if (isMaster!=null) return isMaster;
if (delegate==null) return false;
ManagementContext mgmt = ((EntityInternal)delegate).getManagementContext();
ManagementTransitionMode mode = ((EntityManagerInternal)mgmt.getEntityManager()).getLastManagementTransitionMode(delegate.getId());
Boolean ro = ((EntityInternal)delegate).getManagementSupport().isReadOnlyRaw();
if (mode==null || ro==null) {
// not configured yet
return false;
}
boolean isMasterX = !mode.isReadOnly();
if (isMasterX != !ro) {
LOG.warn("Inconsistent read-only state for "+delegate+" (possibly rebinding); "
+ "management thinks "+isMasterX+" but entity thinks "+!ro);
return false;
}
isMaster = isMasterX;
return isMasterX;
}
/** calls permitted even when in RO / non-master mode */
protected abstract boolean isPermittedReadOnlyMethod(MethodSignature sig);
@Override
public Object invoke(Object proxy, final Method m, final Object[] args) throws Throwable {
if (proxy == null) {
throw new IllegalArgumentException("Static methods not supported via proxy on entity "+delegate);
}
MethodSignature sig = new MethodSignature(m);
Object result;
if (delegate==null) {
// allow access to toString, hashcode, equals (for insertion in hash sets, logging of errors during serialization, etc)
if ("toString".equals(sig.name)) return toString();
if ("getId".equals(sig.name)) return getId();
if ("hashCode".equals(sig.name)) return hashCode();
if ("equals".equals(sig.name) && args.length==1) return equals(args[0]);
throw new NullPointerException("Access to proxy on "+toString()+" before initialized, method "+m+"; " +
"likely the target either is still being initialized or the target had an error when being created/rebinded");
} else if (OBJECT_METHODS.contains(sig)) {
result = m.invoke(delegate, args);
} else if (isPermittedReadOnlyMethod(sig)) {
result = m.invoke(delegate, args);
} else {
if (!isMaster()) {
if (isMaster==null || RebindTracker.isRebinding()) {
// rebinding or caller manipulating before management; permit all access
// (as of this writing, things seem to work fine without the isRebinding check;
// but including in it may allow us to tighten the methods in EntityTransientCopyInternal)
result = m.invoke(delegate, args);
} else {
throw new UnsupportedOperationException("Call to '"+sig+"' not permitted on read-only entity "+delegate);
}
} else {
result = invokeOther(m, args);
}
}
return result == delegate ? getProxy(delegate, false) : result;
}
protected Object invokeOther(Method m, Object[] args) throws IllegalAccessException, InvocationTargetException {
return m.invoke(delegate, args);
}
static class MethodSignature {
private final String name;
private final Class<?>[] parameterTypes;
MethodSignature(Method m) {
name = m.getName();
parameterTypes = m.getParameterTypes();
}
@Override
public int hashCode() {
return Objects.hashCode(name, Arrays.hashCode(parameterTypes));
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof MethodSignature)) return false;
MethodSignature o = (MethodSignature) obj;
return name.equals(o.name) && Arrays.equals(parameterTypes, o.parameterTypes);
}
@Override
public String toString() {
return name+Arrays.toString(parameterTypes);
}
}
@VisibleForTesting
public T getDelegate() {
return delegate;
}
@Override
public int hashCode() {
return delegate!=null ? delegate.hashCode() : id!=null ? id.hashCode() : -1;
}
@Override
public boolean equals(Object other) {
return other==this || (delegate!=null ? delegate.equals(other) : (other instanceof BrooklynObject && ((BrooklynObject)other).getId().equals(id)));
}
public String getId() {
return delegate!=null ? delegate.getId() : id;
}
}