/**
* Copyright (C) 2008 - Abiquo Holdings S.L. All rights reserved.
*
* Please see /opt/abiquo/tomcat/webapps/legal/ on Abiquo server
* or contact contact@abiquo.com for licensing information.
*/
package com.abiquo.hypervisor.plugin.internal;
import static com.abiquo.hypervisor.plugin.internal.function.Functions.METHOD_NAME;
import static com.google.common.base.Objects.firstNonNull;
import static com.google.common.collect.ImmutableSet.copyOf;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Lists.transform;
import static java.util.Arrays.asList;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.abiquo.hypervisor.plugin.Pluggable;
import com.abiquo.hypervisor.plugin.UnsupportedOperation;
import com.abiquo.hypervisor.plugin.exception.ComputeException;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
/**
* Handles automatically unsupported operations. Memoizes the operation.
*
* @author <a href="mailto:serafin.sedano@abiquo.com">Serafin Sedano</a>
*/
public class TryInvocationHandler implements InvocationHandler
{
private static final Logger LOGGER = LoggerFactory.getLogger(TryInvocationHandler.class);
private static final Set<String> ALL_METHODS = copyOf(transform(
asList(Plugin.class.getMethods()), METHOD_NAME));
private class UnsupportedOp
{
private final Set<String> types;
private final Set<String> regions;
UnsupportedOp(final String[] types, final String[] regions)
{
this.types = copyOf(firstNonNull(types, new String[0]));
this.regions = copyOf(firstNonNull(regions, new String[0]));
}
}
private final Map<String, UnsupportedOp> UNSUPPORTED_OPERATIONS;
private final Pluggable target;
private final String type;
public TryInvocationHandler(final Pluggable target, final String type)
{
this.target = target;
this.type = type; // Plugin manager rejects empty types
UNSUPPORTED_OPERATIONS = operationsUnsupported(target);
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws UnsupportedOperationException, ComputeException
{
if (target == null)
{
LOGGER.warn("checkOperationSupported: method {} unsupported for type {}", new Object[] {
method.getName(), type});
return false;
}
UnsupportedOp supported = UNSUPPORTED_OPERATIONS.get(method.getName());
if (supported == null)
{
return true;
}
if (supported.types.isEmpty() && supported.regions.isEmpty())
{
return false;
}
if (supported.types.contains(type))
{
return false;
}
if (args != null && args.length == 1)
{
if (args[0] instanceof String)
{
String region = (String) args[0];
if (supported.regions.contains(region))
{
return false;
}
}
}
return true;
}
/** If method is not implemented or annotated with UnsupportedOperation is not added in the set. */
private Map<String, UnsupportedOp> operationsUnsupported(final Pluggable plugin)
{
Builder<String, UnsupportedOp> builder = ImmutableMap.<String, UnsupportedOp> builder();
if (target != null)
{
addUnsupported(plugin, builder);
addNotImplemented(plugin, builder);
}
return builder.build();
}
private void addUnsupported(final Pluggable plugin, final Builder<String, UnsupportedOp> builder)
{
for (Method m : plugin.getClass().getMethods())
{
if (!m.isSynthetic() && !m.isBridge())
{
UnsupportedOperation uns = m.getAnnotation(UnsupportedOperation.class);
if (uns != null)
{
builder.put(m.getName(), new UnsupportedOp(uns.types(), uns.regions()));
}
}
}
}
/**
* All methods present in {@link Plugin} that are not present in <code>plugin</code> are added
* as unsupported.
*/
private void addNotImplemented(final Pluggable plugin,
final Builder<String, UnsupportedOp> builder)
{
Iterable<String> notImplemented =
filter(ALL_METHODS, Predicates.not(Predicates.in(transform(asList(plugin.getClass()
.getMethods()), METHOD_NAME))));
for (String method : notImplemented)
{
builder.put(method, new UnsupportedOp(null, null));
}
}
}