I'm using the following java configuration for my Neo4j (using SDN 4.0.0.RELEASE) based application, with unit tests:
...
#Bean
public Neo4jServer neo4jServer() {
return new InProcessServer();
}
#Bean
public SessionFactory getSessionFactory() {
Neo4jRequest<String> neo4jRequest = new DefaultRequest(httpClient);
String json = "{" + "\"name\" : \"node_auto_index\", " + "\"config\" : {" + "\"type\" : \"fulltext\", "
+ "\"provider\" : \"lucene\"" + "}" + "}";
neo4jRequest.execute(neo4jServer().url() + "db/data/index/node/", json);
return new SessionFactory("org.myproject.domain");
}
...
I created on getSessionFactory() a full-text node_auto_index. I'm actually missing how to configure my current in-memory istance of Neo4j, because I need to set those properties:
node_auto_indexing=true
node_keys_indexable=title
I read on "Good Relationships: The Spring Data Neo4j Guide Book" that
InProcessServer is useful for test and development environments, but is not recommended for production use. This implementation will start a new instance of CommunityNeoServer running on an available local port and return the URL needed to connect to it.
Do I need to use CommunityNeoServer instead? Should I use it even if it's deprecated? In this case, how can I configure it for an in-memory database that will support node auto indexing?
If you want to supply additional configuration, you can provide your own implementation of Neo4jServer, like this:
public class AutoIndexTestServer implements Neo4jServer {
final String uri;
public AutoIndexTestServer() {
try {
ServerControls controls = TestServerBuilders.newInProcessBuilder()
.withConfig("dbms.security.auth_enabled", "false")
.withConfig("node_auto_indexing", "true")
.withConfig("node_keys_indexable", "title")
.newServer();
uri = controls.httpURI().toString();
}
catch (Exception e) {
throw new RuntimeException("Could not start inprocess server",e);
}
}
#Override
public String url() {
return uri;
}
#Override
public String username() {
return null;
}
#Override
public String password() {
return null;
}
}
and use it
#Bean
public Neo4jServer neo4jServer() {
return new AutoIndexTestServer();
}
Related
I've recently been trying to configure and set up a spring boot application that will later be run in kubernetes and have multiple pods running of it. The application is meant to download files from a FTP server. I've found some existing code for doing this in Springboot, particularly FtpInboundFileSynchronizer and so I tried set it up and make sure it works. I have a working solution with a ConcurrentMetaDataStore. So my only real question is if it will be fine running it with multiple instances or if I require something additional for it to be run with multiple pods?
My configuration looks something like this:
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = "ftp")
public class FtpConfiguration
{
private final static int PASSIVE_LOCAL_DATA_CONNECTION_MODE = 2;
private final static int DEFAULT_FTP_PORT = 21;
String host;
String username;
String password;
String localDirectory;
String remoteDirectory;
FtpRemoteFileTemplate template;
FtpInboundFileSynchronizer synchronizer;
DataSource templateSource;
#Bean
public ConcurrentMetadataStore metadataStore(DataSource dataSource)
{
var jbdcMetaDatastore = new JdbcMetadataStore(dataSource);
jbdcMetaDatastore.setTablePrefix("INT_");
jbdcMetaDatastore.setRegion("TEMPORARY");
jbdcMetaDatastore.afterPropertiesSet();
return jbdcMetaDatastore;
}
#Bean
public DefaultFtpSessionFactory defaultFtpSessionFactory()
{
DefaultFtpSessionFactory sf = new DefaultFtpSessionFactory();
sf.setHost(host);
sf.setUsername(username);
sf.setPassword(password);
sf.setPort(DEFAULT_FTP_PORT);
sf.setConnectTimeout(5000);
sf.setClientMode(PASSIVE_LOCAL_DATA_CONNECTION_MODE);
return sf;
}
#Bean
FtpRemoteFileTemplate ftpRemoteFileTemplate(DefaultFtpSessionFactory dsf)
{
return new FtpRemoteFileTemplate(dsf);
}
#Bean
FtpInboundFileSynchronizer ftpInboundFileSynchronizer(DefaultFtpSessionFactory dsf)
{
FtpInboundFileSynchronizer ftpInSync = new FtpInboundFileSynchronizer(dsf);
ftpInSync.setRemoteDirectory(remoteDirectory);
ftpInSync.setFilter(ftpFileListFilter());
return ftpInSync;
}
public FileListFilter<FTPFile> ftpFileListFilter()
{
try (ChainFileListFilter<FTPFile> chain = new ChainFileListFilter<>())
{
chain.addFilter(new FtpPersistentAcceptOnceFileListFilter(metadataStore(templateSource), "TEST"));
return chain;
}
catch (IOException e)
{
throw new RuntimeException("Failed to create FtpPersistentAcceptOnceFileListFilter", e);
}
}
}
and then I just call the the SynchronizeToLocalDirectory method.
FtpClient(
FtpRemoteFileTemplate template, FtpInboundFileSynchronizer synchronizer,
#Value("${ftp.remote-directory}") String remoteDirectory,
#Value("${ftp.local-directory}") String localDirectory)
{
this.template = template;
this.synchronizer = synchronizer;
this.remoteDirectory = remoteDirectory;
this.localDirectory = localDirectory;
}
synchronizer.setRemoteDirectory(remoteDirectory);
synchronizer.synchronizeToLocalDirectory(new File(localDirectory));
Would this solution handle multiple applications without problems? Or what else would I need? Does the ConcurrentMetaData store alone make sure this works? (so for example there wouldn't be a conflict/crash if two instances at the same time try to synchronise same directory as they'd both be fine thanks to the metastore being #Transactional).
Your assumption is correct: as long as all your pods are connecting to the same data base, that JdbcMetadataStore will ensure that no concurrent read for the same file are going to happen.
It is not clear, though, why would one use an FtpInboundFileSynchronizer manually, but not via an FtpInboundFileSynchronizingMessageSource and subsequent integration flow, but that's I guess fully different story and question.
On the other hand: why do you ask this question at all? Didn't you try your solution? Isn't docs enough to be sure where and how to go: https://docs.spring.io/spring-integration/docs/current/reference/html/file.html#remote-persistent-flf ?
I've tried to upgrade Spring Boot to 2.2.4.RELEASE version. Everzthing if fine exept problem with CompositeHealthIndicator which is deprecated.
I have this bean method
#Autowired
private HealthAggregator healthAggregator;
#Bean
public HealthIndicator solrHealthIndicator() {
CompositeHealthIndicator composite = new CompositeHealthIndicator(
this.healthAggregator);
composite.addHealthIndicator("solr1", createHealthIndicator(firstHttpSolrClient()));
composite.addHealthIndicator("solr2", createHealthIndicator(secondHttpSolrClient()));
composite.addHealthIndicator("querySolr", createHealthIndicator(queryHttpSolrClient()));
return composite;
}
private CustomSolrHealthIndicator createHealthIndicator(SolrClient source) {
try {
return new CustomSolrHealthIndicator(source);
} catch (Exception ex) {
throw new IllegalStateException("Unable to create helthCheckIndicator for solr client instance.", ex);
}
}
That registers HealthIndicator for 3 instances of SOLR (2 indexing, 1 for query). Everything worked fine until Spring Boot update. After update the method CompositeHealthIndicator.addHealthIndicator is not present, the whole class is marked as Deprecated.
The class which is created in createHealthIndicator is like this:
public class CustomSolrHealthIndicator extends SolrHealthIndicator {
private final SolrClient solrClient;
public CustomSolrHealthIndicator(SolrClient solrClient) {
super(solrClient);
this.solrClient = solrClient;
}
#Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
if (!this.solrClient.getClass().isAssignableFrom(HttpSolrClient.class)) {
super.doHealthCheck(builder);
}
HttpSolrClient httpSolrClient = (HttpSolrClient) this.solrClient;
if (StringUtils.isBlank(httpSolrClient.getBaseURL())) {
return;
}
super.doHealthCheck(builder);
}
}
Is there any easy way to transform the old way how to register the instances of SOLR i want to check if they are up or down at Spring Boot version 2.2.X?
EDIT:
I have tried this:
#Bean
public CompositeHealthContributor solrHealthIndicator() {
Map<String, HealthIndicator> solrIndicators = Maps.newLinkedHashMap();
solrIndicators.put("solr1", createHealthIndicator(firstHttpSolrClient()));
solrIndicators.put("solr2", createHealthIndicator(secondHttpSolrClient()));
solrIndicators.put("querySolr", createHealthIndicator(queryHttpSolrClient()));
return CompositeHealthContributor.fromMap(solrIndicators);
}
private CustomSolrHealthIndicator createHealthIndicator(SolrClient source) {
try {
return new CustomSolrHealthIndicator(source);
} catch (Exception ex) {
throw new IllegalStateException("Unable to create healthCheckIndicator for solr client instance.", ex);
}
}
The CustomSolrHealthIndicator has no changes against start state.
But I cannot create that bean. When calling createHealthIndicator I am getting NoClassDefFoundError
Does anyone know where the problem is?
Looks like you can just use CompositeHealthContributor. It's not much different from what you have already. It appears something like this would work. You could override the functionality to add them one at a time if you'd like, also, which might be preferable if you have a large amount of configuration. Shouldn't be any harm with either approach.
#Bean
public HealthIndicator solrHealthIndicator() {
Map<String, HealthIndicator> solrIndicators;
solrIndicators.put("solr1", createHealthIndicator(firstHttpSolrClient()));
solrIndicators.put("solr2", createHealthIndicator(secondHttpSolrClient()));
solrIndicators.put("querySolr", createHealthIndicator(queryHttpSolrClient()));
return CompositeHealthContributor.fromMap(solrIndicators);
}
Instead of deprecated CompositeHealthIndicator#addHealthIndicator use constructor with map:
#Bean
public HealthIndicator solrHealthIndicator() {
Map<String, HealthIndicator> healthIndicators = new HashMap<>();
healthIndicators.put("solr1", createHealthIndicator(firstHttpSolrClient()));
healthIndicators.put("solr2", createHealthIndicator(secondHttpSolrClient()));
healthIndicators.put("querySolr", createHealthIndicator(queryHttpSolrClient()));
return new CompositeHealthIndicator(this.healthAggregator, healthIndicators);
}
I have written a RESTful API using Apache Jersey. I am using MongoDB as my backend. I used Morphia (v.1.3.4) to map and persist POJO to database. I tried to follow "1 application 1 connection" in my API as recommended everywhere but I am not sure I am successful. I run my API in Tomcat 8. I also ran Mongostat to see the details and connection. At start, Mongostat showed 1 connection to MongoDB server. I tested my API using Postman and it was working fine. I then created a load test in SoapUI where I simulated 100 users per second. I saw the update in Mongostat. I saw there were 103 connections. Here is the gif which shows this behaviour.
I am not sure why there are so many connections. The interesting fact is that number of mongo connection are directly proportional to number of users I create on SoapUI. Why is that? I found other similar questions but I think I have implemented there suggestions.
Mongo connection leak with morphia
Spring data mongodb not closing mongodb connections
My code looks like this.
DatabaseConnection.java
// Some imports
public class DatabaseConnection {
private static volatile MongoClient instance;
private static String cloudhost="localhost";
private DatabaseConnection() { }
public synchronized static MongoClient getMongoClient() {
if (instance == null ) {
synchronized (DatabaseConnection.class) {
if (instance == null) {
ServerAddress addr = new ServerAddress(cloudhost, 27017);
List<MongoCredential> credentialsList = new ArrayList<MongoCredential>();
MongoCredential credentia = MongoCredential.createCredential(
"test", "test", "test".toCharArray());
credentialsList.add(credentia);
instance = new MongoClient(addr, credentialsList);
}
}
}
return instance;
}
}
PourService.java
#Secured
#Path("pours")
public class PourService {
final static Logger logger = Logger.getLogger(Pour.class);
private static final int POUR_SIZE = 30;
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response createPour(String request)
{
WebApiResponse response = new WebApiResponse();
Gson gson = new GsonBuilder().setDateFormat("dd/MM/yyyy HH:mm:ss").create();
String message = "Pour was not created.";
HashMap<String, Object> data = null;
try
{
Pour pour = gson.fromJson(request, Pour.class);
// Storing the pour to
PourRepository pourRepository = new PourRepository();
String id = pourRepository.createPour(pour);
data = new HashMap<String, Object>();
if ("" != id && null != id)
{
data.put("id", id);
message = "Pour was created successfully.";
logger.debug(message);
return response.build(true, message, data, 200);
}
logger.debug(message);
return response.build(false, message, data, 500);
}
catch (Exception e)
{
message = "Error while creating Pour.";
logger.error(message, e);
return response.build(false, message, new Object(),500);
}
}
PourDao.java
public class PourDao extends BasicDAO<Pour, String>{
public PourDao(Class<Pour> entityClass, Datastore ds) {
super(entityClass, ds);
}
}
PourRepository.java
public class PourRepository {
private PourDao pourDao;
final static Logger logger = Logger.getLogger(PourRepository.class);
public PourRepository ()
{
try
{
MongoClient mongoClient = DatabaseConnection.getMongoClient();
Datastore ds = new Morphia().map(Pour.class)
.createDatastore(mongoClient, "tilt45");
pourDao = new PourDao(Pour.class,ds);
}
catch (Exception e)
{
logger.error("Error while creating PourDao", e);
}
}
public String createPour (Pour pour)
{
try
{
return pourDao.save(pour).getId().toString();
}
catch (Exception e)
{
logger.error("Error while creating Pour.", e);
return null;
}
}
}
When I work with Mongo+Morphia I get better results using a Factory pattern for the Datastore and not for the MongoClient, for instance, check the following class:
public DatastoreFactory(String dbHost, int dbPort, String dbName) {
final Morphia morphia = new Morphia();
MongoClientOptions.Builder options = MongoClientOptions.builder().socketKeepAlive(true);
morphia.getMapper().getOptions().setStoreEmpties(true);
final Datastore store = morphia.createDatastore(new MongoClient(new ServerAddress(dbHost, dbPort), options.build()), dbName);
store.ensureIndexes();
this.datastore = store;
}
With that approach, everytime you need a datastore you can use the one provided by the factory. Of course, this can implemented better if you use a framework/library that support factory pattern (e.g.: HK2 with org.glassfish.hk2.api.Factory), and also singleton binding.
Besides, you can check the documentation of MongoClientOptions's builder method, perhaps you can find a better connection control there.
Setup: Spring application deployed on Weblogic 12c, using JNDI lookup to get a datasource to the Oracle Database.
We have multiple services which will be polling the database regularly for new jobs. In order to prevent two services picking the same job we are using a native SELECT FOR UPDATE query in a CrudRepository. The application then takes the resulting job and updates it to PROCESSING instead of WAITING using the CrusRepository.save() method.
The problem is that I can't seem to get the save() to work within the FOR UPDATE transaction (at least this is my current working theory of what goes wrong), and as a result the entire polling freezes until the default 10 minute timeout occurs. I have tried putting #Transactional (with various propagation flags) basically everywhere, but I'm not able to get it to work (#EnableTransactionManagement is activated and working).
Obviously there must be some basic knowledge I'm missing. Is this even a possible setup? Unfortunately, just using #Transactional with a non-native CrudRepository SELECT query is not possible, as it apparently first makes a SELECT to see if the row is locked or not, and only then makes a new SELECT that locks it. Another service could very well pick up the same job in the meanwhile, which is why we need it to lock immediately.
Update in relation to #M. Deinum's comment.: I should perhaps also mention that it's a setup wherein the central component that's doing the polling is a library used by all the other services (therefore the library has #SpringBootApplication, as does each service using it, so double component scanning is certainly present). Furthermore, the service has two separate classes for polling depending on the type of service, with a lot of common code, shared in an AbstractTransactionHelper class. Below I've aggregated some code for the sake of brevity.
The library's main class:
#SpringBootApplication
#EnableTransactionManagement
#EnableJpaRepositories
public class JobsMain {
public static void initializeJobsMain(){
PersistenceProviderResolverHolder.setPersistenceProviderResolver(new PersistenceProviderResolver() {
#Override
public List<PersistenceProvider> getPersistenceProviders() {
return Collections.singletonList(new HibernatePersistenceProvider());
}
#Override
public void clearCachedProviders() {
//Not quite sure what this should do...
}
});
}
#Bean
public JtaTransactionManager transactionManager(){
return new WebLogicJtaTransactionManager();
}
public DataSource dataSource(){
final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
dsLookup.setResourceRef(true);
DataSource dataSource = dsLookup.getDataSource("Jobs");
return dataSource;
}
}
The repository (we're returning a set with only one job as we had some other issues when returning a single object):
public interface JobRepository extends CrudRepository<Job, Integer> {
#Query(value = "SELECT * FROM JOB WHERE JOB.ID IN "
+ "(SELECT ID FROM "
+ "(SELECT * FROM JOB WHERE "
+ "JOB.STATUS = :status1 OR "
+ "JOB.STATUS = :status2 "
+ "ORDER BY JOB.PRIORITY ASC, JOB.CREATED ASC) "
+ "WHERE ROWNUM <= 1) "
+ "FOR UPDATE", nativeQuery = true)
public Set<Job> getNextJob(#Param("status1") String status1, #Param("status2") String status2);
The transaction handling class:
#Service
public class JobManagerTransactionHelper extends AbstractTransactionHelper{
#Transactional
#Override
public QdbJob getNextJobToProcess(){
Set<Job> jobs = null;
try {
jobs = jobRepo.getNextJob(Status.DONE.name(), Status.FAILED.name());
} catch (Exception ex) {
logger.error(ex);
}
return extractSingleJobFromSet(jobs);
}
Update 2: Some more code.
AbstractTransactionHelper:
#Service
public abstract class AbstractTransactionHelper {
#Autowired
QdbJobRepository jobRepo;
#Autowired
ArchivedJobRepository archive;
protected Job extractSingleJobFromSet(Set<Job> jobs){
Job job = null;
if(jobs != null && !jobs.isEmpty()){
for(job job : jobs){
if(this instanceof JobManagerTransactionHelper){
updateJob(job);
}
job = job;
}
}
return job;
}
protected void updateJob(Job job){
updateJob(job, Status.PROCESSING, null);
}
protected void updateJob(Job job, Status status, String serviceMessage){
if(job != null){
if(status != null){
job.setStatus(status);
}
if(serviceMessage != null){
job.setServiceMessage(serviceMessage);
}
saveJob(job);
}
}
protected void saveJob(Job job){
jobRepo.save(job);
archive.save(Job.convertJobToArchivedJob(job));
}
Update 4: Threading. newJob() is implemented by each service that uses the library.
#Service
public class JobManager{
#Autowired
private JobManagerTransactionHelper transactionHelper;
#Autowired
JobListener jobListener;
#Autowired
Config config;
protected final AtomicInteger atomicThreadCounter = new AtomicInteger(0);
protected boolean keepPolling;
protected Future<?> futurePoller;
protected ScheduledExecutorService pollService;
protected ThreadPoolExecutor threadPool;
public boolean start(){
if(!keepPolling){
ThreadFactory pollServiceThreadFactory = new ThreadFactoryBuilder()
.setNamePrefix(config.getService() + "ScheduledPollingPool-Thread").build();
ThreadFactory threadPoolThreadFactory = new ThreadFactoryBuilder()
.setNamePrefix(config.getService() + "ThreadPool-Thread").build();
keepPolling = true;
pollService = Executors.newSingleThreadScheduledExecutor(pollServiceThreadFactory);
threadPool = (ThreadPoolExecutor)Executors.newFixedThreadPool(getConfig().getThreadPoolSize(), threadPoolThreadFactory);
futurePoller = pollService.scheduleWithFixedDelay(getPollTask(), 0, getConfig().getPollingFrequency(), TimeUnit.MILLISECONDS);
return true;
}else{
return false;
}
}
protected Runnable getPollTask() {
return new Runnable(){
public void run(){
try{
while(atomicThreadCounter.get() < threadPool.getMaximumPoolSize() &&
threadPool.getActiveCount() < threadPool.getMaximumPoolSize() &&
keepPolling == true){
Job job = transactionHelper.getNextJobToProcess();
if(job != null){
threadPool.submit(getJobHandler(job));
atomicThreadCounter.incrementAndGet();//threadPool.getActiveCount() isn't updated fast enough the first loop
}else{
break;
}
}
}catch(Exception e){
logger.error(e);
}
}
};
}
protected Runnable getJobHandler(final Job job){
return new Runnable(){
public void run(){
try{
atomicThreadCounter.decrementAndGet();
jobListener.newJob(job);
}catch(Exception e){
logger.error(e);
}
}
};
}
As it turns out, the problem was the WeblogicJtaTransactionManager. My guess is that the FOR UPDATE resulted in a JPA transaction, but upon updating the object in the database, the WeblogicJtaTransactionManager was used, which failed to find an ongoing JTA transaction. Since we're deploying on Weblogic we wrongly assumed we had to use the WeblogicJtaTransactionManager.
Either way, exchanging the TransactionManager to a JpaTransactionManager (and explicitly setting the EntityManagerFactory and DataSource on it) basically solved all problems.
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(entityManagerFactory().getObject());
jpaTransactionManager.setDataSource(dataSource());
jpaTransactionManager.setJpaDialect(new HibernateJpaDialect());
return jpaTransactionManager;
}
Assuming you also have added an EntityManagerFactoryBean which is needed if you want to use multiple datasources in the same project (which we're doing, but not within single transactions, so no need for JTA).
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setJpaVendorAdapter(vendorAdapter);
factoryBean.setPackagesToScan("my.model");
return factoryBean;
}
I am currently developing a small Java Application which needs to read some data from SAP.
Nearly everything is working fine.
I can connect to SAP, I can call a BAPI and get a result and I also can handle the given result. But....
I have two different SAP Systems (System A and System B).
If I start my application and make a connection to System A, everything is fine. But after all data from System A are handled I want to call System B (without stopping / restarting my application). In this situation I can't connect to System B.
I think there must be something wrong with the part where I establish the connection to my SAP System.
Can anybody tell my how to do this right?
This is my Code:
This is how I make a connection (SapLogOn and SapSystem are only wrapper classes for the needed parameters)
private void connectToSap(ISapLogOn logOn, ISapSystem system)
throws JCoException {
connectProperties = new Properties();
connectProperties.setProperty("ACTION", "CREATE");
connectProperties.setProperty(DestinationDataProvider.JCO_DEST, "POOL_DE");
connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST, system.getAsHost());
connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, system.getSysNr());
connectProperties.setProperty(DestinationDataProvider.JCO_CLIENT, system.getClient());
connectProperties.setProperty(DestinationDataProvider.JCO_USER, logOn.getUserName());
connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD, logOn.getPassword());
connectProperties.setProperty(DestinationDataProvider.JCO_LANG, system.getLanguage());
connectProperties.setProperty(DestinationDataProvider.JCO_SAPROUTER, system.getSapRouterString());
connectProperties.setProperty(DestinationDataProvider.JCO_POOL_CAPACITY, system.getPoolCapacity());
connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT, system.getPeakLimit());
MyDestinationDataProvider myProvider = new MyDestinationDataProvider();
if (!com.sap.conn.jco.ext.Environment
.isDestinationDataProviderRegistered()) {
com.sap.conn.jco.ext.Environment
.registerDestinationDataProvider(myProvider);
}
myProvider.changePropertiesForABAP_AS(connectProperties);
}
And here comes part two:
public class MyDestinationDataProvider implements DestinationDataProvider {
public static Logger LOGGER = Logger.getLogger(MyDestinationDataProvider.class.getName());
#SuppressWarnings("unused")
private DestinationDataEventListener eL;
private Hashtable<String, Properties> propertiesTab;
public MyDestinationDataProvider() {
this.propertiesTab = new Hashtable<String, Properties>();
this.eL = new DestinationDataEventListener() {
#Override
public void updated(String arg0) {}
#Override
public void deleted(String arg0) {}
};
}
public Properties getDestinationProperties(String destinationName)
{
if(propertiesTab.containsKey(destinationName)){
return propertiesTab.get(destinationName);
}
LOGGER.error("Destination " + destinationName + " is not available");
throw new RuntimeException("Destination " + destinationName + " is not available");
}
public void setDestinationDataEventListener(DestinationDataEventListener eventListener)
{
this.eL = eventListener;
}
public boolean supportsEvents()
{
return true;
}
void changePropertiesForABAP_AS(Properties pConProps)
{
if(pConProps.getProperty("ACTION").equalsIgnoreCase("CREATE")){
propertiesTab.put(pConProps.getProperty("jco.client.dest"), pConProps);
}
else if(pConProps.getProperty("ACTION").equalsIgnoreCase("DELETE")){
propertiesTab.remove(pConProps.getProperty("jco.client.dest"));
}
}
}
I use Java 6 and JCo3.
Regards LStrike