When I start to installing using installutil it gives me following error, I have set ServiceInstaller and ServiceInstallerProcess,
System.InvalidOperationException: Installation failed due to the absence of a ServiceProcessInstaller. The ServiceProcessInstaller must either be the containing installer, or it must be present in the Installers collection on the same installer as the ServiceInstaller.
Any ideas on how to fix the problem?
I had the same problem with the Installer and found that in the [YourInstallerClassName].Designer.cs at InitializeComponent() method, the dfault generated code is Missing add the ServiceProcessInstaller
//
// [YourInstallerClassName]
//
this.Installers.AddRange(new System.Configuration.Install.Installer[] {
this.serviceInstaller1});
Just add your ServiceProcessInstaller in my case its:
//
// ProjectInstaller
//
this.Installers.AddRange(new System.Configuration.Install.Installer[] {
this.serviceProcessInstaller1, //--> Missing
this.serviceInstaller1});
and the Setup project works.
Usually, this means you failed to attribute your installer with RunInstaller(true). Here's an example of one I have handy that works:
namespace OnpointConnect.WindowsService
{
[RunInstaller(true)]
public partial class OnpointConnectServiceInstaller : Installer
{
private ServiceProcessInstaller processInstaller;
private ServiceInstaller serviceInstaller;
public OnpointConnectServiceInstaller()
{
InitializeComponent();
}
public override string HelpText
{
get
{
return
"/name=[service name]\nThe name to give the OnpointConnect Service. " +
"The default is OnpointConnect. Note that each instance of the service should be installed from a unique directory with its own config file and database.";
}
}
public override void Install(IDictionary stateSaver)
{
Initialize();
base.Install(stateSaver);
}
public override void Uninstall(IDictionary stateSaver)
{
Initialize();
base.Uninstall(stateSaver);
}
private void Initialize()
{
processInstaller = new ServiceProcessInstaller();
serviceInstaller = new ServiceInstaller();
processInstaller.Account = ServiceAccount.LocalSystem;
serviceInstaller.StartType = ServiceStartMode.Manual;
string serviceName = "OnpointConnect";
if (Context.Parameters["name"] != null)
{
serviceName = Context.Parameters["name"];
}
Context.LogMessage("The service name = " + serviceName);
serviceInstaller.ServiceName = serviceName;
try
{
//stash the service name in a file for later use in the service
var writer = new StreamWriter("ServiceName.dat");
try
{
writer.WriteLine(serviceName);
}
finally
{
writer.Close();
}
Installers.Add(serviceInstaller);
Installers.Add(processInstaller);
}
catch (Exception err)
{
Context.LogMessage("An error occured while creating configuration information for the service. The error is "
+ err.Message);
}
}
}
}
Related
I'm trying to use Moshi with GraalVM's native-image, and trying to get the reflection to work.
I have my class:
public class SimpleJson {
private String message;
public SimpleJson(String message) { this.message = message; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
and code
var simpleJsonJsonAdapter = moshi.adapter(SimpleJson.class);
var simpleJsonString = "{\"message\": \"hello there\"}";
var simpleJsonObj = simpleJsonJsonAdapter.fromJson(simpleJsonString);
var simpleJsonStringBack = simpleJsonJsonAdapter.toJson(simpleJsonObj);
System.out.println("Converting: " + simpleJsonString);
System.out.println("Simple json has message: " + simpleJsonObj.getMessage());
System.out.println("Simple message full json coming back is: " + simpleJsonStringBack);
which prints:
Converting: {"message": "hello there"}
Simple json has message: null
Simple message full json coming back is: {}
and this only works (by avoiding an exception with SimpleJson is instantiated reflectively but was never registered) with the following chunk of code, to get everything registered ready for reflection:
#AutomaticFeature
public class RuntimeReflectionRegistrationFeature implements Feature {
#Override
public void beforeAnalysis(BeforeAnalysisAccess access) {
try {
// Enable the moshi adapters
var moshiPkgs = "com.squareup.moshi";
// Standard shared models
var pkgs = "my.models";
// Register moshi
new ClassGraph()
.enableClassInfo()
.acceptPackages(moshiPkgs)
.scan()
.getSubclasses(JsonAdapter.class.getName())
.forEach(
classInfo -> {
System.out.println("Building moshi adapter class info for " + classInfo);
registerMoshiAdapter(classInfo.loadClass());
});
// Register everything we've got
new ClassGraph()
.enableClassInfo() // Scan classes, methods, fields, annotations
.acceptPackages(pkgs) // Scan package(s) and subpackages
.scan()
.getAllClasses()
.forEach(
classInfo -> {
System.out.println("Building class info for " + classInfo);
registerGeneralClass(classInfo.loadClass());
});
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
private void registerMoshiAdapter(Class<?> classInfo) {
try {
RuntimeReflection.register(classInfo);
Arrays.stream(classInfo.getMethods()).forEach(RuntimeReflection::register);
ParameterizedType superclass = (ParameterizedType) classInfo.getGenericSuperclass();
// extends JsonAdapter<X>()
var valueType = Arrays.stream(superclass.getActualTypeArguments()).findFirst();
if (valueType.isPresent() && valueType.get() instanceof Class) {
Arrays.stream(((Class<?>) valueType.get()).getConstructors())
.forEach(RuntimeReflection::register);
}
RuntimeReflection.register(classInfo.getConstructor(Moshi.class));
} catch (RuntimeException | NoSuchMethodException name) {
// expected
}
}
private void registerGeneralClass(Class<?> classInfo) {
try {
RuntimeReflection.register(classInfo);
Arrays.stream(classInfo.getDeclaredMethods()).forEach(RuntimeReflection::register);
Arrays.stream(classInfo.getDeclaredConstructors()).forEach(RuntimeReflection::register);
} catch (RuntimeException name) {
// expected
}
}
}
(inspired by this issue, although I believe that's trying to address MoshiAdapters generated which is a Kotlin only thing).
So, Java doesn't complain about reflection (which it was previously trying to do, hence the error message mentioned), but Moshi isn't actually doing anything.
Does anyone have any suggestions on how to work around this?
Note, I did try the manual reflect-config.json approach with
[
{
"allDeclaredClasses": true,
"queryAllDeclaredConstructors": true,
"queryAllPublicConstructors": true,
"name": "my.models.SimpleJson",
"queryAllDeclaredMethods": true,
"queryAllPublicMethods": true,
"allPublicClasses": true
}
}
but this resulted in error around Runtime reflection is not supported for... - also not good!
The solution was simple in the end... the registration just needed
Arrays.stream(classInfo.getDeclaredFields()).forEach(RuntimeReflection::register);
adding.
I have implemented an event listener for Cucumber events:
public class AdccEventListener implements ConcurrentEventListener {
private static boolean stepFailed = false;
#Override
public void setEventPublisher(EventPublisher publisher) {
System.out.println("register handlers!!!");
publisher.registerHandlerFor(TestCaseStarted.class, this::scenarioStartedHandler);
publisher.registerHandlerFor(TestCaseFinished.class, this::scenarioFinishedHandler);
publisher.registerHandlerFor(TestStepStarted.class, this::stepStartedHandler);
publisher.registerHandlerFor(TestStepFinished.class, this::stepFinishedHandler);
}
private void scenarioStartedHandler(TestCaseStarted event) {
stepFailed = false;
}
private void scenarioFinishedHandler(TestCaseFinished event) {
BaseTestUtils.reportInfoMessage("Scenario finish name is: " + event.getTestCase().getName() + " end of Scenario statement!");
if (stepFailed) {
Result result = event.getResult();
setPrivateField(result, "status", Status.FAILED);
}
}
private void stepStartedHandler(TestStepStarted event) {
if (event.getTestStep() instanceof PickleStepTestStep) {
PickleStepTestStep testStep = (PickleStepTestStep) event.getTestStep();
BaseTestUtils.reportInfoMessage("step name is: " + testStep.getStep().getText() + " end of statement!");
ThreadLocalEvent.setStep(testStep);
}
}
private void stepFinishedHandler(TestStepFinished event) {
if (event.getTestStep() instanceof PickleStepTestStep) {
PickleStepTestStep testStep = (PickleStepTestStep) event.getTestStep();
Result result = event.getResult();
ThreadLocalEvent.setResult(result);
if (result.getStatus().equals(Status.FAILED)) {
if (!testStep.getStep().getKeyWord().startsWith("Given")) {
stepFailed = true;
setPrivateField(result, "status", Status.PASSED);
}
}
}
}
private void setPrivateField(Object subject, String fieldName, Object value) {
try {
Field f = subject.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
f.set(subject, value);
} catch (Exception e) {
e.printStackTrace();
}
}
}
i have declared the Event Listener as a plugin in the CucumberOptions of the RunTests class.
#CucumberOptions(plugin = {"pretty", "html:target/cucumber", "json:target/cucumber.json", "com.radware.bdd.AdccEventListener"},
glue = {"com.radware.tests"},
features = {"src/test/resources/Features"},
strict = true,
tags = {"#Functional"})
Now when i am executing tests on my local work station all is good. i am getting all events that were published in listener.
But, When the same project is executed on the docker container, listener does not get any events.
f. e. start Step, end Step events.
any idea what could cause it to not work under container?
Thank you.
Stas
I have found a problem.
When Maven tests were initiated from Jenkins or Docker container CucumberOptions arguments were passed. one of arguments was "--plugin". so my local definitions were overwritten.
Resolution is to add my custom plugin "com.radware.bdd.AdccEventListener" to the options passed through cli command.
I am creating a custom workflow in Microsoft Dynamics CRM to automatically update a field when a record is saved.
A developer on a forum provided the following source code; but he is not responding to my questions.
public class SalesRepActivity2 : WorkFlowActivityBase
{
[Input("Sales Rep Name")]
public InArgument<string> SalesRepName { get; set; }
[Output("Sales Rep")]
[ReferenceTarget("systemuser")]
public OutArgument<EntityReference> SalesRep { get; set; }
[Output("IsSuccess")]
public OutArgument<bool> IsSuccess { get; set; }
[Output("Message")]
public OutArgument<string> Message { get; set; }
protected override void Execute(
CodeActivityContext activityContext,
IWorkflowContext workflowContext,
IOrganizationService CrmService,
ITracingService trace)
{
try
{
string salesRepName = SalesRepName.Get(activityContext);
if (string.IsNullOrWhiteSpace(salesRepName))
{
IsSuccess.Set(activityContext, false);
Message.Set(activityContext, "Sales Rep Name not provided");
}
var QEsystemuser = new QueryExpression("systemuser");
QEsystemuser.ColumnSet.AddColumns("salesrepname");
QEsystemuser.Criteria.AddCondition("salesrepname", ConditionOperator.Equal, salesRepName);
var results = CrmService.RetrieveMultiple(QEsystemuser);
if (results == null || !results.Entities.Any())
{
IsSuccess.Set(activityContext, false);
Message.Set(activityContext, "User with " + salesRepName + " not found");
return;
}
if (results.Entities.Count > 1)
{
IsSuccess.Set(activityContext, false);
Message.Set(activityContext, "Multiple users found with same name : " + salesRepName);
return;
}
IsSuccess.Set(activityContext, true);
SalesRep.Set(activityContext, results.Entities.Single().ToEntityReference());
}
catch (Exception ex)
{
IsSuccess.Set(activityContext, false);
Message.Set(activityContext, "An error occurred trying to find user : " + ex.Message);
}
}
I am trying to get the code to compile on my machine.
I installed the following NuGet packages, which resolved most of the errors:
Microsoft.Xrm.Sdk.Workflow.2015
Microsoft.Xrm.Sdk.2015
But my project cannot resolve the WorkFlowActivityBase class.
Is there a reference I should set or a NuGet package I should install to resolve this?
Thank you.
WorkFlowActivityBase is a custom base class that implements the CodeActivity (System.Activities) anyone can write, it's not an official Dynamics class. Yon can find dozen of classes lie that in the web.
Basically, you should use the CodeActivity. here as an example:
https://learn.microsoft.com/en-us/powerapps/developer/common-data-service/workflow/sample-create-custom-workflow-activity
The code you posted is not a good place for you to start because it makes heavy use of proprietary objects. Ziv has provided good information about extending the CodeActivity class but I do not recommend starting there. Instead read about how to develop custom workflow activities, and write a workflow using the base Microsoft classes so that you understand how they work:
https://learn.microsoft.com/en-us/powerapps/developer/common-data-service/workflow/workflow-extensions
Once you have some experience with creating custom workflows, and you understand the limitations of the base objects, then go back and implement something more fancy.
Here in an exaple from the documentation:
namespace Microsoft.Crm.Sdk.Samples
{
public sealed class SimpleSdkActivity : CodeActivity
{
protected override void Execute(CodeActivityContext executionContext)
{
//Create the tracing service
ITracingService tracingService = executionContext.GetExtension<ITracingService>();
//Create the context
IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
...
var systemUsers = service.RetrieveMultiple(QEsystemuser)
}
}
}
Earlier this year I developed an implementation of the SAP JCO CustomDestinationProvider for one of my Spring MVC tomcat applications. In my application, I use this implementation to call a BAPI in my SAP R/3 system to retrieve data.
I am now working on a second Spring MVC tomcat application that I want to have call a BAPI in my SAP R/3 system to retrieve data. It will be a different BAPI that I will be calling, thus it will be different data that I will be retrieving. Since this is a different application calling a different BAPI, I want to use a different SAP system user in my configurations. This new application will be running on the same physical tomcat server as the first application.
My question is should I develop another implementation of the SAP JCO CustomDestinationProvider for this new application or should I somehow reuse the first implementation? If the answer is that I should develop another implementation for this new application, I would expect then that I would develop another implementation for each new Spring MVC tomcat application that I develop that needs to talk to SAP. Is this correct thinking?
If I do a different implementation for this new application of mine, should I be using the same destination name in the code, or should I use a different name?
Below is the code for my first implementation of CustomDestinationDataProvider:
public class CustomDestinationDataProvider {
public class MyDestinationDataProvider implements DestinationDataProvider {
private DestinationDataEventListener eL;
private HashMap<String, Properties> secureDBStorage = new HashMap<String, Properties>();
public Properties getDestinationProperties(String destinationName) {
try {
Properties p = secureDBStorage.get(destinationName);
if(p!=null) {
if(p.isEmpty())
throw new DataProviderException(DataProviderException.Reason.INVALID_CONFIGURATION, "destination configuration is incorrect", null);
return p;
}
return null;
} catch(RuntimeException re) {
throw new DataProviderException(DataProviderException.Reason.INTERNAL_ERROR, re);
}
}
public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
this.eL = eventListener;
}
public boolean supportsEvents() {
return true;
}
public void changeProperties(String destName, Properties properties) {
synchronized(secureDBStorage) {
if(properties==null) {
if(secureDBStorage.remove(destName)!=null)
eL.deleted(destName);
} else {
secureDBStorage.put(destName, properties);
eL.updated(destName); // create or updated
}
}
}
}
public ArrayList<String> executeSAPCall(Properties connectProperties, ArrayList<String> partnumbers) throws Exception {
String destName = "ABAP_AS";
SAPDAO sapDAO = new SAPDAO();
ArrayList<MaterialBean> searchResults = new ArrayList<MaterialBean>();
MyDestinationDataProvider myProvider = new MyDestinationDataProvider();
boolean destinationDataProviderRegistered = com.sap.conn.jco.ext.Environment.isDestinationDataProviderRegistered();
JCoDestination dest;
try {
if (!destinationDataProviderRegistered) {
com.sap.conn.jco.ext.Environment.registerDestinationDataProvider(myProvider);
myProvider.changeProperties(destName, connectProperties);
}
} catch(IllegalStateException providerAlreadyRegisteredException) {
logger.error("executeSAPCall: providerAlreadyRegisteredException!");
}
try {
dest = JCoDestinationManager.getDestination(destName);
searchResults = sapDAO.searchSAP(dest, partnumbers);
} catch(JCoException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return searchResults;
}
}
If the answer is that I should not need to implement another CustomDestinationDataProvider for my second application, what other considerations do I need to keep in mind?
You can only register one DestinationDataProvider so the one you set must be able to handle both (or more) different connections. In order to do this, you need unique names for each connection, i.e. destName can't be the fixed value ABAP_AS, you need to create one for each connection.
Your current implementation of the provider looks good for me, but your method when calling the RFC is mixing the creation of the connection and the actual RFC-calling too much in my eyes. IMHO you should separate the former into its own method, so you can call it from other parts of your application to e.g. do other things than RFC-calling.
I've figured it out! I discovered two different ways to implement CustomDestinationDataProvider so that I could use multiple destinations.
Something that I did that helped out with both of my different solutions was change out the method in CustomDestinationDataProvider that instantiates the MyDestinationDataProvider inner class so that instead of returning ArrayList, it returns JCoDestination. I changed the name of this method from executeSAPCall to getDestination.
The first way that I discovered that allowed me to use multiple destinations, successfully changing out destinations, was to introduce a class variable for MyDestinationDataProvider so that I could keep my instantiated version. Please note that for this solution, the CustomDestinationDataProvider class is still embedded within my java application code.
I found that this solution only worked for one application. I was not able to use this mechanism in multiple applications on the same tomcat server, but at least I was finally able to successfully switch destinations. Here is the code for CustomDestinationDataProvider.java for this first solution:
public class CustomDestinationDataProvider {
private MyDestinationDataProvider gProvider; // class version of MyDestinationDataProvider
public class MyDestinationDataProvider implements DestinationDataProvider {
private DestinationDataEventListener eL;
private HashMap<String, Properties> secureDBStorage = new HashMap<String, Properties>();
public Properties getDestinationProperties(String destinationName) {
try {
Properties p = secureDBStorage.get(destinationName);
if(p!=null) {
if(p.isEmpty())
throw new DataProviderException(DataProviderException.Reason.INVALID_CONFIGURATION, "destination configuration is incorrect", null);
return p;
}
return null;
} catch(RuntimeException re) {
System.out.println("getDestinationProperties: Exception detected!!! message = " + re.getMessage());
throw new DataProviderException(DataProviderException.Reason.INTERNAL_ERROR, re);
}
}
public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
this.eL = eventListener;
}
public boolean supportsEvents() {
return true;
}
public void changeProperties(String destName, Properties properties) {
synchronized(secureDBStorage) {
if(properties==null) {
if(secureDBStorage.remove(destName)!=null) {
eL.deleted(destName);
}
} else {
secureDBStorage.put(destName, properties);
eL.updated(destName); // create or updated
}
}
}
}
public JCoDestination getDestination(String destName, Properties connectProperties) {
MyDestinationDataProvider myProvider = new MyDestinationDataProvider();
boolean destinationDataProviderRegistered = com.sap.conn.jco.ext.Environment.isDestinationDataProviderRegistered();
if (!destinationDataProviderRegistered) {
try {
com.sap.conn.jco.ext.Environment.registerDestinationDataProvider(myProvider);
gProvider = myProvider; // save our destination data provider in the class var
} catch(IllegalStateException providerAlreadyRegisteredException) {
throw new Error(providerAlreadyRegisteredException);
}
} else {
myProvider = gProvider; // get the destination data provider from the class var.
}
myProvider.changeProperties(destName, connectProperties);
JCoDestination dest = null;
try {
dest = JCoDestinationManager.getDestination(destName);
} catch(JCoException e) {
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
}
return dest;
}
}
This is the code in my servlet class that I use to instantiate and call CustomDestinationDataProvider within my application code:
CustomDestinationDataProvider cddp = new CustomDestinationDataProvider();
SAPDAO sapDAO = new SAPDAO();
Properties p1 = getProperties("SAPSystem01");
Properties p2 = getProperties("SAPSystem02");
try {
JCoDestination dest = cddp.getDestination("SAP_R3_USERID_01", p1); // establish the first destination
sapDAO.searchEmployees(dest, searchCriteria); // call the first BAPI
dest = cddp.getDestination("SAP_R3_USERID_02", p2); // establish the second destination
sapDAO.searchAvailability(dest); // call the second BAPI
} catch (Exception e) {
e.printStackTrace();
}
Again, this solution only works within one application. If you implement this code directly into more than one application, the first app that calls this code gets the resource and the other one will error out.
The second solution that I came up with allows multiple java applications to use the CustomDestinationDataProvider class at the same time. I broke the CustomDestinationDataProvider class out of my application code and created a separate java spring application for it (not a web application) for the purpose of creating a jar. I then transformed the MyDestinationDataProvider inner class into a singleton. Here's the code for the singleton version of CustomDestinationDataProvider:
public class CustomDestinationDataProvider {
public static class MyDestinationDataProvider implements DestinationDataProvider {
////////////////////////////////////////////////////////////////////
// The following lines convert MyDestinationDataProvider into a singleton. Notice
// that the MyDestinationDataProvider class has now been declared as static.
private static MyDestinationDataProvider myDestinationDataProvider = null;
private MyDestinationDataProvider() {
}
public static MyDestinationDataProvider getInstance() {
if (myDestinationDataProvider == null) {
myDestinationDataProvider = new MyDestinationDataProvider();
}
return myDestinationDataProvider;
}
////////////////////////////////////////////////////////////////////
private DestinationDataEventListener eL;
private HashMap<String, Properties> secureDBStorage = new HashMap<String, Properties>();
public Properties getDestinationProperties(String destinationName) {
try {
Properties p = secureDBStorage.get(destinationName);
if(p!=null) {
if(p.isEmpty())
throw new DataProviderException(DataProviderException.Reason.INVALID_CONFIGURATION, "destination configuration is incorrect", null);
return p;
}
return null;
} catch(RuntimeException re) {
throw new DataProviderException(DataProviderException.Reason.INTERNAL_ERROR, re);
}
}
public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
this.eL = eventListener;
}
public boolean supportsEvents() {
return true;
}
public void changeProperties(String destName, Properties properties) {
synchronized(secureDBStorage) {
if(properties==null) {
if(secureDBStorage.remove(destName)!=null) {
eL.deleted(destName);
}
} else {
secureDBStorage.put(destName, properties);
eL.updated(destName); // create or updated
}
}
}
}
public JCoDestination getDestination(String destName, Properties connectProperties) throws Exception {
MyDestinationDataProvider myProvider = MyDestinationDataProvider.getInstance();
boolean destinationDataProviderRegistered = com.sap.conn.jco.ext.Environment.isDestinationDataProviderRegistered();
if (!destinationDataProviderRegistered) {
try {
com.sap.conn.jco.ext.Environment.registerDestinationDataProvider(myProvider);
} catch(IllegalStateException providerAlreadyRegisteredException) {
throw new Error(providerAlreadyRegisteredException);
}
}
myProvider.changeProperties(destName, connectProperties);
JCoDestination dest = null;
try {
dest = JCoDestinationManager.getDestination(destName);
} catch(JCoException ex) {
ex.printStackTrace();
throw ex;
} catch (Exception ex) {
ex.printStackTrace();
throw ex;
}
return dest;
}
}
After putting this code into the jar file application and creating the jar file (I call it JCOConnector.jar), I put the jar file on the shared library classpath of my tomcat server and restarted the tomcat server. In my case, this was /opt/tomcat/shared/lib. Check your /opt/tomcat/conf/catalina.properties file for the shared.loader line for the location of your shared library classpath. Mine looks like this:
shared.loader=\
${catalina.home}/shared/lib\*.jar,${catalina.home}/shared/lib
I also put a copy of this jar file in the "C:\Users\userid\Documents\jars" folder on my workstation so that the test application code could see the code in the jar and compile. I then referenced this copy of the jar file in my pom.xml file in both of my test applications:
<dependency>
<groupId>com.mycompany</groupId>
<artifactId>jcoconnector</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>C:\Users\userid\Documents\jars\JCOConnector.jar</systemPath>
</dependency>
After adding this to the pom.xml file, I right clicked on each project, selected Maven -> Update Project..., and I then right clicked again on each project and selected 'Refresh'. Something very important that I learned was to not add a copy of JCOConnector.jar directly to either of my test projects. The reason for this is because I want the code from the jar file in /opt/tomcat/shared/lib/JCOConnector.jar to be used. I then built and deployed each of my test apps to the tomcat server.
The code that calls my JCOConnector.jar shared library in my first test application looks like this:
CustomDestinationDataProvider cddp = new CustomDestinationDataProvider();
JCoDestination dest = null;
SAPDAO sapDAO = new SAPDAO();
Properties p1 = getProperties("SAPSystem01");
try {
dest = cddp.getDestination("SAP_R3_USERID_01", p1);
sapDAO.searchEmployees(dest);
} catch (Exception ex) {
ex.printStackTrace();
}
The code in my second test application that calls my JCOConnector.jar shared library looks like this:
CustomDestinationDataProvider cddp = new CustomDestinationDataProvider();
JCoDestination dest = null;
SAPDAO sapDAO = new SAPDAO();
Properties p2 = getProperties("SAPSystem02");
try {
dest = cddp.getDestination("SAP_R3_USERID_02", p2);
sapDAO.searchAvailability(dest);
} catch (Exception ex) {
ex.printStackTrace();
}
I know that I've left out a lot of the steps involved in first getting the SAP JCO 3 library installed on your workstation and server. I do hope that this helps out at least one other person of getting over the hill of trying to get multiple spring mvc java spplications talking to SAP on the same server.
I have developed a custom code generator and deploy it via a VSIX, the problem is I should register assembly via regasm.exe after installing VSIX, but I have seen some projects such as DSLTool with custom code generation that registers automatically, any body knows how can I have automatically registration in my VSIX project?
You should be able to do the following:
0. Remove old (bad practice) COM code
Edit your project build settings to not have "Register for COM interop" checked.
Edit your AssemblyInfo.cs and set ComVisible to false:
[assembly: ComVisible(false)]
Assuming your generator is named MyCodeGenerator, open the definition of MyCodeGenerator and add the attribute:
[ComVisible(true)]
1. Edit your VSIX project to enable generation of a pkgdef file.
Right click your project in Solution Explorer and select Unload Project.
Right click the unloaded project and select Edit MyProject.csproj, where MyProject is the name of your project.
Locate the XML element <GeneratePkgDefFile>.
If the element exists, ensure that its value is set to true.
Otherwise, add the following to the end of the first <PropertyGroup> element in your project file which does not have a Condition attribute (this is almost always the first PropertyGroup in the file).
<GeneratePkgDefFile>true</GeneratePkgDefFile>
Repeat step 3 to set <CopyBuildOutputToOutputDirectory> to true.
Save and close the .csproj file.
Right click the unloaded project in Solution Explorer and select Reload Project.
Open your project's source.extension.vsixmanifest file and locate the <Content> element. Add the following element as a child:
<VsPackage>|%CurrentProject%|</VsPackage>
If your extension does not provide any other content elements, the entire <Content> element would now be this:
<Content>
<VsPackage>|%CurrentProject%|</VsPackage>
</Content>
2. Define the required attribute types
At the end of this answer are sections for ProvideGeneratorAttribute.cs and ProvideAssemblyObjectAttribute.cs. Add these files to your project.
3. Register the code generator class
Open your project's AssemblyInfo.cs.
Assuming your custom code generator class is named MyCodeGenerator, add the following attribute to the assembly info file.
[assembly: ProvideAssemblyObject(typeof(MyCodeGenerator))]
4. Associate your code generator with the language service
Open your project's AssemblyInfo.cs.
Assuming your custom code generator class is named MyCodeGenerator, and you want to register the code generator with the C# language service, add the following attribute to the assembly info file.
[assembly: ProvideGenerator(
typeof(MyCodeGenerator),
VSConstants.UICONTEXT.CSharpProject_string,
Description = "Description of the generator",
GeneratesDesignTimeSource = true)]
Appendix A: ProvideGeneratorAttribute.cs
Disclaimer: This code is completely untested.
using System;
using Microsoft.VisualStudio.Shell;
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class ProvideGeneratorAttribute : RegistrationAttribute
{
private readonly Type _generatorType;
private readonly Guid _languageServiceGuid;
private string _name;
private string _description;
private bool _generatesDesignTimeSource;
public ProvideGeneratorAttribute(Type generatorType, string languageServiceGuid)
{
if (generatorType == null)
throw new ArgumentNullException("generatorType");
if (languageServiceGuid == null)
throw new ArgumentNullException("languageServiceGuid");
if (string.IsNullOrEmpty(languageServiceGuid))
throw new ArgumentException("languageServiceGuid cannot be empty");
_generatorType = generatorType;
_languageServiceGuid = new Guid(languageServiceGuid);
_name = _generatorType.Name;
}
public Type GeneratorType
{
get
{
return _generatorType;
}
}
public Guid LanguageServiceGuid
{
get
{
return _languageServiceGuid;
}
}
public string Name
{
get
{
return _name;
}
set
{
if (value == null)
throw new ArgumentNullException("value");
if (string.IsNullOrEmpty(value))
throw new ArgumentException("value cannot be empty");
_name = value;
}
}
public string Description
{
get
{
return _description;
}
set
{
_description = value;
}
}
public bool GeneratesDesignTimeSource
{
get
{
return _generatesDesignTimeSource;
}
set
{
_generatesDesignTimeSource = value;
}
}
private string RegistrationKey
{
get
{
return string.Format(#"Generators\{0}\{1}", LanguageServiceGuid.ToString("B"), Name);
}
}
public override void Register(RegistrationContext context)
{
using (Key key = context.CreateKey(RegistrationKey))
{
if (!string.IsNullOrEmpty(Description))
key.SetValue(string.Empty, Description);
key.SetValue("CLSID", GeneratorType.GUID.ToString("B"));
key.SetValue("GeneratesDesignTimeSource", GeneratesDesignTimeSource ? 1 : 0);
}
}
public override void Unregister(RegistrationContext context)
{
context.RemoveKey(RegistrationKey);
}
}
Appendix B: ProvideAssemblyObjectAttribute.cs
Disclaimer: This code is completely untested.
using System;
using Microsoft.VisualStudio.Shell;
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class ProvideAssemblyObjectAttribute : RegistrationAttribute
{
private readonly Type _objectType;
private RegistrationMethod _registrationMethod;
public ProvideAssemblyObjectAttribute(Type objectType)
{
if (objectType == null)
throw new ArgumentNullException("objectType");
_objectType = objectType;
}
public Type ObjectType
{
get
{
return _objectType;
}
}
public RegistrationMethod RegistrationMethod
{
get
{
return _registrationMethod;
}
set
{
_registrationMethod = value;
}
}
private string ClsidRegKey
{
get
{
return string.Format(#"CLSID\{0}", ObjectType.GUID.ToString("B"));
}
}
public override void Register(RegistrationContext context)
{
using (Key key = context.CreateKey(ClsidRegKey))
{
key.SetValue(string.Empty, ObjectType.FullName);
key.SetValue("InprocServer32", context.InprocServerPath);
key.SetValue("Class", ObjectType.FullName);
if (context.RegistrationMethod != RegistrationMethod.Default)
_registrationMethod = context.RegistrationMethod;
switch (RegistrationMethod)
{
case Microsoft.VisualStudio.Shell.RegistrationMethod.Default:
case Microsoft.VisualStudio.Shell.RegistrationMethod.Assembly:
key.SetValue("Assembly", ObjectType.Assembly.FullName);
break;
case Microsoft.VisualStudio.Shell.RegistrationMethod.CodeBase:
key.SetValue("CodeBase", context.CodeBase);
break;
default:
throw new InvalidOperationException();
}
key.SetValue("ThreadingModel", "Both");
}
}
public override void Unregister(RegistrationContext context)
{
context.RemoveKey(ClsidRegKey);
}
}