I write a maven project named "error project". Its aim is to handle exceptions in other projects. It utilizes another maven project "configuration manager". The ultimate aim of configuration manager is to obtain database credentials from some xml files. In this project, it has "IExceptionService", it is proposed that other projects write their exceptions through this interface. This interface's concrete class is assigned at runtime by "DependencyResolver" class. The assignment has been done in "resolve" method as such:
#SuppressWarnings("unchecked")
public static <T> T resolve(Class<T> classType, HashMap<DependencyResolverParameter,String> parameterMap )
{
try {
if(classType == null)
throw new ArgumanBosHatasi("classType is null");
if(parameterMap == null)
throw new ArgumanBosHatasi(HashMap.class.toString());
assert classType != null : "classType == null";
if(classType.equals(IErrorRepository.class))
return (T)getIErrorRepository();
else if(classType.equals(IErrorFile.class))
return (T)getIErrorFile();
else if(classType.equals(ErrorBusinessRule.class))
return (T)getErrorBusinessRule();
else if(classType.equals(IErrorService.class))
{
try {
return (T)getIErrorService(parameterMap);
} catch (UygulamaHatasi applicationException) {
throw applicationException; }
}
else
throw new UnsupportedOperationException("Unknown class type.");
} catch (Exception exception) {
throw exception;
}
}
private static IErrorRepository getIErrorRepository() {
try
{
if(errorDatabase != null)
return errorDatabase;
KonfigurasyonYoneticisi yonetici = new KonfigurasyonYoneticisi();
String sifreKodu = "HATASIFRE";
String schema = yonetici.okuAyar("HATASCHEMA");
String tabloAdi = yonetici.okuAyar("HATATABLOSU");
String veritabaniUrl = yonetici.okuAyar("HATAVERITABANIURL"); //Here is line 92 in DependencyResolver
String kullaniciAdi = yonetici.okuAyar("HATAKULLANICIADI");
if(SozceIsleri.bosMu(sifreKodu))
throw new BasarisizIslemHatasi("sifreKodu boş geliyor.");
if(SozceIsleri.bosMu(schema))
throw new BasarisizIslemHatasi("schema boş geliyor.");
if(SozceIsleri.bosMu(tabloAdi))
throw new BasarisizIslemHatasi("tabloAdi boş geliyor.");
if(SozceIsleri.bosMu(veritabaniUrl))
throw new BasarisizIslemHatasi("veritabaniUrl boş geliyor.");
if(SozceIsleri.bosMu(kullaniciAdi))
throw new BasarisizIslemHatasi("kullaniciAdi boş geliyor.");
assert SozceIsleri.doluMu(sifreKodu) : "SozceIsleri.bosMu(sifreKodu)";
assert SozceIsleri.doluMu(schema) : "SozceIsleri.bosMu(schema)";
assert SozceIsleri.doluMu(tabloAdi) : "SozceIsleri.bosMu(tabloAdi)";
assert SozceIsleri.doluMu(veritabaniUrl) : "SozceIsleri.bosMu(veritabaniUrl)";
assert SozceIsleri.doluMu(kullaniciAdi) : "SozceIsleri.bosMu(kullaniciAdi)";
oracle.jdbc.OracleDriver oracleDriver = new oracle.jdbc.OracleDriver();
lock();
errorDatabase = new HataRepository(oracleDriver, veritabaniUrl,kullaniciAdi,sifreKodu,schema,tabloAdi);
openLock();
if(errorDatabase == null)
throw new BosIsaretciHatasi(MesajIsleri.<IErrorRepository>getirBosGeliyorMesaji(IErrorRepository.class));
return errorDatabase;
} catch (BosIsaretciHatasi | BasarisizIslemHatasi hata) {
throw hata;
}finally {
openLock();
}
}
"DependencyResolverParameter" is an enum and it only has the value "UYGULAMAANAHTAR". (APPLICATION KEY)
I call this method like that:
int applicationKey = 4;
HashMap<DependencyResolverParameter,String> map = new HashMap<DependencyResolverParameter,String>();
map.put(DependencyResolverParameter.APPLICATIONKEY, Integer.toString(applicationKey));
IExceptionService service = DependencyResolver.<IExceptionService>resolve(IExceptionService.class, map);
I define all necessary dependencies within the project. In the code above, the "service" variable is not null as expected and it writes trial error successfully whenever I write it inside a main method as below:
public static void main(String[] args) {
int applicationKey = 4;
HashMap<DependencyResolverParameter,String> map = new HashMap<DependencyResolverParameter,String>();
map.put(DependencyResolverParameter.APPLICATIONKEY, Integer.toString(applicationKey));
IExceptionService servis = DependencyResolver.<IExceptionService>resolve(IExceptionService.class, map);
System.out.println(String.format("is it null? %b", servis == null));
servis.report(new Exception("trial error. Do not care."));
}
However, when I write the code inside JUnit4 test I am getting "NullPointerException" as below:
#Test
public void getHataService_001() {
try
{
int applicationKey= 4;
HashMap<DependencyResolverParameter,String> map = new HashMap<DependencyResolverParameter,String>();
map.put(DependencyResolverParameter.APPLICATIONKEY, Integer.toString(applicationKey));
IExceptionService servis = DependencyResolver.<IExceptionService>resolve(IExceptionService.class, map);
assertNotNull(servis);
} catch (Exception exception) {
fail(exception.getMessage());
}
}
How can I solve that problem? Thanks in advance.
From the stacktrace, I deduce that there is a problem of reading settings from configuration class.
junit dependency :
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
stacktrace:
m:java.lang.NullPointerExceptionlm:nullst:hata.businesslayer.dependencyresolvers.DependencyResolver.resolve(DependencyResolver.java:92)
kisi.core.crosscuttingconcern.exceptionhandling.HataIsleri.getHataService(HataIsleri.java:229)
kisi.core.crosscuttingconcern.exceptionhandling.HataIsleri.raporlaHata(HataIsleri.java:75)
test.hata.HataServisTesti.getHataService_001(HataServisTesti.java:62)
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.base/java.lang.reflect.Method.invoke(Method.java:567)org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
2020/07/09 14:41:34
m:java.lang.NullPointerException
lm:nullst:hata.businesslayer.dependencyresolvers.DependencyResolver.resolve(DependencyResolver.java:92)
test.hata.HataServisTesti.getHataService_001(HataServisTesti.java:56)
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.base/java.lang.reflect.Method.invoke(Method.java:567)
org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)2020/07/09 14:41:34
It is because of an apache maven project inside a project. When I remove the apache maven project, it works.
Related
For below method i am writing JUnit testcase for sonarqube coverage.
#Transformer
public Object errorUnWrapper(Message<?> message) {
String value = "";
try {
if (!errorFlag) {
value = errorTransform(ESBConstants.SYSTEMERRCODE, ESBConstants.SYSTEMERROR, ESBConstants.SYSTEMTEXT);
} else {
value = getbankholiday.getErrorMessage();
}
} catch (Exception e) {
getbankholiday.getE2EObject().logMessage("3005", "Error Occurred in error unwrapper");
value = ESBConstants.SYSTEMERRORXML;
}
MessageHeaders headers = ((MessagingException) message.getPayload()).getFailedMessage().getHeaders();
return MessageBuilder.withPayload(value).copyHeaders(message.getHeaders())
.copyHeadersIfAbsent(headers).setHeader(ESBConstants.CONTENTTYPE, ESBConstants.CONTENTVALUE)
.build();
}
JUnit testcase:
#Test
void testErrorUnWrapper() throws IOException {
String xml = FileUtils.readFileToString(new File("src/test/resources/JunitTestCases/input/TC02_wltRequest.xml"),
"UTF-8");
test = MessageBuilder.withPayload(xml).build();
errorTransform.errorUnWrapper(test);
Assertions.assertTrue(true);
}
but, unable to mock or test the below line in JUnit testcase.
MessageHeaders headers = ((MessagingException) message.getPayload()).getFailedMessage().getHeaders();
Exception:
java.lang.ClassCastException: java.lang.String cannot be cast to org.springframework.messaging.MessagingException
at com.bt.or.esb.exceptions.ErrorTransform.errorUnWrapper(ErrorTransform.java:75)
at com.bt.or.esb.exceptions.ErrorTransformTest.testErrorUnWrapper(ErrorTransformTest.java:87)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Your productive code
(MessagingException) message.getPayload()
means you expect message.payload to be a MessagingException. But in your test, you create
MessageBuilder.withPayload(xml).build()
so payload will be a string. You need to build a message with a MessagingException as payload.
This usually means you need to refactor.
except for value everything else is common in if else and catch blocks
why don't you compute value first and then execute below two lines?
MessageHeaders headers = ((MessagingException) message.getPayload()).getFailedMessage().getHeaders();
return MessageBuilder.withPayload(value).copyHeaders(message.getHeaders()).copyHeadersIfAbsent(headers)
.setHeader(ESBConstants.CONTENTTYPE, ESBConstants.CONTENTVALUE).build();
TL:DR; When running tests with different #ResourceArgs, the configuration of different tests get thrown around and override others, breaking tests meant to run with specific configurations.
So, I have a service that has tests that run in different configuration setups. The main difference at the moment is the service can either manage its own authentication or get it from an external source (Keycloak).
I firstly control this using test profiles, which seem to work fine. Unfortunately, in order to support both cases, the ResourceLifecycleManager I have setup supports setting up a Keycloak instance and returns config values that break the config for self authentication (This is due primarily to the fact that I have not found out how to get the lifecycle manager to determine on its own what profile or config is currently running. If I could do this, I think I would be much better off than using #ResourceArg, so would love to know if I missed something here).
To remedy this shortcoming, I have attempted to use #ResourceArgs to convey to the lifecycle manager when to setup for external auth. However, I have noticed some really odd execution timings and the config that ends up at my test/service isn't what I intend based on the test class's annotations, where it is obvious the lifecycle manager has setup for external auth.
Additionally, it should be noted that I have my tests ordered such that the profiles and configs shouldn't be running out of order; all the tests that don't care are run first, then the 'normal' tests with self auth, then the tests with the external auth profile. I can see this working appropriately when I run in intellij, and the fact I can tell the time is being taken to start up the new service instance between the test profiles.
Looking at the logs when I throw a breakpoint in places, some odd things are obvious:
When breakpoint on an erring test (before the external-configured tests run)
The start() method of my TestResourceLifecycleManager has been called twice
The first run ran with Keycloak starting, would override/break config
though the time I would expect to need to be taken to start up keycloak not happening, a little confused here
The second run is correct, not starting keycloak
The profile config is what is expected, except for what the keycloak setup would override
When breakpoint on an external-configured test (after all self-configured tests run):
The start() method has now been called 4 times; appears that things were started in the same order as before again for the new run of the app
There could be some weirdness in how Intellij/Gradle shows logs, but I am interpreting this as:
Quarkus initting the two instances of LifecycleManager when starting the app for some reason, and one's config overrides the other, causing my woes.
The lifecycle manager is working as expected; it appropriately starts/ doesn't start keycloak when configured either way
At this point I can't tell if I'm doing something wrong, or if there's a bug.
Test class example for self-auth test (same annotations for all tests in this (test) profile):
#Slf4j
#QuarkusTest
#QuarkusTestResource(TestResourceLifecycleManager.class)
#TestHTTPEndpoint(Auth.class)
class AuthTest extends RunningServerTest {
Test class example for external auth test (same annotations for all tests in this (externalAuth) profile):
#Slf4j
#QuarkusTest
#TestProfile(ExternalAuthTestProfile.class)
#QuarkusTestResource(value = TestResourceLifecycleManager.class, initArgs = #ResourceArg(name=TestResourceLifecycleManager.EXTERNAL_AUTH_ARG, value="true"))
#TestHTTPEndpoint(Auth.class)
class AuthExternalTest extends RunningServerTest {
ExternalAuthTestProfile extends this, providing the appropriate profile name:
public class NonDefaultTestProfile implements QuarkusTestProfile {
private final String testProfile;
private final Map<String, String> overrides = new HashMap<>();
protected NonDefaultTestProfile(String testProfile) {
this.testProfile = testProfile;
}
protected NonDefaultTestProfile(String testProfile, Map<String, String> configOverrides) {
this(testProfile);
this.overrides.putAll(configOverrides);
}
#Override
public Map<String, String> getConfigOverrides() {
return new HashMap<>(this.overrides);
}
#Override
public String getConfigProfile() {
return testProfile;
}
#Override
public List<TestResourceEntry> testResources() {
return QuarkusTestProfile.super.testResources();
}
}
Lifecycle manager:
#Slf4j
public class TestResourceLifecycleManager implements QuarkusTestResourceLifecycleManager {
public static final String EXTERNAL_AUTH_ARG = "externalAuth";
private static volatile MongodExecutable MONGO_EXE = null;
private static volatile KeycloakContainer KEYCLOAK_CONTAINER = null;
private boolean externalAuth = false;
public synchronized Map<String, String> startKeycloakTestServer() {
if(!this.externalAuth){
log.info("No need for keycloak.");
return Map.of();
}
if (KEYCLOAK_CONTAINER != null) {
log.info("Keycloak already started.");
} else {
KEYCLOAK_CONTAINER = new KeycloakContainer()
// .withEnv("hello","world")
.withRealmImportFile("keycloak-realm.json");
KEYCLOAK_CONTAINER.start();
log.info(
"Test keycloak started at endpoint: {}\tAdmin creds: {}:{}",
KEYCLOAK_CONTAINER.getAuthServerUrl(),
KEYCLOAK_CONTAINER.getAdminUsername(),
KEYCLOAK_CONTAINER.getAdminPassword()
);
}
String clientId;
String clientSecret;
String publicKey = "";
try (
Keycloak keycloak = KeycloakBuilder.builder()
.serverUrl(KEYCLOAK_CONTAINER.getAuthServerUrl())
.realm("master")
.grantType(OAuth2Constants.PASSWORD)
.clientId("admin-cli")
.username(KEYCLOAK_CONTAINER.getAdminUsername())
.password(KEYCLOAK_CONTAINER.getAdminPassword())
.build();
) {
RealmResource appsRealmResource = keycloak.realms().realm("apps");
ClientRepresentation qmClientResource = appsRealmResource.clients().findByClientId("quartermaster").get(0);
clientSecret = qmClientResource.getSecret();
log.info("Got client id \"{}\" with secret: {}", "quartermaster", clientSecret);
//get private key
for (KeysMetadataRepresentation.KeyMetadataRepresentation curKey : appsRealmResource.keys().getKeyMetadata().getKeys()) {
if (!SIG.equals(curKey.getUse())) {
continue;
}
if (!"RSA".equals(curKey.getType())) {
continue;
}
String publicKeyTemp = curKey.getPublicKey();
if (publicKeyTemp == null || publicKeyTemp.isBlank()) {
continue;
}
publicKey = publicKeyTemp;
log.info("Found a relevant key for public key use: {} / {}", curKey.getKid(), publicKey);
}
}
// write public key
// = new File(TestResourceLifecycleManager.class.getResource("/").toURI().toString() + "/security/testKeycloakPublicKey.pem");
File publicKeyFile;
try {
publicKeyFile = File.createTempFile("oqmTestKeycloakPublicKey",".pem");
// publicKeyFile = new File(TestResourceLifecycleManager.class.getResource("/").toURI().toString().replace("/classes/java/", "/resources/") + "/security/testKeycloakPublicKey.pem");
log.info("path of public key: {}", publicKeyFile);
// if(publicKeyFile.createNewFile()){
// log.info("created new public key file");
//
// } else {
// log.info("Public file already exists");
// }
try (
FileOutputStream os = new FileOutputStream(
publicKeyFile
);
) {
IOUtils.write(publicKey, os, UTF_8);
} catch (IOException e) {
log.error("Failed to write out public key of keycloak: ", e);
throw new IllegalStateException("Failed to write out public key of keycloak.", e);
}
} catch (IOException e) {
log.error("Failed to create public key file: ", e);
throw new IllegalStateException("Failed to create public key file", e);
}
String keycloakUrl = KEYCLOAK_CONTAINER.getAuthServerUrl().replace("/auth", "");
return Map.of(
"test.keycloak.url", keycloakUrl,
"test.keycloak.authUrl", KEYCLOAK_CONTAINER.getAuthServerUrl(),
"test.keycloak.adminName", KEYCLOAK_CONTAINER.getAdminUsername(),
"test.keycloak.adminPass", KEYCLOAK_CONTAINER.getAdminPassword(),
//TODO:: add config for server to talk to
"service.externalAuth.url", keycloakUrl,
"mp.jwt.verify.publickey.location", publicKeyFile.getAbsolutePath()
);
}
public static synchronized void startMongoTestServer() throws IOException {
if (MONGO_EXE != null) {
log.info("Flapdoodle Mongo already started.");
return;
}
Version.Main version = Version.Main.V4_0;
int port = 27018;
log.info("Starting Flapdoodle Test Mongo {} on port {}", version, port);
IMongodConfig config = new MongodConfigBuilder()
.version(version)
.net(new Net(port, Network.localhostIsIPv6()))
.build();
try {
MONGO_EXE = MongodStarter.getDefaultInstance().prepare(config);
MongodProcess process = MONGO_EXE.start();
if (!process.isProcessRunning()) {
throw new IOException();
}
} catch (Throwable e) {
log.error("FAILED to start test mongo server: ", e);
MONGO_EXE = null;
throw e;
}
}
public static synchronized void stopMongoTestServer() {
if (MONGO_EXE == null) {
log.warn("Mongo was not started.");
return;
}
MONGO_EXE.stop();
MONGO_EXE = null;
}
public synchronized static void cleanMongo() throws IOException {
if (MONGO_EXE == null) {
log.warn("Mongo was not started.");
return;
}
log.info("Cleaning Mongo of all entries.");
}
#Override
public void init(Map<String, String> initArgs) {
this.externalAuth = Boolean.parseBoolean(initArgs.getOrDefault(EXTERNAL_AUTH_ARG, Boolean.toString(this.externalAuth)));
}
#Override
public Map<String, String> start() {
log.info("STARTING test lifecycle resources.");
Map<String, String> configOverride = new HashMap<>();
try {
startMongoTestServer();
} catch (IOException e) {
log.error("Unable to start Flapdoodle Mongo server");
}
configOverride.putAll(startKeycloakTestServer());
return configOverride;
}
#Override
public void stop() {
log.info("STOPPING test lifecycle resources.");
stopMongoTestServer();
}
}
The app can be found here: https://github.com/Epic-Breakfast-Productions/OpenQuarterMaster/tree/main/software/open-qm-base-station
The tests are currently failing in the ways I am describing, so feel free to look around.
Note that to run this, you will need to run ./gradlew build publishToMavenLocal in https://github.com/Epic-Breakfast-Productions/OpenQuarterMaster/tree/main/software/libs/open-qm-core to install a dependency locally.
Github issue also tracking this: https://github.com/quarkusio/quarkus/issues/22025
Any use of #QuarkusTestResource() without restrictToAnnotatedClass set to true, means that the QuarkusTestResourceLifecycleManager will be applied to all tests no matter where the annotation is placed.
Hope restrictToAnnotatedClass will solve the problem.
I have a class that scans a column from a dynamo db table, whilst using the aws sdk for java(main method taken out for simplicity):
public class fetchCmdbColumn {
public static List<String> CMDB(String tableName, String tableColumn) throws Exception {
DynamoDbClient client = DynamoDbClient.builder()
.region(Region.EU_WEST_1)
.build();
List<String> ListValues = new ArrayList<>();
try {
ScanRequest scanRequest = ScanRequest.builder()
.tableName(tableName)
.build();
ScanResponse response = client.scan(scanRequest);
for (Map<String, AttributeValue> item : response.items()){
Set<String> keys = item.keySet();
for (String key : keys) {
if (key == tableColumn) {
ListValues.add(item.get(key).s()) ;
}
}
}
//To check what is being returned, comment out below
// System.out.println(ListValues);
} catch (DynamoDbException e){
e.printStackTrace();
System.exit(1);
}
client.close();
return ListValues;
}
}
I also have a junit tests created for that class:
public class fetchCMDBTest {
// Define the data members required for the test
private static String tableName = "";
private static String tableColumn = "";
#BeforeAll
public static void setUp() throws IOException {
// Run tests on Real AWS Resources
try (InputStream input = fetchCMDBTest.class.getClassLoader().getResourceAsStream("config.properties")) {
Properties prop = new Properties();
if (input == null) {
System.out.println("Sorry, unable to find config.properties");
return;
}
//load a properties file from class path, inside static method
prop.load(input);
// Populate the data members required for all tests
tableName = prop.getProperty("environment_list");
tableColumn = prop.getProperty("env_name");
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Test
void fetchCMDBtable() throws Exception{
try {
fetchCMDB.CMDB(tableName, tableColumn);
System.out.println("Test 1 passed");
} catch (Exception e) {
System.out.println("Test 1 failed!");
e.printStackTrace();
}
}
}
When i run the test using mvn test I get the error:
software.amazon.awssdk.core.exception.SdkClientException: Multiple HTTP implementations were found on the classpath ,
even though I have only declared the client builder once in the class.
What am i missing?
I run the UNIT tests from the IntelliJ IDE. I find using the IDE works better then from the command line. Once I setup the config.properties file that contains the values for the tests and run them, all tests pass -- as shown here:
In fact - we test all Java V2 code examples in this manner to ensure they all work.
I also tested all DynamoDB examples from the command line using mvn test . All passed:
Amend your test to build a single instance of the DynamoDB client and then as your first test, make sure it was created successfully. See if this works for you. Once you get this working, add more tests!
public class DynamoDBTest {
private static DynamoDbClient ddb;
#BeforeAll
public static void setUp() throws IOException {
// Run tests on Real AWS Resources
Region region = Region.US_WEST_2;
ddb = DynamoDbClient.builder().region(region).build();
try (InputStream input = DynamoDBTest.class.getClassLoader().getResourceAsStream("config.properties")) {
Properties prop = new Properties();
if (input == null) {
System.out.println("Sorry, unable to find config.properties");
return;
}
//load a properties file from class path, inside static method
prop.load(input);
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Test
#Order(1)
public void whenInitializingAWSService_thenNotNull() {
assertNotNull(ddb);
System.out.println("Test 1 passed");
}
Turns out my pom file contained other clients, so had to remove the likes of :
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<exclusions>
<exclusion>
<groupId>software.amazon.awssdk</groupId>
<artifactId>netty-nio-client</artifactId>
</exclusion>
<exclusion>
<groupId>software.amazon.awssdk</groupId>
<artifactId>apache-client</artifactId>
</exclusion>
</exclusions>
</dependency>
and replaced them with :
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>aws-crt-client</artifactId>
<version>2.14.13-PREVIEW</version>
</dependency>
as mentioned in https://aws.amazon.com/blogs/developer/introducing-aws-common-runtime-http-client-in-the-aws-sdk-for-java-2-x/
as a complement to the other answers, for me only worked the option 4 from the reference.
Option 4: Change the default HTTP client using a system property in Java code.
I defined it on the setUp() method of my integration test using JUnit 5.
#BeforeAll
public static void setUp() {
System.setProperty(
SdkSystemSetting.SYNC_HTTP_SERVICE_IMPL.property(),
"software.amazon.awssdk.http.apache.ApacheSdkHttpService");
}
and because I am using gradle:
implementation ("software.amazon.awssdk:s3:${awssdk2Version}") {
exclude group: 'software.amazon.awssdk', module: 'netty-nio-client'
exclude group: 'software.amazon.awssdk', module: 'apache-client'
}
implementation "software.amazon.awssdk:aws-crt-client:2.17.71-PREVIEW"
I am new to SAP JCo I have requirement to call multiple SAP System using SAP Jco. But I am unable to connect multiple sap system at the same time......
Here is code :
package com.sap.test;
import java.util.Properties;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.JCoRepository;
import com.sap.conn.jco.ext.DestinationDataProvider;
import com.sap.conn.jco.ext.Environment;
import com.sap.utils.MyDestinationDataProvider;
import com.sap.utils.SapSystem;
public class TestMultipleSAPConnection {
public static Properties properties;
public static JCoDestination dest = null;
public static JCoRepository repos = null;
public static SapSystem system = null;
String SAP_SERVER = "SAP_SERVER";
MyDestinationDataProvider myProvider = null;
public static void main(String[] args) throws JCoException {
getConnection_CRM();
getConnection_R3();
}
public static JCoDestination getConnection_R3() {
boolean connR3_flag = true;
JCoDestination dest = null;
JCoRepository repos = null;
String SAP_SERVER = "SAP_SERVER";
Properties properties = new Properties();
SapSystem system = new SapSystem();
system.setClient("100");
system.setHost("r3devsvr.myweb.com");
system.setLanguage("en");
system.setSystemNumber("00");
system.setUser("SAP-R3-USER");
system.setPassword("init1234");
properties.setProperty("jco.client.ashost", system.getHost());
properties.setProperty("jco.client.sysnr", system.getSystemNumber());
properties.setProperty("jco.client.client", system.getClient());
properties.setProperty("jco.client.user", system.getUser());
properties.setProperty("jco.client.passwd", system.getPassword());
properties.setProperty("jco.client.lang", system.getLanguage());
System.out.println("******* Connection Parameter Set *******");
MyDestinationDataProvider myProvider = new MyDestinationDataProvider();
System.out.println("******* Destination Provider Set *******");
myProvider.changePropertiesForABAP_AS(properties);
if (!Environment.isDestinationDataProviderRegistered()) {
System.out.println("Registering Destination Provider R3");
Environment.registerDestinationDataProvider((DestinationDataProvider) myProvider);
}else{
System.out.println("Destination Provider already set..R3");
connR3_flag = false;
}
try {
dest = JCoDestinationManager.getDestination((String) SAP_SERVER);
repos = dest.getRepository();
if (repos == null) {
System.out.println("Repos is null.....");
} else {
System.out.println("Repos is not null.....");
}
System.out.println("After getting repos...");
if(connR3_flag){
System.out.println("R3 Connection Successfull...");
}
} catch (Exception ex) {
System.out.println(ex);
}
return dest;
}
public static JCoDestination getConnection_CRM() {
boolean connCRM_flag = true;
JCoDestination dest = null;
JCoRepository repos = null;
String SAP_SERVER = "SAP_SERVER";
Properties properties = new Properties();
SapSystem system = new SapSystem();
system.setClient("200");
system.setHost("crmdevsvr.myweb.com");
system.setLanguage("en");
system.setSystemNumber("00");
system.setUser("SAP-CRM-USER");
system.setPassword("init1234");
properties.setProperty("jco.client.ashost", system.getHost());
properties.setProperty("jco.client.sysnr", system.getSystemNumber());
properties.setProperty("jco.client.client", system.getClient());
properties.setProperty("jco.client.user", system.getUser());
properties.setProperty("jco.client.passwd", system.getPassword());
properties.setProperty("jco.client.lang", system.getLanguage());
System.out.println("******* Connection Parameter Set *******");
MyDestinationDataProvider myProvider = new MyDestinationDataProvider();
System.out.println("******* Destination Provider Set *******");
myProvider.changePropertiesForABAP_AS(properties);
if (!Environment.isDestinationDataProviderRegistered()) {
System.out.println("Registering Destination Provider CRM");
Environment.registerDestinationDataProvider((DestinationDataProvider) myProvider);
}else{
System.out.println("Destination Provider already set..CRM");
connCRM_flag = false;
}
try {
dest = JCoDestinationManager.getDestination((String) SAP_SERVER);
repos = dest.getRepository();
if (repos == null) {
System.out.println("Repos is null.....");
} else {
System.out.println("Repos is not null.....");
}
System.out.println("After getting repos...");
if(connCRM_flag){
System.out.println("CRM Connection Successfull...");
}
} catch (Exception ex) {
System.out.println(ex);
}
return dest;
}
}
The JCo JavaDoc documentation says:
Only one implementation of DestinationDataProvider can be registered.
For registering another implementation the infrastructure has first to
unregister the implementation that is currently registered. It is not
recommended to permanently exchange DestinationDataProvider
registrations. The one registered instance should globally manage all
destination configurations for the whole infrastructure environment.
So you have to register ONE instance of the DestinationDataProvider, this is an instance of your class MyDestinationDataProvider.
This implementation needs to manage and store ALL the different logon configurations for all your SAP systems, accessible via a distinct destination name string. A simple HashMap<String, Properties> would be a sufficient storage form for this. Add the two Properties instances with distinct destination name strings to the HashMap and return the Properties instance associated to the passed destinationName from method MyDestinationDataProvider.getDestinationProperties(String destinationName).
So you can access the desired JCoDestination instance targeting any SAP system via its specific destination name. In your example you used "SAP_SERVER" for both destination configurations which won't work. For example, use the SAP System ID (SID) as the destination name (key) instead.
If using the names from your example, then JCoDestinationManager.getDestination("CRM") would return the JCoDestination instance for system "CRM" and JCoDestinationManager.getDestination("R3") the JCoDestination for system "R3". And both can be used independently and simultaneously. That's it.
Sharing with you a solution I recently came up with for this exact problem. 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've written a bean class containing a HashMultiMap (from the Guava library). I would like to XML encode the bean using the JRE's XMLEncoder. Using a custom PersistenceDelegate I've successfully written the bean to file. However, when I attempt to deserialize the XML I get the exception:
java.lang.NoSuchMethodException: <unbound>=HashMultimap.put("pz1", "pz2")
What am I doing wrong?
// create the bean
SomeBean sb = new SomeBean();
// add some data
HashMultimap<String, String> stateMap = HashMultimap.create();
stateMap.put("pz1", "pz2");
stateMap.put("pz3", "pz4");
sb.setStateMap(stateMap);
// encode as xml
FileOutputStream os = new FileOutputStream("myXMLFile.xml");
XMLEncoder encoder = new XMLEncoder(os);
encoder.setPersistenceDelegate(HashMultimap.class, new CustomPersistenceDelegate());
encoder.writeObject(sb);
// decode the xml
FileInputStream is = new FileInputStream("myXMLFile.xml");
XMLDecoder decoder = new XMLDecoder(is);
Object deSerializedObject = decoder.readObject();
class CustomPersistenceDelegate extends DefaultPersistenceDelegate
{
protected Expression instantiate(Object oldInstance, Encoder out)
{
return new Expression(oldInstance, oldInstance.getClass(), "create", null);
}
protected void initialize(Class<?> type, Object oldInstance, Object newInstance,
Encoder out)
{
super.initialize(type, oldInstance, newInstance, out);
com.google.common.collect.HashMultimap<String, String> m =
(com.google.common.collect.HashMultimap) oldInstance;
for (Map.Entry<String, String> entry : m.entries())
{
out.writeStatement(new Statement(oldInstance, "put",
new Object[] { entry.getKey(), entry.getValue() }));
}
}
}
public class SomeBean
{
private HashMultimap<String, String> stateMap;
public HashMultimap<String, String> getStateMap()
{
return stateMap;
}
public void setStateMap(HashMultimap<String, String> stateMap)
{
this.stateMap = stateMap;
}
}
I don't have a solution (yet). But here is something which at least clarifies the problem. It seems that some change made in Java 7 build 15 and higher has broken the method look up that your Statement requires. If you add an ExceptionListener to the XmlEncoder, it gives you a better idea of how this is failing:
encoder.setExceptionListener(new ExceptionListener() {
#Override
public void exceptionThrown(Exception e) {
System.out.println("got exception. e=" + e);
e.printStackTrace();
}
});
You will see a full stacktrace then:
java.lang.Exception: Encoder: discarding statement HashMultimap.put(Object, Object);
at java.beans.Encoder.writeStatement(Encoder.java:306)
at java.beans.XMLEncoder.writeStatement(XMLEncoder.java:400)
at test2.XmlEncoderTest$CustomPersistenceDelegate.initialize(XmlEncoderTest.java:83)
at java.beans.PersistenceDelegate.writeObject(PersistenceDelegate.java:118)
at java.beans.Encoder.writeObject(Encoder.java:74)
at java.beans.XMLEncoder.writeObject(XMLEncoder.java:327)
at java.beans.Encoder.writeExpression(Encoder.java:330)
at java.beans.XMLEncoder.writeExpression(XMLEncoder.java:454)
at java.beans.PersistenceDelegate.writeObject(PersistenceDelegate.java:115)
at java.beans.Encoder.writeObject(Encoder.java:74)
at java.beans.XMLEncoder.writeObject(XMLEncoder.java:327)
at java.beans.Encoder.writeExpression(Encoder.java:330)
at java.beans.XMLEncoder.writeExpression(XMLEncoder.java:454)
at java.beans.DefaultPersistenceDelegate.doProperty(DefaultPersistenceDelegate.java:194)
at java.beans.DefaultPersistenceDelegate.initBean(DefaultPersistenceDelegate.java:253)
at java.beans.DefaultPersistenceDelegate.initialize(DefaultPersistenceDelegate.java:400)
at java.beans.PersistenceDelegate.writeObject(PersistenceDelegate.java:118)
at java.beans.Encoder.writeObject(Encoder.java:74)
at java.beans.XMLEncoder.writeObject(XMLEncoder.java:327)
at java.beans.Encoder.writeExpression(Encoder.java:330)
at java.beans.XMLEncoder.writeExpression(XMLEncoder.java:454)
at java.beans.PersistenceDelegate.writeObject(PersistenceDelegate.java:115)
at java.beans.Encoder.writeObject(Encoder.java:74)
at java.beans.XMLEncoder.writeObject(XMLEncoder.java:327)
at java.beans.Encoder.writeObject1(Encoder.java:258)
at java.beans.Encoder.cloneStatement(Encoder.java:271)
at java.beans.Encoder.writeStatement(Encoder.java:301)
at java.beans.XMLEncoder.writeStatement(XMLEncoder.java:400)
at java.beans.XMLEncoder.writeObject(XMLEncoder.java:330)
...
Caused by: java.lang.NoSuchMethodException: HashMultimap.put(Object, Object);
at java.beans.Statement.invokeInternal(Statement.java:313)
at java.beans.Statement.access$000(Statement.java:58)
at java.beans.Statement$2.run(Statement.java:185)
at java.security.AccessController.doPrivileged(Native Method)
at java.beans.Statement.invoke(Statement.java:182)
at java.beans.Statement.execute(Statement.java:173)
at java.beans.Encoder.writeStatement(Encoder.java:304)
... 51 more
The Caused by section shows that it failed to locate the put method. It looks to me like this happens because it can't match the method signature properly any more. It fails in the java beans MethodFinder, but since the source code is not included in JDK, I couldn't track it down well enough.
If I can find exact cause, I will update this. Just wanted to provide you with more information in the meantime.
UPDATE
I think it's a bug in these later versions. Here is a unit test which exposes the bug (or unexpected behavior) more directly. The failure below is exactly what is happening in your code:
#Test
public void testMethodFinder() throws Exception {
Method m0 = MethodFinder.findMethod(this.getClass(), "setUp", new Class<?>[0]);
assertNotNull(m0);
// this is okay, because method is declared in the type referenced
Method m = MethodFinder.findMethod(Multimap.class, "put", new Class<?>[] { Object.class, Object.class });
assertNotNull(m);
try {
// this fails, apparently because method is not declared in this subclass (is inherited from parent class)
Method m2 = MethodFinder.findMethod(HashMultimap.class, "put", new Class<?>[] { Object.class, Object.class });
assertNotNull(m2);
} catch (Exception e) {
System.out.println("got exception. e=" + e);
}
}