diff --git a/common/src/main/java/org/red5/server/net/rtmp/status/RuntimeStatusObject.java b/common/src/main/java/org/red5/server/net/rtmp/status/RuntimeStatusObject.java index d8e64130ab1506423d9863993264280512a389bf..17d3ec3ef32274a035f4b173049e1f711e0cd76c 100644 --- a/common/src/main/java/org/red5/server/net/rtmp/status/RuntimeStatusObject.java +++ b/common/src/main/java/org/red5/server/net/rtmp/status/RuntimeStatusObject.java @@ -7,20 +7,14 @@ package org.red5.server.net.rtmp.status; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; +import org.red5.io.amf3.IDataInput; +import org.red5.io.amf3.IDataOutput; /** * Runtime status object */ public class RuntimeStatusObject extends StatusObject { - /** - * Serializable - */ - private static final long serialVersionUID = 6990998992583246039L; - /** * Status event details */ @@ -109,14 +103,14 @@ public class RuntimeStatusObject extends StatusObject { } @Override - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + public void readExternal(IDataInput in) { super.readExternal(in); clientid = in.readInt(); details = (String) in.readObject(); } @Override - public void writeExternal(ObjectOutput out) throws IOException { + public void writeExternal(IDataOutput out) { super.writeExternal(out); out.writeInt(clientid); out.writeObject(details); diff --git a/common/src/main/java/org/red5/server/net/rtmp/status/Status.java b/common/src/main/java/org/red5/server/net/rtmp/status/Status.java index 575290f0e8a49046319f55811a46b363ecc3c017..e240db8d11c3b3f94b8c61fe80350d47ef01ddef 100644 --- a/common/src/main/java/org/red5/server/net/rtmp/status/Status.java +++ b/common/src/main/java/org/red5/server/net/rtmp/status/Status.java @@ -7,22 +7,16 @@ package org.red5.server.net.rtmp.status; -import java.io.Externalizable; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; - import org.red5.annotations.Anonymous; -import org.red5.io.object.ICustomSerializable; -import org.red5.io.object.Output; +import org.red5.io.amf3.IDataInput; +import org.red5.io.amf3.IDataOutput; +import org.red5.io.amf3.IExternalizable; /** * Represents status object that are transferred between server and client */ @Anonymous -public class Status implements StatusCodes, ICustomSerializable, Externalizable { - - private static final long serialVersionUID = -5501563718489586136L; +public class Status implements StatusCodes, IExternalizable { /** * Error constant @@ -206,36 +200,22 @@ public class Status implements StatusCodes, ICustomSerializable, Externalizable return "Status: code: " + getCode() + " desc: " + getDescription() + " level: " + getLevel(); } - public void serialize(Output output) { - output.putString("level"); - output.writeString(getLevel()); - output.putString("code"); - output.writeString(getCode()); - output.putString("description"); - output.writeString(getDescription()); - output.putString("details"); - if (getDetails() != null) { - output.writeString(getDetails()); - } else { - output.writeNull(); - } - output.putString("clientid"); - output.writeNumber(getClientid()); - } - - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + @Override + public void readExternal(IDataInput in) { clientid = (Number) in.readDouble(); - code = (String) in.readObject(); - description = (String) in.readObject(); - details = (String) in.readObject(); - level = (String) in.readObject(); + code = (String) in.readUTF(); + description = (String) in.readUTF(); + details = (String) in.readUTF(); + level = (String) in.readUTF(); } - public void writeExternal(ObjectOutput out) throws IOException { + @Override + public void writeExternal(IDataOutput out) { out.writeDouble(clientid.doubleValue()); - out.writeObject(code); - out.writeObject(description); - out.writeObject(details); - out.writeObject(level); + out.writeUTF(code); + out.writeUTF(description); + out.writeUTF(details); + out.writeUTF(level); } + } diff --git a/common/src/main/java/org/red5/server/net/rtmp/status/StatusObject.java b/common/src/main/java/org/red5/server/net/rtmp/status/StatusObject.java index bab8755c54dda13f3313f64d88530a6132ed232b..be75d993be4336795f9bfec9c0b13574203e2f58 100644 --- a/common/src/main/java/org/red5/server/net/rtmp/status/StatusObject.java +++ b/common/src/main/java/org/red5/server/net/rtmp/status/StatusObject.java @@ -7,18 +7,13 @@ package org.red5.server.net.rtmp.status; -import java.io.Externalizable; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; -import java.io.Serializable; import java.util.HashMap; import java.util.Map; import org.red5.annotations.Anonymous; -import org.red5.io.object.ICustomSerializable; -import org.red5.io.object.Output; -import org.red5.io.object.Serializer; +import org.red5.io.amf3.IDataInput; +import org.red5.io.amf3.IDataOutput; +import org.red5.io.amf3.IExternalizable; /** * Status object that is sent to client with every status event @@ -27,9 +22,7 @@ import org.red5.io.object.Serializer; * @author Luke Hubbard, Codegent Ltd (luke@codegent.com) */ @Anonymous -public class StatusObject implements Serializable, ICustomSerializable, Externalizable { - - private static final long serialVersionUID = 8817297676191096283L; +public class StatusObject implements IExternalizable { public static final String ERROR = "error"; @@ -139,10 +132,14 @@ public class StatusObject implements Serializable, ICustomSerializable, External return application; } - /** {@inheritDoc} */ - @Override - public String toString() { - return String.format("Status code: %s level: %s description: %s", code, level, description); + public void setAdditional(String name, Object value) { + if ("code,level,description,application".indexOf(name) != -1) { + throw new RuntimeException("the name \"" + name + "\" is reserved"); + } + if (additional == null) { + additional = new HashMap<>(); + } + additional.put(name, value); } /** @@ -154,54 +151,37 @@ public class StatusObject implements Serializable, ICustomSerializable, External return new Status(getCode(), getLevel(), getDescription()); } - public void setAdditional(String name, Object value) { - if ("code,level,description,application".indexOf(name) != -1) { - throw new RuntimeException("the name \"" + name + "\" is reserved"); - } - if (additional == null) { - additional = new HashMap<String, Object>(); - } - additional.put(name, value); - } - - public void serialize(Output output) { - output.putString("level"); - output.writeString(getLevel()); - output.putString("code"); - output.writeString(getCode()); - output.putString("description"); - output.writeString(getDescription()); - if (application != null) { - output.putString("application"); - Serializer.serialize(output, application); - } - if (additional != null) { - // Add additional parameters - for (Map.Entry<String, Object> entry : additional.entrySet()) { - output.putString(entry.getKey()); - Serializer.serialize(output, entry.getValue()); - } - } + /** {@inheritDoc} */ + @Override + public String toString() { + return String.format("Status code: %s level: %s description: %s", code, level, description); } @SuppressWarnings("unchecked") - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - code = (String) in.readObject(); - description = (String) in.readObject(); - level = (String) in.readObject(); - additional = (Map<String, Object>) in.readObject(); + @Override + public void readExternal(IDataInput in) { + code = (String) in.readUTF(); + description = (String) in.readUTF(); + level = (String) in.readUTF(); application = in.readObject(); + additional = (Map<String, Object>) in.readObject(); } - public void writeExternal(ObjectOutput out) throws IOException { - out.writeObject(code); - out.writeObject(description); - out.writeObject(level); + @Override + public void writeExternal(IDataOutput out) { + out.writeUTF(code); + out.writeUTF(description); + out.writeUTF(level); if (application != null) { - out.writeObject(additional); + out.writeObject(application); + } else { + out.writeObject(null); } if (additional != null) { - out.writeObject(application); + out.writeObject(additional); + } else { + out.writeObject(null); } } + } diff --git a/common/src/main/java/org/red5/server/net/rtmp/status/StatusObjectService.java b/common/src/main/java/org/red5/server/net/rtmp/status/StatusObjectService.java index a75e0af22f8b791604a984b7e8e043968d3aaf24..7d17d94fb3cc138a69dbef761985f1cfe4d3d318 100644 --- a/common/src/main/java/org/red5/server/net/rtmp/status/StatusObjectService.java +++ b/common/src/main/java/org/red5/server/net/rtmp/status/StatusObjectService.java @@ -46,17 +46,15 @@ public class StatusObjectService implements StatusCodes, InitializingBean { * Initialization */ public void afterPropertiesSet() throws Exception { - log.trace("Loading status objects"); - loadStatusObjects(); - log.trace("Caching status objects"); - cacheStatusObjects(); + log.trace("Loading and caching status objects"); + loadAndCacheStatusObjects(); log.debug("Status service ready"); } /** - * Creates all status objects and adds them to status objects map + * Creates all status objects and adds them to status objects map; then caches them. */ - public void loadStatusObjects() { + public void loadAndCacheStatusObjects() { statusObjects = new HashMap<>(); statusObjects.put(NC_CALL_FAILED, new StatusObject(NC_CALL_FAILED, StatusObject.ERROR, "")); @@ -103,23 +101,15 @@ public class StatusObjectService implements StatusCodes, InitializingBean { statusObjects.put(NS_PLAY_FILE_STRUCTURE_INVALID, new StatusObject(NS_PLAY_FILE_STRUCTURE_INVALID, StatusObject.ERROR, "")); statusObjects.put(NS_PLAY_NO_SUPPORTED_TRACK_FOUND, new StatusObject(NS_PLAY_NO_SUPPORTED_TRACK_FOUND, StatusObject.ERROR, "")); - } - - /** - * Cache status objects - */ - public void cacheStatusObjects() { - - cachedStatusObjects = new HashMap<String, byte[]>(); - + cachedStatusObjects = new HashMap<>(); String statusCode; IoBuffer out = IoBuffer.allocate(256); out.setAutoExpand(true); - for (String s : statusObjects.keySet()) { statusCode = s; StatusObject statusObject = statusObjects.get(statusCode); - if (statusObject instanceof RuntimeStatusObject) { + if (RuntimeStatusObject.class.isAssignableFrom(statusObject.getClass())) { + log.debug("Skip caching runtime status object: {}", statusCode); continue; } serializeStatusObject(out, statusObject); @@ -127,7 +117,7 @@ public class StatusObjectService implements StatusCodes, InitializingBean { if (log.isTraceEnabled()) { log.trace(HexDump.formatHexDump(out.getHexDump())); } - byte[] cachedBytes = new byte[out.limit()]; + byte[] cachedBytes = new byte[out.remaining()]; out.get(cachedBytes); out.clear(); cachedStatusObjects.put(statusCode, cachedBytes); diff --git a/common/src/main/java/org/red5/server/service/Call.java b/common/src/main/java/org/red5/server/service/Call.java index 234ea9f21bea7257d74c3f7895279dc112efb7ba..dedd5a1c96abc7634234b024919fd6f11ac69827 100644 --- a/common/src/main/java/org/red5/server/service/Call.java +++ b/common/src/main/java/org/red5/server/service/Call.java @@ -7,12 +7,12 @@ package org.red5.server.service; -import java.io.Externalizable; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; - +import org.red5.io.amf3.IDataInput; +import org.red5.io.amf3.IDataOutput; +import org.red5.io.amf3.IExternalizable; import org.red5.server.api.service.IServiceCall; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Basic service call (remote call) implementation @@ -20,9 +20,9 @@ import org.red5.server.api.service.IServiceCall; * @author The Red5 Project * @author Luke Hubbard, Codegent Ltd (luke@codegent.com) */ -public class Call implements IServiceCall, Externalizable { +public class Call implements IServiceCall, IExternalizable { - private static final long serialVersionUID = -3699712251588013875L; + private static final Logger log = LoggerFactory.getLogger(Call.class); /** * Pending status constant @@ -279,25 +279,37 @@ public class Call implements IServiceCall, Externalizable { return sb.toString(); } - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + @Override + public void readExternal(IDataInput in) { // keep a time of receipt readTime = System.currentTimeMillis(); // read-in properties - serviceName = (String) in.readObject(); - serviceMethodName = (String) in.readObject(); - arguments = (Object[]) in.readObject(); status = in.readByte(); + serviceName = (String) in.readUTF(); + serviceMethodName = (String) in.readUTF(); + arguments = (Object[]) in.readObject(); + if (log.isDebugEnabled()) { + for (int i = 0; i < arguments.length; i++) { + log.debug("ReadExt - Arg: {} type: {} => {}", i, (arguments[i] != null ? arguments[i].getClass().getName() : null), arguments[i]); + } + } exception = (Exception) in.readObject(); } - public void writeExternal(ObjectOutput out) throws IOException { + @Override + public void writeExternal(IDataOutput out) { // keep a time of receipt writeTime = System.currentTimeMillis(); // write-out properties - out.writeObject(serviceName); - out.writeObject(serviceMethodName); - out.writeObject(arguments); out.writeByte(status); + out.writeUTF(serviceName); + out.writeUTF(serviceMethodName); + if (log.isDebugEnabled()) { + for (int i = 0; i < arguments.length; i++) { + log.debug("WriteExt - Arg: {} type: {} => {}", i, (arguments[i] != null ? arguments[i].getClass().getName() : null), arguments[i]); + } + } + out.writeObject(arguments); out.writeObject(exception); } } diff --git a/common/src/main/java/org/red5/server/service/PendingCall.java b/common/src/main/java/org/red5/server/service/PendingCall.java index d0bb5fd63c954c0d508f7fd9a534329976ce78b4..c83078fe409642e3fbe7903d49e71b61a81dae39 100644 --- a/common/src/main/java/org/red5/server/service/PendingCall.java +++ b/common/src/main/java/org/red5/server/service/PendingCall.java @@ -7,12 +7,11 @@ package org.red5.server.service; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; import java.util.HashSet; import java.util.Set; +import org.red5.io.amf3.IDataInput; +import org.red5.io.amf3.IDataOutput; import org.red5.server.api.service.IPendingServiceCall; import org.red5.server.api.service.IPendingServiceCallback; @@ -21,8 +20,6 @@ import org.red5.server.api.service.IPendingServiceCallback; */ public class PendingCall extends Call implements IPendingServiceCall { - private static final long serialVersionUID = 3219267601240355335L; - /** * Result object */ @@ -105,13 +102,13 @@ public class PendingCall extends Call implements IPendingServiceCall { } @Override - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + public void readExternal(IDataInput in) { super.readExternal(in); result = in.readObject(); } @Override - public void writeExternal(ObjectOutput out) throws IOException { + public void writeExternal(IDataOutput out) { super.writeExternal(out); out.writeObject(result); } diff --git a/common/src/main/java/org/red5/server/service/ReflectionUtils.java b/common/src/main/java/org/red5/server/service/ReflectionUtils.java index d65c2eeb5fad6427b04bf50b2eff3107be071b9b..8733586939a19bb665d41e1ceccb62114f250902 100644 --- a/common/src/main/java/org/red5/server/service/ReflectionUtils.java +++ b/common/src/main/java/org/red5/server/service/ReflectionUtils.java @@ -45,22 +45,21 @@ public class ReflectionUtils { * @param args arguments * @return Method/params pairs or null if not found */ - public static Object[] findMethod(Object service, String methodName, List<?> args) { + public static Object[] findMethod(Object service, String methodName, List<?> listArgs) { if (isDebug) { - log.debug("Find method: {} in service: {} args: {}", methodName, service, args); + log.debug("Find method: {} in service: {} args: {}", methodName, service, listArgs); } // return value(s) Object[] methodResult = NULL_RETURN; // get all the name matched methods once, then filter out the ones that contain a $ - final Set<Method> methods = Arrays.stream(service.getClass().getMethods()).filter(m -> (m.getName().equals(methodName) && !m.getName().contains("$"))).filter(m -> m.getParameterCount() == 1 || m.getParameterCount() == args.size()).collect(Collectors.toUnmodifiableSet()); - if (methods.isEmpty()) { - // no methods found - } else { + final Set<Method> methods = Arrays.stream(service.getClass().getMethods()).filter(m -> (m.getName().equals(methodName) && !m.getName().contains("$"))).filter(m -> m.getParameterCount() == 1 || m.getParameterCount() == listArgs.size()).collect(Collectors.toUnmodifiableSet()); + if (!methods.isEmpty()) { if (isDebug) { log.debug("Named method(s) {}: {} found in {}", methods.size(), methodName, service); } + Object[] args = listArgs.toArray(); // convert the args to their class types - final Class<?>[] callParams = ConversionUtils.convertParams(args.toArray()); + final Class<?>[] callParams = ConversionUtils.convertParams(args); // search for method with matching parameters for (Method method : methods) { // track method parameters count @@ -69,32 +68,36 @@ public class ReflectionUtils { log.trace("Method {} count - parameters: {} args: {}", methodName, paramCount, callParams.length); } // if there are no args nor parameters - if ((args == null || args.isEmpty()) && paramCount == 0) { + if ((listArgs == null || listArgs.isEmpty()) && paramCount == 0) { + if (isTrace) { + log.trace("Method {} matched - zero-length", methodName); + } // fastest way to handle zero parameter methods - methodResult = new Object[] { method, args }; + methodResult = new Object[] { method, listArgs }; break; } // get the methods parameter types Class<?>[] paramTypes = method.getParameterTypes(); // search for method with List as the first and only parameter - if (paramCount == 1 && paramTypes[0].isAssignableFrom(List.class)) { - methodResult = new Object[] { method, List.of(args) }; + if (paramTypes[0].isAssignableFrom(List.class)) { + if (isTrace) { + log.trace("Method {} matched - parameter 0 is a list", methodName); + } + methodResult = new Object[] { method, listArgs }; break; } // search for method matching parameters without a forced connection parameter - if (callParams.length == paramCount) { - // search for method with exact parameters - boolean valid = true; - for (int j = 0; j < callParams.length; j++) { - // call params content can be null, param types cannot - if (!paramTypes[j].equals(callParams[j]) && !paramTypes[j].isAssignableFrom(callParams[j]) && callParams[j] != null) { - valid = false; - break; + if (paramCount == callParams.length) { + // attempt to convert the args to match the method + try { + Object[] convertedArgs = ConversionUtils.convertParams(args, paramTypes); + if (isTrace) { + log.trace("Method {} matched - parameters: {}", methodName, paramTypes); } - } - if (valid) { - methodResult = new Object[] { method, args }; + methodResult = new Object[] { method, convertedArgs }; break; + } catch (Exception e) { + log.warn("Method {} not found in {} with parameters {}", methodName, service, Arrays.asList(paramTypes), e); } } } @@ -118,7 +121,7 @@ public class ReflectionUtils { // get the arguments final Object[] args = call.getArguments(); // convert the args to their class types - final Class<?>[] callParams = ConversionUtils.convertParams(args); + Class<?>[] callParams = ConversionUtils.convertParams(args); // XXX(paul) someday this will be deprecated as its an extremely legacy feature to have a method with a // connection as the first parameter // build an array with the incoming args and the current connection as the first element @@ -128,10 +131,7 @@ public class ReflectionUtils { argsWithConnection[0] = conn; for (int i = 0; i < args.length; i++) { if (isDebug) { - log.debug("{} => {}", i, args[i]); - if (isTrace && args[i] != null) { - log.trace("Arg type: {}", args[i].getClass().getName()); - } + log.debug("Arg: {} type: {} => {}", i, (args[i] != null ? args[i].getClass().getName() : null), args[i]); } argsWithConnection[i + 1] = args[i]; } @@ -159,6 +159,9 @@ public class ReflectionUtils { } // if there are no args nor parameters if ((args == null || args.length == 0) && paramCount == 0) { + if (isTrace) { + log.trace("Method {} matched - zero-length", methodName); + } // fastest way to handle zero parameter methods methodResult = new Object[] { method, args }; break; @@ -167,59 +170,43 @@ public class ReflectionUtils { Class<?>[] paramTypes = method.getParameterTypes(); // search for method with Object[] as the first and only parameter if (paramCount == 1 && paramTypes[0].isArray()) { + if (isTrace) { + log.trace("Method {} matched - parameter 0 is an array", methodName); + } methodResult = new Object[] { method, args }; break; } - // search for method with List as the first and only parameter - if (paramCount == 1 && paramTypes[0].isAssignableFrom(List.class)) { - methodResult = new Object[] { method, List.of(args) }; - break; - } - // search for method with Set as the first and only parameter - if (paramCount == 1 && paramTypes[0].isAssignableFrom(Set.class)) { - methodResult = new Object[] { method, Set.of(args) }; - break; - } // search for method matching parameters without a forced connection parameter - if (callParams.length == paramCount) { - // search for method with exact parameters - boolean valid = true; - for (int j = 0; j < callParams.length; j++) { - // call params content can be null, param types cannot - if (!paramTypes[j].equals(callParams[j]) && !paramTypes[j].isAssignableFrom(callParams[j]) && args[j] != null) { - valid = false; - break; + if (paramCount == callParams.length && !paramTypes[0].isAssignableFrom(IConnection.class)) { + // attempt to convert the args to match the method + try { + Object[] convertedArgs = ConversionUtils.convertParams(args, paramTypes); + if (isTrace) { + log.trace("Method {} matched - parameters: {}", methodName, paramTypes); } - } - if (valid) { - methodResult = new Object[] { method, args }; + methodResult = new Object[] { method, convertedArgs }; break; + } catch (Exception e) { + log.warn("Method {} not found in {} with parameters {}", methodName, service, Arrays.asList(paramTypes), e); } } - // search for method with Object[] as the first and only parameter - if (paramTypes[0].isArray()) { - methodResult = new Object[] { method, args }; - break; - } // lastly try with connection at position 0 in parameters - if (conn != null) { - // search for method with exact parameters - boolean valid = true; - for (int j = 0; j < argsWithConnection.length; j++) { - // call params content can be null, param types cannot - if (!paramTypes[j].equals(argsWithConnection[j].getClass()) && !paramTypes[j].isAssignableFrom(argsWithConnection[j].getClass()) && argsWithConnection[j] != null) { - valid = false; - break; + if (conn != null && paramCount == (callParams.length + 1) && paramTypes[0].isAssignableFrom(IConnection.class)) { + // attempt to convert the args to match the method + try { + Object[] convertedArgs = ConversionUtils.convertParams(argsWithConnection, paramTypes); + if (isTrace) { + log.trace("Method {} matched - parameters: {}", methodName, paramTypes); } - } - if (valid) { - methodResult = new Object[] { method, args }; + methodResult = new Object[] { method, convertedArgs }; break; + } catch (Exception e) { + log.warn("Method {} not found in {} with parameters {}", methodName, service, Arrays.asList(paramTypes), e); } } } if (methodResult[0] == null) { - log.warn("Method {} not found in {} with parameters {}", methodName, service, Arrays.asList(args)); + log.warn("Method {} not found in {} with parameters {}", methodName, service, Arrays.asList(callParams)); call.setStatus(Call.STATUS_METHOD_NOT_FOUND); call.setException(new MethodNotFoundException(methodName, args)); } diff --git a/io/src/main/java/org/red5/io/amf/Input.java b/io/src/main/java/org/red5/io/amf/Input.java index e0b9bbe963a5900d72a2dc4fbf377d4dc4221178..9bc832642bb89d37aae670dcbccba83105e4ef13 100644 --- a/io/src/main/java/org/red5/io/amf/Input.java +++ b/io/src/main/java/org/red5/io/amf/Input.java @@ -92,7 +92,7 @@ public class Input extends BaseInput implements org.red5.io.object.Input { do { // get the data type currentDataType = buf.get(); - log.trace("Data type: {}", currentDataType); + log.trace("Data type {}: {}", currentDataType, DataTypes.toStringValue(currentDataType)); switch (currentDataType) { case AMF.TYPE_NULL: case AMF.TYPE_UNDEFINED: diff --git a/io/src/main/java/org/red5/io/amf/Output.java b/io/src/main/java/org/red5/io/amf/Output.java index 02844cee114238896dfb0c97b137806449c696a6..7850abe2616e5cc371d3791d6377be1b800b89de 100644 --- a/io/src/main/java/org/red5/io/amf/Output.java +++ b/io/src/main/java/org/red5/io/amf/Output.java @@ -12,6 +12,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.file.Paths; +import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -25,7 +26,6 @@ import org.apache.mina.core.buffer.IoBuffer; import org.red5.annotations.Anonymous; import org.red5.io.amf3.ByteArray; import org.red5.io.object.BaseOutput; -import org.red5.io.object.ICustomSerializable; import org.red5.io.object.RecordSet; import org.red5.io.object.Serializer; import org.red5.io.utils.XMLUtils; @@ -110,13 +110,14 @@ public class Output extends BaseOutput implements org.red5.io.object.Output { if (hasReference(obj)) { writeReference(obj); return true; - } else - return false; + } + return false; } /** {@inheritDoc} */ @Override public void writeArray(Collection<?> array) { + log.debug("writeArray - (collection source) array: {}", array); if (!checkWriteReference(array)) { storeReference(array); buf.put(AMF.TYPE_ARRAY); @@ -130,7 +131,7 @@ public class Output extends BaseOutput implements org.red5.io.object.Output { /** {@inheritDoc} */ @Override public void writeArray(Object[] array) { - log.debug("writeArray - array: {}", array); + log.debug("writeArray - (array source) array: {}", Arrays.asList(array)); if (array != null) { if (!checkWriteReference(array)) { storeReference(array); @@ -148,12 +149,14 @@ public class Output extends BaseOutput implements org.red5.io.object.Output { /** {@inheritDoc} */ @Override public void writeArray(Object array) { + log.debug("writeArray - (object source) array: {}", array); if (array != null) { if (!checkWriteReference(array)) { storeReference(array); buf.put(AMF.TYPE_ARRAY); - buf.putInt(Array.getLength(array)); - for (int i = 0; i < Array.getLength(array); i++) { + final int length = Array.getLength(array); + buf.putInt(length); + for (int i = 0; i < length; i++) { Serializer.serialize(this, Array.get(array, i)); } } @@ -307,11 +310,11 @@ public class Output extends BaseOutput implements org.red5.io.object.Output { } else { buf.put(AMF.TYPE_OBJECT); } - if (object instanceof ICustomSerializable) { - ((ICustomSerializable) object).serialize(this); - buf.put(AMF.END_OF_OBJECT_SEQUENCE); - return; - } + // if (object instanceof ICustomSerializable) { + // ((ICustomSerializable) object).serialize(this); + // buf.put(AMF.END_OF_OBJECT_SEQUENCE); + // return; + // } // Iterate thru entries and write out property names with separators for (Object key : attrs) { String fieldName = key.toString(); @@ -405,6 +408,7 @@ public class Output extends BaseOutput implements org.red5.io.object.Output { buf.put(AMF.TYPE_OBJECT); boolean isBeanMap = (map instanceof BeanMap); for (Map.Entry<Object, Object> entry : map.entrySet()) { + log.debug("Key: {} item: {}", entry.getKey(), entry.getValue()); if (isBeanMap && "class".equals(entry.getKey())) { continue; } diff --git a/io/src/main/java/org/red5/io/amf3/DataInput.java b/io/src/main/java/org/red5/io/amf3/DataInput.java index 1a3eaa366642369c64920a2cf5ca6321f9fcceb4..3f1933c32b01aa58cc05e1c691584d82a83a52a0 100644 --- a/io/src/main/java/org/red5/io/amf3/DataInput.java +++ b/io/src/main/java/org/red5/io/amf3/DataInput.java @@ -140,13 +140,13 @@ public class DataInput implements IDataInput { /** {@inheritDoc} */ @Override public int readUnsignedShort() { - return buffer.getShort() & 0xffff; //buffer.getUnsignedShort(); + return buffer.getShort() & 0xffff; } /** {@inheritDoc} */ @Override public String readUTF() { - int length = buffer.getShort() & 0xffff; //buffer.getUnsignedShort(); + int length = buffer.getShort() & 0xffff; return readUTFBytes(length); } diff --git a/io/src/main/java/org/red5/io/amf3/Input.java b/io/src/main/java/org/red5/io/amf3/Input.java index 5db3e6b3d417290851848a7b0b627c3badfdbb77..2e1bd6918c598e178ae171613c35d63a882db308 100644 --- a/io/src/main/java/org/red5/io/amf3/Input.java +++ b/io/src/main/java/org/red5/io/amf3/Input.java @@ -51,7 +51,7 @@ import org.w3c.dom.Document; * @author Luke Hubbard, Codegent Ltd (luke@codegent.com) * @author Joachim Bauch (jojo@struktur.de) */ -public class Input extends org.red5.io.amf.Input implements org.red5.io.object.Input { +public class Input extends org.red5.io.amf.Input { /** * Holds informations about already deserialized classes. @@ -513,6 +513,7 @@ public class Input extends org.red5.io.amf.Input implements org.red5.io.object.I collection = (Class) target; } if (collection.isArray()) { + log.debug("Using an array for key: {} count: {}", key, count); nested = ArrayUtils.getGenericType(collection.getComponentType()); result = Array.newInstance(nested, count); storeReference(result); @@ -526,6 +527,7 @@ public class Input extends org.red5.io.amf.Input implements org.red5.io.object.I } else if (Set.class.isAssignableFrom(collection)) { resultCollection = new HashSet(count); } else { + log.debug("Using an ArrayList for key: {} count: {}", key, count); resultCollection = new ArrayList(count); } result = resultCollection; diff --git a/io/src/main/java/org/red5/io/amf3/Output.java b/io/src/main/java/org/red5/io/amf3/Output.java index c9f8ae25c873a09823061891147c5eeebd431944..8d10c0bed89e932124350c49f4284c8a18c7eb76 100644 --- a/io/src/main/java/org/red5/io/amf3/Output.java +++ b/io/src/main/java/org/red5/io/amf3/Output.java @@ -44,7 +44,7 @@ import org.w3c.dom.Document; * @author Joachim Bauch (jojo@struktur.de) * @author Harald Radi (harald.radi@nme.at) */ -public class Output extends org.red5.io.amf.Output implements org.red5.io.object.Output { +public class Output extends org.red5.io.amf.Output { protected static Logger log = LoggerFactory.getLogger(Output.class); @@ -271,7 +271,6 @@ public class Output extends org.red5.io.amf.Output implements org.red5.io.object private void writePrimitiveByteArray(byte[] bytes) { writeAMF3(); this.buf.put(AMF3.TYPE_BYTEARRAY); - if (hasReference(bytes)) { putInteger(getReferenceId(bytes) << 1); return; @@ -279,7 +278,6 @@ public class Output extends org.red5.io.amf.Output implements org.red5.io.object storeReference(bytes); int length = bytes.length; putInteger(length << 1 | 0x1); - this.buf.put(bytes); } @@ -436,7 +434,8 @@ public class Output extends org.red5.io.amf.Output implements org.red5.io.object } log.debug("Storing object reference"); storeReference(object); - if (object instanceof IExternalizable) { + Class<?> objectClass = object.getClass(); + if (IExternalizable.class.isAssignableFrom(objectClass)) { log.debug("Object is IExternalizable"); // the object knows how to serialize itself int type = 1 << 1 | 1; @@ -446,7 +445,7 @@ public class Output extends org.red5.io.amf.Output implements org.red5.io.object type |= AMF3.TYPE_OBJECT_EXTERNALIZABLE << 2; } putInteger(type); - putString(Serializer.getClassName(object.getClass())); + putString(Serializer.getClassName(objectClass)); amf3_mode += 1; ((IExternalizable) object).writeExternal(new DataOutput(this)); amf3_mode -= 1; @@ -467,10 +466,9 @@ public class Output extends org.red5.io.amf.Output implements org.red5.io.object return; } // write out either start of object marker for class name or "empty" start of object marker - Class<?> objectClass = object.getClass(); if (!objectClass.isAnnotationPresent(Anonymous.class)) { log.debug("Object is annotated as Anonymous"); - putString(Serializer.getClassName(object.getClass())); + putString(Serializer.getClassName(objectClass)); } else { putString(""); } diff --git a/io/src/main/java/org/red5/io/object/Deserializer.java b/io/src/main/java/org/red5/io/object/Deserializer.java index 1481a045358b163acf217ee6c8dfd8252114b2d0..a9a870057ecdc110b236afe2ecb5272e92dfb9b9 100644 --- a/io/src/main/java/org/red5/io/object/Deserializer.java +++ b/io/src/main/java/org/red5/io/object/Deserializer.java @@ -69,9 +69,7 @@ public class Deserializer { } byte type = in.readDataType(); if (log.isTraceEnabled()) { - log.trace("Type: {} target: {}", type, (target != null ? target.toString() : "Target not specified")); - } else if (log.isDebugEnabled()) { - log.debug("Datatype: {}", DataTypes.toStringValue(type)); + log.trace("Type {}: {} target: {}", type, DataTypes.toStringValue(type), (target != null ? target.toString() : "Target not specified")); } Object result = null; switch (type) { diff --git a/io/src/main/java/org/red5/io/object/Serializer.java b/io/src/main/java/org/red5/io/object/Serializer.java index 4ffd346ac553d9b6d2739c922373151447741531..4843fadb474bc00fd16d701ec1f657a8f6d909c9 100644 --- a/io/src/main/java/org/red5/io/object/Serializer.java +++ b/io/src/main/java/org/red5/io/object/Serializer.java @@ -48,11 +48,11 @@ public class Serializer { * * @param out * Output writer - * @param any + * @param obj * Object to serialize */ - public static void serialize(Output out, Object any) { - Serializer.serialize(out, null, null, null, any); + public static void serialize(Output out, Object obj) { + Serializer.serialize(out, null, null, null, obj); } /** @@ -64,60 +64,85 @@ public class Serializer { * The field to serialize * @param getter * The getter method if not a field - * @param object + * @param parent * Parent object - * @param value + * @param obj * Object to serialize */ @SuppressWarnings("unchecked") - public static void serialize(Output out, Field field, Method getter, Object object, Object value) { - log.trace("serialize"); - if (value instanceof IExternalizable) { + public static void serialize(Output out, Field field, Method getter, Object parent, Object obj) { + log.trace("serialize: {}", obj); + if (obj == null) { + out.writeNull(); + } else if (IExternalizable.class.isAssignableFrom(obj.getClass())) { // make sure all IExternalizable objects are serialized as objects - out.writeObject(value); - } else if (value instanceof ByteArray) { + log.trace("write externalizable: {}", obj); + out.writeObject(obj); + } else if (obj instanceof ByteArray) { // write ByteArray objects directly - out.writeByteArray((ByteArray) value); - } else if (value instanceof Vector) { - log.trace("Serialize Vector"); - // scan the vector to determine the generic type - Vector<?> vector = (Vector<?>) value; - int ints = 0; - int longs = 0; - int dubs = 0; - int nans = 0; - for (Object o : vector) { - if (o instanceof Integer) { - ints++; - } else if (o instanceof Long) { - longs++; - } else if (o instanceof Number || o instanceof Double) { - dubs++; + out.writeByteArray((ByteArray) obj); + } else if (out.isCustom(obj)) { + log.trace("write custom: {}", obj); + // Write custom data + out.writeCustom(obj); + } else { + if (writeBasic(out, obj)) { + log.trace("Wrote as basic"); + } else if (obj.getClass().isArray() && obj.getClass().getComponentType().isPrimitive()) { + log.trace("write array: {}", obj); + out.writeArray(obj); + } else if (obj instanceof Object[]) { + log.trace("write object array: {}", obj); + out.writeArray((Object[]) obj); + } else if (obj instanceof Collection) { + log.trace("write collection"); + out.writeArray((Collection<Object>) obj); + } else if (obj instanceof List<?>) { + log.trace("write list"); + writeList(out, (List<?>) obj); + } else if (obj instanceof Document) { + writeDocument(out, (Document) obj); + } else if (obj instanceof Vector) { + log.trace("write vector"); + // scan the vector to determine the generic type + Vector<?> vector = (Vector<?>) obj; + int ints = 0; + int longs = 0; + int dubs = 0; + int nans = 0; + for (Object o : vector) { + if (o instanceof Integer) { + ints++; + } else if (o instanceof Long) { + longs++; + } else if (o instanceof Number || o instanceof Double) { + dubs++; + } else { + nans++; + } + } + // look at the type counts + if (nans > 0) { + // if we have non-number types, use object + ((org.red5.io.amf3.Output) out).enforceAMF3(); + out.writeVectorObject((Vector<Object>) obj); + } else if (dubs == 0 && longs == 0) { + // no doubles or longs + out.writeVectorInt((Vector<Integer>) obj); + } else if (dubs == 0 && ints == 0) { + // no doubles or ints + out.writeVectorUInt((Vector<Long>) obj); } else { - nans++; + // handle any other types of numbers + ((org.red5.io.amf3.Output) out).enforceAMF3(); + out.writeVectorNumber((Vector<Double>) obj); } - } - // look at the type counts - if (nans > 0) { - // if we have non-number types, use object - ((org.red5.io.amf3.Output) out).enforceAMF3(); - out.writeVectorObject((Vector<Object>) value); - } else if (dubs == 0 && longs == 0) { - // no doubles or longs - out.writeVectorInt((Vector<Integer>) value); - } else if (dubs == 0 && ints == 0) { - // no doubles or ints - out.writeVectorUInt((Vector<Long>) value); + } else if (obj instanceof Iterator) { + writeIterator(out, (Iterator<Object>) obj); + } else if (writeObjectType(out, obj)) { + log.trace("Wrote as object type"); } else { - // handle any other types of numbers - ((org.red5.io.amf3.Output) out).enforceAMF3(); - out.writeVectorNumber((Vector<Double>) value); - } - } else { - if (writeBasic(out, value)) { - log.trace("Wrote as basic"); - } else if (!writeComplex(out, value)) { - log.trace("Unable to serialize: {}", value); + log.trace("Unable to serialize: {}", obj); } } } @@ -151,32 +176,6 @@ public class Serializer { return true; } - /** - * Writes a complex type object out - * - * @param out - * Output writer - * @param complex - * Complex datatype object - * @return boolean true if object was successfully serialized, false otherwise - */ - public static boolean writeComplex(Output out, Object complex) { - log.trace("writeComplex"); - if (writeListType(out, complex)) { - return true; - } else if (writeArrayType(out, complex)) { - return true; - } else if (writeXMLType(out, complex)) { - return true; - } else if (writeCustomType(out, complex)) { - return true; - } else if (writeObjectType(out, complex)) { - return true; - } else { - return false; - } - } - /** * Writes Lists out as a data type * @@ -241,15 +240,15 @@ public class Serializer { */ @SuppressWarnings("all") protected static boolean writeArrayType(Output out, Object arrType) { - log.trace("writeArrayType"); - if (arrType instanceof Collection) { - out.writeArray((Collection<Object>) arrType); - } else if (arrType instanceof Iterator) { - writeIterator(out, (Iterator<Object>) arrType); - } else if (arrType.getClass().isArray() && arrType.getClass().getComponentType().isPrimitive()) { + log.trace("writeArrayType: {}", arrType); + if (arrType.getClass().isArray() && arrType.getClass().getComponentType().isPrimitive()) { out.writeArray(arrType); } else if (arrType instanceof Object[]) { out.writeArray((Object[]) arrType); + } else if (arrType instanceof Collection) { + out.writeArray((Collection<Object>) arrType); + } else if (arrType instanceof Iterator) { + writeIterator(out, (Iterator<Object>) arrType); } else { return false; } @@ -318,6 +317,7 @@ public class Serializer { */ @SuppressWarnings("all") protected static boolean writeObjectType(Output out, Object obj) { + log.trace("writeObjectType: {} {}", obj.getClass().getName(), obj); if (obj instanceof ObjectMap || obj instanceof BeanMap) { out.writeObject((Map) obj); } else if (obj instanceof Map) { @@ -330,25 +330,6 @@ public class Serializer { return true; } - /** - * Writes a custom data to the output - * - * @param out - * Output writer - * @param obj - * Custom data - * @return true if the object has been written, otherwise false - */ - protected static boolean writeCustomType(Output out, Object obj) { - if (out.isCustom(obj)) { - // Write custom data - out.writeCustom(obj); - return true; - } else { - return false; - } - } - /** * Checks whether the field should be serialized or not * diff --git a/io/src/main/java/org/red5/io/utils/ConversionUtils.java b/io/src/main/java/org/red5/io/utils/ConversionUtils.java index cec9178f8f0e427af2511a932921327f303db40d..6970568d824e4edf760f91f2acee252cacce3ecc 100644 --- a/io/src/main/java/org/red5/io/utils/ConversionUtils.java +++ b/io/src/main/java/org/red5/io/utils/ConversionUtils.java @@ -312,19 +312,14 @@ public class ConversionUtils { * @return Array of converted objects */ public static Class<?>[] convertParams(Object[] source) { - Class<?>[] converted = new Class<?>[0]; + Class<?>[] converted = source != null ? new Class<?>[source.length] : new Class<?>[0]; if (source != null) { - converted = new Class<?>[source.length]; for (int i = 0; i < source.length; i++) { - if (source[i] != null) { - converted[i] = source[i].getClass(); - } else { - converted[i] = null; - } + converted[i] = source[i] != null ? source[i].getClass() : null; + } + if (log.isTraceEnabled()) { + log.trace("Converted parameters: {}", Arrays.toString(converted)); } - } - if (log.isTraceEnabled()) { - log.trace("Converted parameters: {}", Arrays.toString(converted)); } return converted; } diff --git a/server/src/test/java/org/red5/io/utils/ConversionUtilsTest.java b/server/src/test/java/org/red5/io/utils/ConversionUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7c3674dd3e4073ec9068c44afef2c428f4b408f7 --- /dev/null +++ b/server/src/test/java/org/red5/io/utils/ConversionUtilsTest.java @@ -0,0 +1,182 @@ +/* + * RED5 Open Source Media Server - https://github.com/Red5/ Copyright 2006-2023 by respective authors (see below). All rights reserved. Licensed 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.red5.io.utils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import org.apache.commons.beanutils.ConversionException; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author The Red5 Project + * @author Luke Hubbard, Codegent Ltd (luke@codegent.com) + */ +public class ConversionUtilsTest { + + private static final Logger log = LoggerFactory.getLogger(ConversionUtilsTest.class); + + class TestJavaBean { + + } + + @Test + public void testBasic() { + Object result = ConversionUtils.convert(Integer.valueOf(42), String.class); + if (!(result instanceof String)) { + fail("Should be a string"); + } + String str = (String) result; + assertEquals("42", str); + } + + @Test + public void testConvertListToStringArray() { + ArrayList<String> source = new ArrayList<String>(); + + source.add("Testing 1"); + source.add("Testing 2"); + source.add("Testing 3"); + + Class<? extends String[]> target = (new String[0]).getClass(); + + Object result = ConversionUtils.convert(source, target); + if (!(result.getClass().isArray() && result.getClass().getComponentType().equals(String.class))) { + fail("Should be String[]"); + } + String[] results = (String[]) result; + + assertEquals(results.length, source.size()); + assertEquals(results[2], source.get(2)); + + } + + @Test + public void testConvertListToPrimitiveArray() { + List<Integer> source = new ArrayList<Integer>(); + source.add(1); + source.add(2); + source.add(3); + + Class<? extends int[]> target = (new int[0]).getClass(); + + Object result = ConversionUtils.convert(source, target); + if (!(result.getClass().isArray() && result.getClass().getComponentType().equals(int.class))) { + fail("Should be int[]"); + } + int[] results = (int[]) result; + + assertEquals(results.length, source.size()); + assertEquals(results[2], source.get(2).intValue()); + } + + @Test + public void testConvertObjectArrayToStringArray() { + Object[] source = new Object[3]; + + source[0] = Integer.valueOf(21); + source[1] = Boolean.FALSE; + source[2] = "Woot"; + + Class<? extends String[]> target = (new String[0]).getClass(); + + Object result = ConversionUtils.convert(source, target); + if (!(result.getClass().isArray() && result.getClass().getComponentType().equals(String.class))) { + fail("Should be String[]"); + } + String[] results = (String[]) result; + + assertEquals(results.length, source.length); + assertEquals(results[2], source[2]); + + } + + @Test + public void testConvertToSet() { + Object[] source = new Object[3]; + source[0] = Integer.valueOf(21); + source[1] = Boolean.FALSE; + source[2] = "Woot"; + Object result = ConversionUtils.convert(source, Set.class); + if (!(result instanceof Set<?>)) { + fail("Should be a set"); + } + Set<?> results = (Set<?>) result; + assertEquals(results.size(), source.length); + + } + + @Test + public void testConvertArrayListToSet() { + List<String> source = new ArrayList<String>(3); + source.add("a"); + source.add("b"); + source.add("c"); + Object result = ConversionUtils.convert(source, Set.class); + if (!(result instanceof Set<?>)) { + fail("Should be a set"); + } + Set<?> results = (Set<?>) result; + assertEquals(source.size(), results.size()); + } + + @Test + public void testNoOppConvert() { + TestJavaBean source = new TestJavaBean(); + Object result = ConversionUtils.convert(source, TestJavaBean.class); + assertEquals(result, source); + } + + @Test + public void testNullConvert() { + Object result = ConversionUtils.convert(null, TestJavaBean.class); + assertNull(result); + } + + @Test(expected = ConversionException.class) + public void testNullConvertNoClass() { + // should throw exception + ConversionUtils.convert(new TestJavaBean(), null); + } + + @Test + public void testObjectArray() { + Object[] args = new Object[] { new Object[] { "test", 42 }, new Object[] { "blllllaaahhh" } }; + // convert to object array (known / expected type) + Object result = ConversionUtils.convert(args, Object[].class); + if (!(result instanceof Object[])) { + fail("Should be a Object[]"); + } + Object[] ret = (Object[]) result; + assertTrue(Arrays.equals(args, ret)); + // convert to object array (unknown type) + Class<?>[] resultTypes = ConversionUtils.convertParams(args); + if (Object[].class.isAssignableFrom(resultTypes[0])) { + log.info("Result type: {}", resultTypes[0]); + assertTrue(true); + if (Object[].class.isAssignableFrom(resultTypes[1])) { + log.info("Result type 2: {}", resultTypes[1]); + assertTrue(true); + } else { + fail("Should be a Object[] for param 1"); + } + } else { + fail("Should be a Object[] for params"); + } + } + +} diff --git a/server/src/test/java/org/red5/server/service/ReflectionUtilsTest.java b/server/src/test/java/org/red5/server/service/ReflectionUtilsTest.java index 802351721fd763b5531ce3c04961d66b1f58006b..5f66ee3a6791622ca249d5985963371adcca4e6f 100644 --- a/server/src/test/java/org/red5/server/service/ReflectionUtilsTest.java +++ b/server/src/test/java/org/red5/server/service/ReflectionUtilsTest.java @@ -51,16 +51,6 @@ public class ReflectionUtilsTest { log.info("Result with no-args: {}", Arrays.asList(result)); } - // call with two parameters string and int - call = new PendingCall("TestService.doTestObjectArray", new Object[] { "test", 42 }); - methodName = "doTestObjectArray"; - result = ReflectionUtils.findMethod(conn, call, service, methodName); - if (result[0] == null) { - log.info("Result is null"); - fail("Result is null, method not found"); - } else { - log.info("Result with Object array of string and int: {}", Arrays.asList(result)); - } // call with two parameters string and int but expecting Connection as first parameter to hit on 2nd call methodName = "doTest"; call = new PendingCall("TestService.doTest", new Object[] { "test", 42 }); @@ -83,6 +73,28 @@ public class ReflectionUtilsTest { } else { log.info("Result with list: {}", Arrays.asList(result)); } + + // call with two parameters string and int + call = new PendingCall("TestService.doTestObjectArray", new Object[] { "test", 42 }); + methodName = "doTestObjectArray"; + result = ReflectionUtils.findMethod(conn, call, service, methodName); + if (result[0] == null) { + log.info("Result is null"); + fail("Result is null, method not found"); + } else { + log.info("Result with Object array of string and int: {}", Arrays.asList(result)); + } + + // call with two parameters string and int + call = new PendingCall("TestService.doTestObjectArray", new Object[] { new Object[] { "test", 42 }, new Object[] { "blllllaaahhh" } }); + methodName = "doTestObjectArray"; + result = ReflectionUtils.findMethod(conn, call, service, methodName); + if (result[0] == null) { + log.info("Result is null"); + fail("Result is null, method not found"); + } else { + log.info("Result with Object array of string and int: {}", Arrays.asList(result)); + } } private class DummyConnection extends RTMPMinaConnection { @@ -126,6 +138,11 @@ public class ReflectionUtilsTest { log.info("doTestObjectArray - Object array: {}", Arrays.asList(param)); } + // simple method generically taking an object array + public void doTestObjectArray(Object[] param0, Object[] param1) { + log.info("doTestObjectArray - Object array(s): {} {}", Arrays.asList(param0), Arrays.asList(param1)); + } + } }