I have the following interface, who's implementation will encrypt a file at a specific path.
package xx.messaging.fileTransfer;
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.annotation.Payload;
/**
* <H1>FileEncryptionService</H1>
*/
public interface FileEncryptionService {
/**
* Generates a new encrypted filename based on the input, calls the default method and returns
* the encrypted file name
* #param srcFilename
* #return
* #throws Exception
*/
#Gateway
public String encryptFile(#Payload String srcFilename) throws Exception;
/**
* Encrypt the a file
* #param srcFilename The source file name
* #param destFilename The target file name
*/
#Gateway
public void encryptFile(#Payload String srcFilename, #Header("encryptedFilename") String destFilename);
}
The service will be called via the spring integration and is registered in the context as a gateway via
<int:gateway service-interface="lu.scoteqint.messaging.fileTransfer.FileEncryptionService"/>
The implementing bean and service-activator are registered as
<beans:bean id="fileEncryptionService" class="xx.messaging.fileTransfer.impl.CommandLineEncryptionService"/>
<int:service-activator
input-channel="file1"
output-channel="file2"
expression="#fileEncryptionService.encryptFile(payload)"/>
I'm expecting that the message payload is the string path to the file, since the wire-tag log shows
2013-02-05 15:50:26,911 DEBUG [org.springframework.integration.handler.LoggingHandler] (task-scheduler-1) [Payload=.\src\test\resources\test\xx.xml][Headers={timestamp=1360079426907, id=1c3be020-fc6d-42ba-b3f0-5b963f76fb76}]
But it seems the 'service-activator' expression finds a File.
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1004E:(pos 31): Method call: Method encryptFile(java.io.File) cannot be found on lu.scoteqint.messaging.fileTransfer.impl.CommandLineEncryptionService type
at org.springframework.expression.spel.ast.MethodReference.findAccessorForMethod(MethodReference.java:182)
at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:106)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:57)
at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:102)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:102)
at org.springframework.integration.util.AbstractExpressionEvaluator.evaluateExpression(AbstractExpressionEvaluator.java:126)
at org.springframework.integration.util.AbstractExpressionEvaluator.evaluateExpression(AbstractExpressionEvaluator.java:86)
... 30 more
EDIT
The log detail
2013-02-05 16:26:42,737 DEBUG [org.springframework.integration.channel.DirectChannel] (task-scheduler-1) preSend on channel 'file1', message: [Payload=.\src\test\resources\test.xml][Headers={timestamp=1360081602736, id=877407f6-c5a2-4bea-9ec7-f970b09f08a8}]
Is there a way to ensure the parameter is mapped as a String rather than a File?
Related
Is it possible to have a dynamic reply queue with Jms OutboundGateway via DSL?
Jms.inboundGateway(jmsListenerContainer)
.defaultReplyQueueName("queue1 or queue2")
Working Solution using ThreadLocal and DestinationResolver:
private static final ThreadLocal<String> REPLY_QUEUE = new ThreadLocal<>();
IntegrationFlows.from(Jms.inboundGateway(listenerContainer)
.defaultReplyQueueName("defaultQueue1")
.destinationResolver(destinationResolver())
.transform(p -> {
// on some condition, else "defaultQueue1"
REPLY_QUEUE.set("changedToQueue2");
return p;
})
#Bean
public DestinationResolver destinationResolver() {
return (session, destinationName, pubSubDomain) -> session.createQueue(REPLY_QUEUE.get());
}
It is not clear from where you'd like to take that dynamic reply queue name, but there is another option:
/**
* #param destinationResolver the destinationResolver.
* #return the spec.
* #see ChannelPublishingJmsMessageListener#setDestinationResolver(DestinationResolver)
*/
public S destinationResolver(DestinationResolver destinationResolver) {
By default this one is a DynamicDestinationResolver which does only this: return session.createQueue(queueName);. Probably here you can play somehow with your different names to determine.
Another way is to have a JMSReplyTo property set in the request message from the publisher.
UPDATE
Since you cannot rely on a default Reply-To JMS message property, I suggest you to look into a ThreadLocal in your downstream flow where you can place your custom header. Then a custom DestinationResolver can take a look into that ThreadLocal variable for a name to delegate to the same mentioned DynamicDestinationResolver.
<service-activator ref="serviceName" input-channel="request-channel" method="methodName">
<poller task-executor="taskExecutorCustom"/>
</service-activator>
<task:executor id="taskExecutorCustom" pool-size="5-20" queue-capacity="0">
can any one suggest how can I pass the MCD context to the method of service "serviceName"?
The answer is to decorace a Runnable which is going to be performed on that TaskExecutor.
There are many article in the Internet on the matter:
How to use MDC with thread pools?
https://gist.github.com/pismy/117a0017bf8459772771
https://rmannibucau.metawerx.net/post/javaee-concurrency-utilities-mdc-propagation
Also Spring Security provides some solution how to propagate a SecurityContext from one thread to another: https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5/#concurrency
What I would suggest you to take some ideas from those links and use an existing API in the ThreadPoolTaskExecutor:
/**
* Specify a custom {#link TaskDecorator} to be applied to any {#link Runnable}
* about to be executed.
* <p>Note that such a decorator is not necessarily being applied to the
* user-supplied {#code Runnable}/{#code Callable} but rather to the actual
* execution callback (which may be a wrapper around the user-supplied task).
* <p>The primary use case is to set some execution context around the task's
* invocation, or to provide some monitoring/statistics for task execution.
* #since 4.3
*/
public void setTaskDecorator(TaskDecorator taskDecorator) {
So, that your decorator should just have a code like this:
taskExecutor.setTaskDecorator(runnable -> {
Map<String, String> mdc = MDC.getCopyOfContextMap();
return () -> {
MDC.setContextMap(mdc);
runnable.run();
};
});
I am using Java VDM Generator to generate service classes with SAP Cloud SDK archetype project and the version is 3.7.0.
The OData service I used is API_SALES_ORDER_SIMULATION_SRV. I downloaded the metadata from SAP API Business Hub. Actually I also tested the metadata on S/4HANA on-premise 1909. The issue still exists.
The plugin I used to in pom.xml is as below:
<plugin>
<groupId>com.sap.cloud.sdk.datamodel</groupId>
<artifactId>odata-generator-maven-plugin</artifactId>
<version>3.7.0</version>
<executions>
<execution>
<id>generate-consumption</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputDirectory>${project.basedir}/edmx</inputDirectory>
<outputDirectory>${project.build.directory}/vdm</outputDirectory>
<deleteOutputDirectory>true</deleteOutputDirectory>
<defaultBasePath>/sap/opu/odata/sap/</defaultBasePath>
<packageName>com.bosch.testvdm</packageName>
<serviceNameMappingFile>${project.basedir}/serviceNameMappings.properties</serviceNameMappingFile>
<compileScope>COMPILE</compileScope>
</configuration>
</execution>
</executions>
</plugin>
The service interface/class generated are APISALESORDERSIMULATIONSRVService and DefaultAPISALESORDERSIMULATIONSRVService. Some methods are lost in the service. E.g. createSalesOrderSimulationAPI().
/*
* Generated by OData VDM code generator of SAP Cloud SDK in version 3.7.0
*/
package com.bosch.testvdm.services;
import javax.annotation.Nonnull;
import com.bosch.testvdm.namespaces.salesordersimulationsrv.batch.APISALESORDERSIMULATIONSRVServiceBatch;
import com.sap.cloud.sdk.datamodel.odata.helper.batch.BatchService;
/**
* <h3>Details:</h3><table summary='Details'><tr><td align='right'>OData Service:</td><td>API_SALES_ORDER_SIMULATION_SRV</td></tr></table>
*
*/
public interface APISALESORDERSIMULATIONSRVService
extends BatchService<APISALESORDERSIMULATIONSRVServiceBatch>
{
/**
* If no other path was provided via the {#link #withServicePath(String)} method, this is the default service path used to access the endpoint.
*
*/
String DEFAULT_SERVICE_PATH = "/sap/opu/odata/sap/API_SALES_ORDER_SIMULATION_SRV";
/**
* Overrides the default service path and returns a new service instance with the specified service path. Also adjusts the respective entity URLs.
*
* #param servicePath
* Service path that will override the default.
* #return
* A new service instance with the specified service path.
*/
#Nonnull
APISALESORDERSIMULATIONSRVService withServicePath(
#Nonnull
final String servicePath);
}
/*
* Generated by OData VDM code generator of SAP Cloud SDK in version 3.7.0
*/
package com.bosch.testvdm.services;
import javax.annotation.Nonnull;
import javax.inject.Named;
import com.bosch.testvdm.namespaces.salesordersimulationsrv.batch.DefaultAPISALESORDERSIMULATIONSRVServiceBatch;
/**
* <h3>Details:</h3><table summary='Details'><tr><td align='right'>OData Service:</td><td>API_SALES_ORDER_SIMULATION_SRV</td></tr></table>
*
*/
#Named("com.bosch.testvdm.services.DefaultAPISALESORDERSIMULATIONSRVService")
public class DefaultAPISALESORDERSIMULATIONSRVService
implements APISALESORDERSIMULATIONSRVService
{
#Nonnull
private final String servicePath;
/**
* Creates a service using {#link APISALESORDERSIMULATIONSRVService#DEFAULT_SERVICE_PATH} to send the requests.
*
*/
public DefaultAPISALESORDERSIMULATIONSRVService() {
servicePath = APISALESORDERSIMULATIONSRVService.DEFAULT_SERVICE_PATH;
}
/**
* Creates a service using the provided service path to send the requests.
* <p>
* Used by the fluent {#link #withServicePath(String)} method.
*
*/
private DefaultAPISALESORDERSIMULATIONSRVService(
#Nonnull
final String servicePath) {
this.servicePath = servicePath;
}
#Override
#Nonnull
public DefaultAPISALESORDERSIMULATIONSRVService withServicePath(
#Nonnull
final String servicePath) {
return new DefaultAPISALESORDERSIMULATIONSRVService(servicePath);
}
/**
* {#inheritDoc}
*
*/
#Override
#Nonnull
public DefaultAPISALESORDERSIMULATIONSRVServiceBatch batch() {
return new DefaultAPISALESORDERSIMULATIONSRVServiceBatch(this);
}
}
It works fine with SAP Cloud SDK version 3.3.1. All the methods are generated in the service.
By the way, it is weird that it is working fine for some OData services with version 3.7.
Update: Version 3.9.0 of the SAP Cloud SDK is available, containing the fix for this issue.
I had a look at this and found out that a new feature we added between those versions results in unexpected behavior.
For a quick workaround you could try to remove the <annotation> blocks usually found at the end of the your edmx file.
We are looking into a fix for this and I will update this question once we have a fix available.
I have following configuration for KinesisMessageDrivenChannelAdapter, when I remove dynamoDbMetaDataStore as checkpointer, messages are received correctly, but when I add it back records are always empty.
I debugged the code and KinesisMessageDrivenChannelAdapter.processTask() line 776 (version 2.0.0.M2) returns empty records.
UPDATE:
public DynamoDbMetaDataStore dynamoDbMetaDataStore() {
String url = consumerClientProperties.getDynamoDB().getUrl();
final AmazonDynamoDBAsync amazonDynamoDB = AmazonDynamoDBAsyncClientBuilder.standard()
.withEndpointConfiguration(new EndpointConfiguration(
url,
Regions.fromName(awsRegion).getName()))
.withClientConfiguration(new ClientConfiguration()
.withMaxErrorRetry(consumerClientProperties.getDynamoDB().getRetries())
.withConnectionTimeout(consumerClientProperties.getDynamoDB().getConnectionTimeout())).build();
DynamoDbMetaDataStore dynamoDbMetaDataStore = new DynamoDbMetaDataStore(amazonDynamoDB, "consumer-test");
return dynamoDbMetaDataStore;
}
public KinesisMessageDrivenChannelAdapter kinesisInboundChannel(
AmazonKinesis amazonKinesis, String[] streamNames) {
KinesisMessageDrivenChannelAdapter adapter =
new KinesisMessageDrivenChannelAdapter(amazonKinesis, streamNames);
adapter.setConverter(null);
adapter.setOutputChannel(kinesisReceiveChannel());
adapter.setCheckpointStore(dynamoDbMetaDataStore());
adapter.setConsumerGroup(consumerClientProperties.getName());
adapter.setCheckpointMode(CheckpointMode.manual);
adapter.setListenerMode(ListenerMode.record);
adapter.setStartTimeout(10000);
adapter.setDescribeStreamRetries(1);
adapter.setConcurrency(10);
return adapter;
}
Thank you
I recommend you to test your solution with the latest 2.0.0.BUILD-SNAPSHOT.
There is already an option like:
/**
* Specify a {#link LockRegistry} for an exclusive access to provided streams.
* This is not used when shards-based configuration is provided.
* #param lockRegistry the {#link LockRegistry} to use.
* #since 2.0
*/
public void setLockRegistry(LockRegistry lockRegistry) {
where you would need to inject a DynamoDbLockRegistry for better checkpoint management.
For that purpose you would also need to add this dependency:
compile("com.amazonaws:dynamodb-lock-client:1.0.0")
There indeed might be some issues with filtering in that M2 yet...
I try to find how to let JSDoc3 automatically generate links to classes from other modules.
I find it hard to explain in words, so let me give some examples. The following script generates the expected output:
/**
* #constructor
*/
var SomeClass = function(){}
/**
* #param {SomeClass} someParam description
*/
var someFunc = function(someParam){}
That is, JSDoc3 correctly generates a link from the parameter list of someFunc to the class description of SomeClass. However, when I put SomeClass in an external module I can't seem to let JSDoc3 generate the links:
/**
* #file SomeClass.js
* #module SomeClass
*/
/**
* #constructor
*/
exports.SomeClass(){}
/**
* #file main.js
*/
var SomeClass = require('./SomeClass');
/**
* #param {SomeClass} someParam description
*/
function someFunc(someParam){}
Now JSDoc3 correctly generates the documentation for both files, but it doesn't link the parameter type of someFunc to the page of SomeClass. I tried replacing #param {SomeClass} with:
#param {SomeClass.SomeClass}
#param {SomeClass/SomeClass}
#param {#link SomeClass}
#param {#link SomeClass.SomeClass}
#param {#link SomeClass/SomeClass}
But none of these worked: in all cases the documentation simply shows the text inside the curly brackets (even when I used #link).
How can I let JSDoc3 correctly generate links to the external modules?
Use the module: prefix when referencing modules. If the module's return value is the class itself, then use module:SomeClass. If it is a property of the module, use module:SomeClass.SomeClass. The #link tag shouldn't be necessary if jsdoc can find a reference to the existing class documentation.
Use typeof import
/**
* #param {typeof import("puppeteer").Browser} browser
*/
Docs here