NativeComObject.java
/*
** Module : NativeComObject.java
** Abstract : A representation of a native COM object. Delegates access to native APIs to access
** it, via a network server to access the client-side.
**
** Copyright (c) 2017-2019, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------Description-----------------------------------
** 001 CA 20171025 Created initial version.
** 002 EVL 20171101 Changed reading external value for long value based method. Adding override
** for id() method to return proper COM object ID as resource ID. Adding COM
** object to registry once creation but only for the server side. The method
** call can also return comhandle to be registered and released later. Adding
** support for arrays returned from native COM property and method.
** 003 CA 20180517 Fixed comhandle FWD-specific resource management.
** 004 OM 20190223 Added support for generic OCXes and indexed properties.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.comauto;
import java.io.*;
import java.util.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.util.*;
/**
* This is representation of a native COM object. It delegates access to native APIs in
* {@link ComOleHelper} to access and/or manage it.
*/
public class NativeComObject
extends ComObject
implements Externalizable
{
/** Stores context-local state variables. */
private static final ContextLocal<WorkArea> work =
new ContextLocal<WorkArea>()
{
@Override
public WeightFactor getWeight()
{
return WeightFactor.LAST;
};
protected synchronized WorkArea initialValue()
{
return new WorkArea();
}
};
/** Flag which marks this COM object as deleted/released. */
private boolean deleted = false;
/** The ID used to identify this COM object on the native side. */
private long comId;
/** The COM automation name. */
private String name;
/**
* Save the reference to the {@link ComOleOps} proxy which delegates the calls to the
* client-side.
*/
private ComOleOps comOps;
/**
* Default c'tor, for deserialization purposes only.
*/
public NativeComObject()
{
// no-op, for deserialization only
}
/**
* Constructor which can be used by the native (or client-side) to create an instance marked
* with the {@link #comId}, which will be mapped via the {@link WorkArea#registry} on
* server-side.
*
* @param comId
* The ID used to identify this COM object on the native side.
*/
public NativeComObject(long comId)
{
// from native-side only
this.comId = comId;
}
/**
* Create a new instance with the specified details.
*
* @param name
* The COM automation name.
* @param comId
* The ID used to identify this COM object on the native side.
*/
private NativeComObject(String name, long comId)
{
this.comId = comId;
this.name = name;
WorkArea wa = work.get();
comOps = wa.comOps;
wa.registry.put(comId, this);
}
/**
* Obtain a reference to an automation. It can be a new instance or a reference to an
* already existing top-level automation.
*
* @param comObjectType
* The name of the automation.
* @param topLevel
* Only return top-level. If the requested {@code comObjectType} is not a top-level,
* return {@code null}.
* @param noCreate
* Must exist. Do not allow creation of new instances. Only used with {@code topLevel}
* requests.
*
* @return The {@code ComObject} requested, or {@code null} if such automation does not exist
* or it does not satisfy the parameter constraints.
*/
static ComObject getAutomation(String comObjectType,
boolean topLevel,
boolean noCreate)
{
WorkArea wa = work.get();
if (!wa.supported)
{
// TODO: show error?
return null;
}
long nativeId = wa.comOps.create(comObjectType, topLevel, noCreate);
if (nativeId != 0)
{
return new NativeComObject(comObjectType, nativeId);
}
// failed to create the object
return null;
}
/**
* Create a new {@link comhandle} by re-using the existing native COM object or create a new
* one.
*
* @param nco
* The {@link NativeComObject} with the COM details.
*
* @return See above.
*/
private static comhandle createComHandle(NativeComObject nco)
{
WorkArea wa = work.get();
NativeComObject com = null;
if (wa.registry.containsKey(nco.comId))
{
com = wa.registry.get(nco.comId);
}
else
{
String name = wa.comOps.getName(nco.comId);
com = new NativeComObject(name, nco.comId);
}
return new comhandle(com);
}
/**
* Get the {@link #comId}.
*
* @return See above.
*/
public long getComId()
{
return comId;
}
/**
* Get this resource's ID, if is already set.
*
* @return The resource's ID or <code>null</code> if not set.
*/
@Override
public Long id()
{
return getComId();
}
/**
* This is a no-op, as the native COM objects get their IDs from the native side.
*
* @param id
* Ignored.
*/
@Override
public void id(long id)
{
// no-op
}
/**
* Get the COM automation {@link #name}.
*
* @return See above.
*/
@Override
public String getActivexName()
{
return name;
}
/**
* Reports if this object is valid for use.
*
* @return <code>true</code> if we are valid (can be used).
*/
@Override
public boolean valid()
{
return !deleted && work.get().registry.containsKey(comId);
}
/**
* Perform actual delete of an resource. At the time of this call, it is assumed the resource
* is valid for deletion (the handle and the resource are both valid).
*/
@Override
public void delete()
{
if (valid())
{
try
{
comOps.release(comId);
}
finally
{
destroy();
deleted = true;
work.get().registry.remove(comId);
}
}
}
/**
* Obtain a property value from the COM object stored in this handle.
*
* @param prop
* The legacy name of the property. Case insensitive. If no property is found then a
* warning is displayed and method returns {@code unknown} value.
* @param indices
* A variable number of indices used to access this property's element.
*
* @return The value of the requested property or {@code unknown} on exceptions.
*/
@Override
public BaseDataType getProperty(String prop, Object... indices)
{
// TODO: extent property?
// TODO: how to handle errors?
ComParameter[] aIndices = unmarshal(indices);
BaseDataType ret = comOps.getProperty(comId, prop, aIndices);
if (ret instanceof comhandle)
{
return createComHandle((NativeComObject) ((comhandle) ret).getResource());
}
return ret;
}
/**
* Sets a COM property for the COM object stored by the handle.
*
* @param prop
* The legacy property name. Case insensitive. If no property is found then a warning
* is displayed and method returns without altering the stored object.
* @param newVal
* The new value for the property. It must be of a compatible type with the property.
* @param indices
* A variable number of indices used to access this property's element.
*
* @return <code>true</code> if the property was set.
*/
@Override
public boolean setProperty(String prop, Object newVal, Object... indices)
{
ComParameter val = unmarshal(newVal);
ComParameter[] aIndices = unmarshal(indices);
return comOps.setProperty(comId, prop, val, aIndices);
}
/**
* Calls a COM-method without parameters on the COM-object stored in this com-handle.
*
* @param methodName
* The method name. Case insensitive. If the COM-object does not declare such method
* an error message is displayed and this method returns {@code unknown} value.
* @param params
* The list of actual parameters. Their types must be compatible with the parameters
* of called method.
*
* @return The result of the called method, if any.
*/
@Override
public BaseDataType call(String methodName, Object... params)
{
// TODO: extent return type?
ComParameter[] aParams = unmarshal(params);
BaseDataType[][] vals = comOps.call(comId, methodName, aParams);
checkParameters(vals, params);
// method also can return COM-HANDLE and we need to put it in registry
// to be able to release later
if (vals[0] != null && vals[0][0] instanceof comhandle)
{
vals[0][0] = createComHandle((NativeComObject) ((comhandle) vals[0][0]).getResource());
}
return vals[0][0];
}
/**
* Calls a COM-method without parameters on the COM-object stored in this com-handle.
*
* @param methodName
* The method name. Case insensitive. If the COM-object does not declare such method
* an error message is displayed and this method returns {@code unknown} value.
* @param params
* The list of actual parameters. Their types must be compatible with the parameters
* of called method.
*
* @return The result of the called method, in a {@code comhandle}, so it can be used in a
* chained call or property access.
*/
@Override
public comhandle chainCall(String methodName, Object... params)
{
ComParameter[] aParams = unmarshal(params);
BaseDataType[][] vals = comOps.call(comId, methodName, aParams);
checkParameters(vals, params);
return createComHandle((NativeComObject) ((comhandle) vals[0][0]).getResource());
}
/**
* Replacement for the default object writing method. Only the {@link #comId} is used on
* network transfer. The receiving side must know to create a new instance based on this ID.
*
* @param out
* The output destination to which fields will be saved.
*
* @throws IOException
* In case of I/O errors.
*/
@Override
public void writeExternal(ObjectOutput out)
throws IOException
{
out.writeLong(comId);
}
/**
* Replacement for the default object reading method. Only the {@link #comId} is used on
* network transfer. The receiving side must know to create a new instance based on this ID.
*
* @param in
* The input source from which fields will be restored.
*
* @throws IOException
* In case of I/O errors.
*/
public void readExternal(ObjectInput in)
throws IOException
{
comId = in.readLong();
};
/**
* Holds context-local data specific to native COM objects, on server-side.
*/
private static class WorkArea
{
/** The registry of native COM objects available. */
private final Map<Long, NativeComObject> registry = new HashMap<>();
/** The {@link ComOleOps} network proxy which delegates the calls to FWD client-side. */
private final ComOleOps comOps;
/** Flag indicating if the current system has proper support for COM Automation. */
private final boolean supported;
/**
* Default c'tor, initializes the context-local fields.
*/
public WorkArea()
{
comOps = (ComOleOps) RemoteObject.obtainNetworkInstance(ComOleOps.class);
supported = comOps.isSupported();
}
}
}