/*
* 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.activemq.transport.amqp.client.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import org.apache.qpid.proton.engine.Connection;
import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.engine.Link;
import org.apache.qpid.proton.engine.Receiver;
import org.apache.qpid.proton.engine.Sasl;
import org.apache.qpid.proton.engine.Sender;
import org.apache.qpid.proton.engine.Session;
import org.apache.qpid.proton.engine.Transport;
/**
* Utility that creates proxy objects for the Proton objects which
* won't allow any mutating operations to be applied so that the test
* code does not interact with the proton engine outside the client
* serialization thread.
*/
public final class UnmodifiableProxy {
private static ArrayList<String> blacklist = new ArrayList<>();
// These methods are mutating but don't take an arguments so they
// aren't automatically filtered out. We will have to keep an eye
// on proton API in the future and modify this list as it evolves.
static {
blacklist.add("close");
blacklist.add("free");
blacklist.add("open");
blacklist.add("sasl");
blacklist.add("session");
blacklist.add("close_head");
blacklist.add("close_tail");
blacklist.add("outputConsumed");
blacklist.add("process");
blacklist.add("processInput");
blacklist.add("unbind");
blacklist.add("settle");
blacklist.add("clear");
blacklist.add("detach");
blacklist.add("abort");
}
private UnmodifiableProxy() {}
public static Transport transportProxy(final Transport target) {
Transport wrap = wrap(Transport.class, target);
return wrap;
}
public static Sasl saslProxy(final Sasl target) {
return wrap(Sasl.class, target);
}
public static Connection connectionProxy(final Connection target) {
return wrap(Connection.class, target);
}
public static Session sessionProxy(final Session target) {
return wrap(Session.class, target);
}
public static Delivery deliveryProxy(final Delivery target) {
return wrap(Delivery.class, target);
}
public static Link linkProxy(final Link target) {
return wrap(Link.class, target);
}
public static Receiver receiverProxy(final Receiver target) {
return wrap(Receiver.class, target);
}
public static Sender senderProxy(final Sender target) {
return wrap(Sender.class, target);
}
private static boolean isProtonType(Class<?> clazz) {
String packageName = clazz.getPackage().getName();
if (packageName.startsWith("org.apache.qpid.proton.")) {
return true;
}
return false;
}
private static <T> T wrap(Class<T> type, final Object target) {
return type.cast(java.lang.reflect.Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
if ("toString".equals(method.getName()) && method.getParameterTypes().length == 0) {
return "Unmodifiable proxy -> (" + method.invoke(target, objects) + ")";
}
// Don't let methods that mutate be invoked.
if (method.getParameterTypes().length > 0) {
throw new UnsupportedOperationException("Cannot mutate outside the Client work thread");
}
if (blacklist.contains(method.getName())) {
throw new UnsupportedOperationException("Cannot mutate outside the Client work thread");
}
Class<?> returnType = method.getReturnType();
try {
Object result = method.invoke(target, objects);
if (result == null) {
return null;
}
if (returnType.isPrimitive() || returnType.isArray() || Object.class.equals(returnType)) {
// Skip any other checks
} else if (returnType.isAssignableFrom(ByteBuffer.class)) {
// Buffers are modifiable but we can just return null to indicate
// there's nothing there to access.
result = null;
} else if (returnType.isAssignableFrom(Map.class)) {
// Prevent return of modifiable maps
result = Collections.unmodifiableMap((Map<?, ?>) result);
} else if (isProtonType(returnType) && returnType.isInterface()) {
// Can't handle the crazy Source / Target types yet as there's two
// different types for Source and Target the result can't be cast to
// the one people actually want to use.
if (!returnType.getName().equals("org.apache.qpid.proton.amqp.transport.Source") &&
!returnType.getName().equals("org.apache.qpid.proton.amqp.messaging.Source") &&
!returnType.getName().equals("org.apache.qpid.proton.amqp.transport.Target") &&
!returnType.getName().equals("org.apache.qpid.proton.amqp.messaging.Target")) {
result = wrap(returnType, result);
}
}
return result;
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
}));
}
}