
org.jinterop.dcom.core.JILocalCoClass Maven / Gradle / Ivy
/** j-Interop (Pure Java implementation of DCOM protocol)
* Copyright (C) 2006 Vikram Roopchand
*
* This library 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.0 of the License, or (at your option) any later version.
*
* Though a sincere effort has been made to deliver a professional,
* quality product,the library itself is distributed WITHOUT ANY WARRANTY;
* 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 this library; if not, write to the Free Software
* Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
*/
package org.jinterop.dcom.core;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import ndr.NetworkDataRepresentation;
import org.jinterop.dcom.common.JIErrorCodes;
import org.jinterop.dcom.common.JIException;
import org.jinterop.dcom.common.JISystem;
import org.jinterop.dcom.impls.JIObjectFactory;
import rpc.core.UUID;
/**
*
* Represents a Java COCLASS
.
*
* Please refer to MSInternetExplorer, Test_ITestServer2_Impl,
* SampleTestServer and MSShell examples for more details on how to use this
* class.
*
* @since 2.0 (formerly JIJavaCoClass)
*
*/
public final class JILocalCoClass implements Serializable {
private static final Logger LOGGER = Logger.getLogger("org.jinterop");
private static final long serialVersionUID = 5542223845228327383L;
private static final String IID_IDISPATCH = "00020400-0000-0000-c000-000000000046";
private static Random randomGen = new Random(Double.doubleToRawLongBits(Math.random()));
private final int identifier;
private WeakReference interfacePointer = null;
private boolean isAlreadyExported = false;
private byte[] objectID = null;
private JILocalInterfaceDefinition interfaceDefinition = null;
private final List listOfSupportedInterfaces = new ArrayList<>();
private final Map mapOfIIDsToInterfaceDefinitions = new HashMap<>();
private JISession session = null;
private boolean realIID = false;
private final Map ipidVsIID = new HashMap<>();// will use this to identify which IID is being talked about
//if it is IDispatch then delegate to it's invoke.
private final Map IIDvsIpid = new HashMap<>();// will use this to identify which IPID is being talked about
private void init(JILocalInterfaceDefinition interfaceDefinition, Class clazz, Object instance, boolean realIID) {
listOfSupportedInterfaces.add(IID_IDISPATCH.toUpperCase()); //IDispatch
listOfSupportedInterfaces.add("00000131-0000-0000-C000-000000000046"); //IRemUnknown
this.interfaceDefinition = interfaceDefinition;
interfaceDefinition.clazz = clazz;
interfaceDefinition.instance = instance;
listOfSupportedInterfaces.add(interfaceDefinition.getInterfaceIdentifier().toUpperCase());
mapOfIIDsToInterfaceDefinitions.put(interfaceDefinition.getInterfaceIdentifier().toUpperCase(), interfaceDefinition);
this.realIID = realIID;
}
/**
* Creates a local class instance. The framework will try to create a
* instance of the clazz
using Class.newInstance
.
* Make sure that clazz
has a visible null
* constructor.
*
* @param interfaceDefinition implementing structurally the definition of
* the COM callback interface.
* @param clazz class
to instantiate for serving requests from
* COM client. Must implement the interfaceDefinition
fully.
* @throws IllegalArgumentException if interfaceDefinition
or
* clazz
are null
.
*/
public JILocalCoClass(JILocalInterfaceDefinition interfaceDefinition, Class clazz) {
if (interfaceDefinition == null || clazz == null) {
throw new IllegalArgumentException(JISystem.getLocalizedMessage(JIErrorCodes.JI_COM_RUNTIME_INVALID_CONTAINER_INFO));
}
this.identifier = clazz.hashCode() ^ new Object().hashCode() ^ randomGen.nextInt();
init(interfaceDefinition, clazz, null, false);
}
/**
* Refer {@link #JILocalCoClass(JILocalInterfaceDefinition, Class)}.
*
* @param interfaceDefinition implementing structurally the definition of
* the COM callback interface.
* @param clazz class
to instantiate for serving requests from
* COM client. Must implement the interfaceDefinition
fully.
* @param useInterfaceDefinitionIID true
if the
* IID
of interfaceDefinition
* should be used as to create the local COM Object. Use this when a reference other than IUnknown*
is required.
* For all {@link JIObjectFactory#attachEventHandler(IJIComObject, String,
* IJIComObject)} operations this should be set to false
since
* the IConnectionPoint::Advise
method takes in a
* IUnknown*
reference.
* @throws IllegalArgumentException if interfaceDefinition
or
* clazz
are null
.
*/
public JILocalCoClass(JILocalInterfaceDefinition interfaceDefinition, Class clazz, boolean useInterfaceDefinitionIID) {
if (interfaceDefinition == null || clazz == null) {
throw new IllegalArgumentException(JISystem.getLocalizedMessage(JIErrorCodes.JI_COM_RUNTIME_INVALID_CONTAINER_INFO));
}
this.identifier = clazz.hashCode() ^ new Object().hashCode() ^ randomGen.nextInt();
init(interfaceDefinition, clazz, null, useInterfaceDefinitionIID);
}
/**
* Creates a local class instance.
*
* @param interfaceDefinition implementing structurally the definition of
* the COM callback interface.
* @param instance instance for serving requests from COM client. Must
* implement the interfaceDefinition
fully.
* @throws IllegalArgumentException if interfaceDefinition
or
* instance
are null
.
*/
public JILocalCoClass(JILocalInterfaceDefinition interfaceDefinition, Object instance) {
if (interfaceDefinition == null || instance == null) {
throw new IllegalArgumentException(JISystem.getLocalizedMessage(JIErrorCodes.JI_COM_RUNTIME_INVALID_CONTAINER_INFO));
}
this.identifier = instance.hashCode() ^ new Object().hashCode() ^ randomGen.nextInt();
init(interfaceDefinition, null, instance, false);
}
/**
* Creates a local class instance.
*
* @param interfaceDefinition implementing structurally the definition of
* the COM callback interface.
* @param instance instance for serving requests from COM client. Must
* implement the interfaceDefinition
fully.
* @param useInterfaceDefinitionIID true
if the
* IID
of interfaceDefinition
* should be used as to create the local COM Object. Use this when a reference other than IUnknown*
is required.
* For all {@link JIObjectFactory#attachEventHandler(IJIComObject, String,
* IJIComObject)} operations this should be set to false
since
* the IConnectionPoint::Advise
method takes in a
* IUnknown*
reference.
* @throws IllegalArgumentException if interfaceDefinition
or
* instance
are null
.
*/
public JILocalCoClass(JILocalInterfaceDefinition interfaceDefinition, Object instance, boolean useInterfaceDefinitionIID) {
if (interfaceDefinition == null || instance == null) {
throw new IllegalArgumentException(JISystem.getLocalizedMessage(JIErrorCodes.JI_COM_RUNTIME_INVALID_CONTAINER_INFO));
}
this.identifier = instance.hashCode() ^ new Object().hashCode() ^ randomGen.nextInt();
init(interfaceDefinition, null, instance, useInterfaceDefinitionIID);
}
/**
* Sets the interface identifiers (IID
s) of the event
* interfaces this class would support. This in case the same
* clazz
or instance
is implementing more than one
* IID
.
*
* @param listOfIIDs
* @see #JILocalCoClass(JILocalInterfaceDefinition, Class)
* @see #JILocalCoClass(JILocalInterfaceDefinition, Object)
*/
public void setSupportedEventInterfaces(List listOfIIDs) {
if (listOfIIDs != null) {
for (int i = 0; i < listOfIIDs.size(); i++) {
String s = listOfIIDs.get(i).toUpperCase();
listOfSupportedInterfaces.add(s);
mapOfIIDsToInterfaceDefinitions.put(s, interfaceDefinition);
}
}
}
/**
* Add another interface definition and it's supporting object instance.
*
* @param interfaceDefinition implementing structurally the definition of
* the COM callback interface.
* @param instance instance for serving requests from COM client. Must
* implement the interfaceDefinition
fully.
* @throws IllegalArgumentException if interfaceDefinition
or
* instance
are null
.
*/
public void addInterfaceDefinition(JILocalInterfaceDefinition interfaceDefinition, Object instance) {
if (interfaceDefinition == null || instance == null) {
throw new IllegalArgumentException(JISystem.getLocalizedMessage(JIErrorCodes.JI_COM_RUNTIME_INVALID_CONTAINER_INFO));
}
interfaceDefinition.instance = instance;
String s = interfaceDefinition.getInterfaceIdentifier().toUpperCase();
listOfSupportedInterfaces.add(s);
mapOfIIDsToInterfaceDefinitions.put(s, interfaceDefinition);
}
/**
* Add another interface definition and it's class. Make sure that this
* class has a default constructor, so that instantiation using
* reflection can take place.
*
* @param interfaceDefinition implementing structurally the definition of
* the COM callback interface.
* @param clazz instance for serving requests from COM client. Must
* implement the interfaceDefinition
fully.
* @throws IllegalArgumentException if interfaceDefinition
or
* clazz
are null
.
*/
public void addInterfaceDefinition(JILocalInterfaceDefinition interfaceDefinition, Class clazz) {
if (interfaceDefinition == null || clazz == null) {
throw new IllegalArgumentException(JISystem.getLocalizedMessage(JIErrorCodes.JI_COM_RUNTIME_INVALID_CONTAINER_INFO));
}
interfaceDefinition.clazz = clazz;
String s = interfaceDefinition.getInterfaceIdentifier().toUpperCase();
listOfSupportedInterfaces.add(s);
mapOfIIDsToInterfaceDefinitions.put(s, interfaceDefinition);
}
/**
* Returns the instance representing the interface definition.
*
* @return
* @see #JILocalCoClass(JILocalInterfaceDefinition, Object)
*/
public Object getServerInstance() {
return interfaceDefinition.instance;
}
/**
* Returns the actual class representing the interface definition.
*
* @return
* @see #JILocalCoClass(JILocalInterfaceDefinition, Class)
*/
public Class getServerClass() {
return interfaceDefinition.clazz;
}
//called from com runtime.
/**
* @exclude
*/
void setObjectId(byte[] objectId) {
this.objectID = objectId;
}
/**
* @exclude
*/
void setAssociatedInterfacePointer(JIInterfacePointer interfacePointer) {
isAlreadyExported = true;
this.interfacePointer = new WeakReference<>(interfacePointer);
String ipid = interfacePointer.getIPID().toUpperCase();
String iid = interfacePointer.getIID().toUpperCase();
IIDvsIpid.put(iid, ipid);
ipidVsIID.put(ipid, iid);
}
/**
*
* @exclude
*/
boolean isAssociatedReferenceAlive() {
return interfacePointer == null ? false : (interfacePointer.get() != null);
}
boolean isAlreadyExported() {
return isAlreadyExported;
}
/**
* @exclude
*/
byte[] getObjectId() {
return objectID;
}
/**
* @exclude @param iid
* @return
*/
boolean isPresent(String iid) {
return listOfSupportedInterfaces.contains(iid.toUpperCase());
}
/**
* @exclude @param uniqueIID
* @param IPID
* @throws InstantiationException
* @throws IllegalAccessException
*/
//advances the index...it cannot be reversed.
synchronized boolean exportInstance(String uniqueIID, String IPID) throws InstantiationException, IllegalAccessException {
//Object retval = null;
IPID = IPID.toUpperCase();
if (!isPresent(uniqueIID)) {//not supported IID.
return false;
}
IIDvsIpid.put(uniqueIID.toUpperCase(), IPID);
ipidVsIID.put(IPID, uniqueIID.toUpperCase());
return true;
}
/**
* Returns the interface identifier of this COCLASS.
*
* @return
* @see #JILocalCoClass(JILocalInterfaceDefinition, Class)
* @see #JILocalCoClass(JILocalInterfaceDefinition, Object)
* @see JILocalInterfaceDefinition#getInterfaceIdentifier()
*/
public String getCoClassIID() {
return interfaceDefinition.getInterfaceIdentifier();
}
/**
* @exclude @param IPID
* @param Opnum
* @param inparams
* @return
* @throws JIException
*/
//This will invoke the API via reflection and return the results of the call back to the
//actual COM object. This API is to be invoked via the RemUnknown Object
Object[] invokeMethod(String IPID, int Opnum, NetworkDataRepresentation ndr) throws JIException {
IPID = IPID.toUpperCase();
//somehow identify the method from the Opnum
//this will come from the IDL.
Object retVal = null;//will be an array.
String iid = ipidVsIID.get(IPID);
if (iid == null) {
throw new JIException(JIErrorCodes.RPC_E_INVALID_OBJECT);
}
JILocalInterfaceDefinition interfaceDefinitionOfClass = mapOfIIDsToInterfaceDefinitions.get(iid);
interfaceDefinitionOfClass = interfaceDefinitionOfClass == null ? interfaceDefinition : interfaceDefinitionOfClass;
JILocalMethodDescriptor methodDescriptor = null;
boolean execute = false;
Object[] params = null;
//that means the calls will come as IUnknown + IDispatch op numbers...0,1,2 & 3,4,5,6
//from 7th (inclusive) onwards are the actual COM servers calls
//now check for dispinterface and take a call...
//if dispinterface is supported then all calls will come with base of 6 {0,1,2 & 3,4,5,6}
//i.e 6th will be invoke and 7th(inclusive) onwards will be standard api calls.
//if not supported than it will be base 2 {0,1,2} i.e real method calls will start from 3(inclusive) onwards.
boolean isStandardCall = true;
if (interfaceDefinition.isDispInterface()) {
isStandardCall = false;
switch (Opnum) {
case 3: //getTypeInfoCount
//not supported
retVal = new Object[1];
((Object[]) retVal)[0] = 0; //not supported
break;
case 4: //getTypeInfo
throw new JIException(JIErrorCodes.E_NOTIMPL);
case 5: //getIDOfNames
JILocalParamsDescriptor paramObject = new JILocalParamsDescriptor();
paramObject.addInParamAsType(UUID.class, JIFlags.FLAG_NULL);
paramObject.addInParamAsObject(new JIArray(new JIString(JIFlags.FLAG_REPRESENTATION_STRING_LPWSTR), null, 1, true), JIFlags.FLAG_NULL);
paramObject.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
paramObject.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
//now read and then send the result back.
JIArray array = (JIArray) paramObject.read(ndr)[1];
Object[] arrayObj = (Object[]) array.getArrayInstance();
Integer[] dispIds = new Integer[arrayObj.length];
//get the first member of the Array , which is the APINAME and send the retVal with it's dispId
JIString apiName = (JIString) arrayObj[0];
JILocalMethodDescriptor info = interfaceDefinitionOfClass.getMethodDescriptor(apiName.getString());
if (info == null) {
dispIds[0] = JIErrorCodes.DISP_E_UNKNOWNNAME;
} else {
dispIds[0] = info.getMethodNum();
}
//rest are all 0,1,2...parameters
for (int i = 1; i < arrayObj.length; i++) {
dispIds[i] = i - 1;
}
JIArray results = new JIArray(dispIds);
retVal = new Object[1];
((Object[]) retVal)[0] = results;
break;
case 6: //invoke of IDispatch
paramObject = new JILocalParamsDescriptor();
paramObject.setSession(session);
paramObject.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
paramObject.addInParamAsType(UUID.class, JIFlags.FLAG_NULL);
paramObject.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
paramObject.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
JIStruct dispParams = new JIStruct();
dispParams.addMember(new JIPointer(new JIArray(JIVariant.class, null, 1, true)));
dispParams.addMember(new JIPointer(new JIArray(Integer.class, null, 1, true)));
dispParams.addMember(Integer.class);
dispParams.addMember(Integer.class);
paramObject.addInParamAsObject(dispParams, JIFlags.FLAG_REPRESENTATION_IDISPATCH_INVOKE);
paramObject.addInParamAsType(Integer.class, JIFlags.FLAG_NULL);
paramObject.addInParamAsObject(new JIArray(Integer.class, null, 1, true), JIFlags.FLAG_NULL);
paramObject.addInParamAsObject(new JIArray(JIVariant.class, null, 1, true), JIFlags.FLAG_NULL);
Object[] retresults = paramObject.read(ndr);
//named params not supported
int dispId = ((Number) retresults[0]).intValue();
info = interfaceDefinitionOfClass.getMethodDescriptorForDispId(dispId);
if (info == null) {
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.log(Level.SEVERE, "MethodDescriptor not found for DispId :- {0}", dispId);
}
throw new JIException(JIErrorCodes.DISP_E_MEMBERNOTFOUND);
}
dispParams = (JIStruct) retresults[4];
JIPointer ptrToParamsArray = (JIPointer) dispParams.getMember(0);
params = new Object[0];
if (!ptrToParamsArray.isNull()) {
//form the real array
array = (JIArray) ptrToParamsArray.getReferent();
Object[] variants = (Object[]) array.getArrayInstance();
params = new Object[variants.length];
for (int i = 0; i < variants.length; i++) {
params[i] = ((JIVariant) variants[i]).getObject();
}
}
if (((Number) retresults[5]).intValue() != 0) {
//now replace the params at index from the index array.
array = (JIArray) retresults[6];
Integer[] indexs = (Integer[]) array.getArrayInstance();
array = (JIArray) retresults[7];
JIVariant[] variants = (JIVariant[]) array.getArrayInstance();
for (int i = 0; i < indexs.length; i++) {
params[indexs[i]] = variants[i];
}
}
//now to reverse this array of params.
int halflength = params.length / 2;
for (int i = 0; i < halflength; i++) {
Object t = params[i];
params[i] = params[params.length - 1 - i];
params[params.length - 1 - i] = t;
}
methodDescriptor = info;
execute = true;
break;
default: //others are normal API calls ...Opnum - 6 is there real Opnum. 0,1,2 and 3,4,5,6
isStandardCall = true;
Opnum -= 4; //adjust for only IDispatch(3,4,5,6) , IUnknown(0,1,2) will get adjusted below.
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.log(Level.INFO, "Standard call came: Opnum is {0}", Opnum);
}
}
}
if (isStandardCall) {
methodDescriptor = interfaceDefinitionOfClass.getMethodDescriptor(Opnum - 3); //adjust for IUnknown
if (methodDescriptor == null) {
throw new JIException(JIErrorCodes.RPC_S_PROCNUM_OUT_OF_RANGE);
}
methodDescriptor.getParameterObject().setSession(session);
params = methodDescriptor.getParameterObject().read(ndr);
execute = true;
}
if (execute) {
//JILocalInterfaceDefinition interfaceDefinitionOfCall = interfaceDefinition;
Class calleeClazz = interfaceDefinitionOfClass.instance == null ? interfaceDefinitionOfClass.clazz : interfaceDefinitionOfClass.instance.getClass();
Method method = null;
try {
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.log(Level.INFO, "methodDescriptor: {0}", methodDescriptor.getMethodName());
}
method = calleeClazz.getDeclaredMethod(methodDescriptor.getMethodName(), methodDescriptor.getInparametersAsClass());
Object calleeInstance = interfaceDefinitionOfClass.instance == null ? calleeClazz.newInstance() : interfaceDefinitionOfClass.instance;
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.log(Level.INFO, "Call Back Method to be executed: {0} , to be executed on {1}", new Object[]{method, calleeInstance});
}
Object result = method.invoke(calleeInstance, params);
if (result == null) {
retVal = null;
} else if (!(result instanceof Object[])) {
retVal = new Object[1];
((Object[]) retVal)[0] = result;
} else {
retVal = result;
}
} catch (IllegalArgumentException e) {
LOGGER.throwing("JILocalCoClass", "invokeMethod", e);
throw new JIException(JIErrorCodes.E_INVALIDARG, e);
} catch (IllegalAccessException | SecurityException e) {
LOGGER.throwing("JILocalCoClass", "invokeMethod", e);
throw new JIException(JIErrorCodes.ERROR_ACCESS_DENIED, e);
} catch (InvocationTargetException | InstantiationException e) {
LOGGER.throwing("JILocalCoClass", "invokeMethod", e);
throw new JIException(JIErrorCodes.E_UNEXPECTED, e);
} catch (NoSuchMethodException e) {
LOGGER.throwing("JILocalCoClass", "invokeMethod", e);
throw new JIException(JIErrorCodes.RPC_S_PROCNUM_OUT_OF_RANGE, e);
}
}
return (Object[]) retVal;
}
/**
* Returns the primary interfaceDefinition.
*
* @return
* @see #JILocalCoClass(JILocalInterfaceDefinition, Class)
* @see #JILocalCoClass(JILocalInterfaceDefinition, Object)
*/
public JILocalInterfaceDefinition getInterfaceDefinition() {
return interfaceDefinition;
}
@Override
public boolean equals(Object target) {
if (target == null || !(target instanceof JILocalCoClass)) {
return false;
}
return identifier == ((JILocalCoClass) target).identifier;
}
@Override
public int hashCode() {
return identifier;
}
/**
* Returns the interface definition based on the IID of the interface.
*
* @param IID
* @return null
if no interface definition matching the IID
has been found.
*/
public JILocalInterfaceDefinition getInterfaceDefinition(String IID) {
return mapOfIIDsToInterfaceDefinitions.get(IID.toUpperCase());
}
JILocalInterfaceDefinition getInterfaceDefinitionFromIPID(String IPID) {
return mapOfIIDsToInterfaceDefinitions.get(ipidVsIID.get(IPID.toUpperCase()));
}
String getIpidFromIID(String uniqueIID) {
return IIDvsIpid.get(uniqueIID.toUpperCase());
}
String getIIDFromIpid(String ipid) {
return ipidVsIID.get(ipid.toUpperCase());
}
/**
*
* Returns true
if the primary interface definition represents
* a real IID
.
*
* @return
*/
// The bind-auth3 and all are then all done as per this IID
and not IUnknown.
public boolean isCoClassUnderRealIID() {
return realIID;
}
/**
* Associate the Session with this CoClass. Called by the framework.
*
* @exclude
* @param session
*/
void setSession(JISession session) {
this.session = session;
}
/**
* Returns the session associated with this CoClass.
*
* @return
*/
JISession getSession() {
return session;
}
List getSupportedInterfaces() {
return listOfSupportedInterfaces;
}
}