I have a simple Camel route. I want to extract a file from the queue and pass it by using POST request to an external resource. This route works and the request reaches an external resource:
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
from("activemq:alfresco-queue")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
byte[] bytes = exchange.getIn().getBody(byte[].class);
// All of that not working...
// exchange.getIn().setHeader("content", bytes); gives "java.lang.IllegalAgrumentException: Request header is too large"
// exchange.getIn().setBody(bytes, byte[].class); gives "size of content is -1"
// exchange.getIn().setBody(bytes); gives "size of content is -1"
// ???
// ??? But I can print file content here
for(int i=0; i < bytes.length; i++) {
System.out.print((char) bytes[i]);
}
}
})
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("multipart/form-data"))
.to("http://vm-alfce52-31......com:8080/alfresco/s/someco/queuefileuploader?guest=true")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
System.out.println("The response code is: " + exchange.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE));
}
});
}
}
The question is that the payload of the request is lost:
// somewhere on an external resource
Content content = request.getContent();
long len = content.getSize() // is always == -1.
// the file name is passed successfully
String fileName = request.getHeader("fileName");
How to set and pass the payload of POST request in this route/ processor?
I noticed that ANY data setted by this way is losted too. Only the headers are sent to the remote resource.
By using simple HTML form with <input type="file"> encoded in multipart/form-data I can successfully send all the data to the external resource.
What could be the reason?
Updated.
The following code also gives null-content:
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
// this also gives null-content
//multipartEntityBuilder.addBinaryBody("file", exchange.getIn().getBody(byte[].class));
multipartEntityBuilder.addPart("file", new ByteArrayBody(exchange.getIn().getBody(byte[].class), exchange.getIn().getHeader("fileName", String.class)));
exchange.getOut().setBody(multipartEntityBuilder.build().getContent());
/********** This also gives null-content *********/
StringBody username = new StringBody("username", ContentType.MULTIPART_FORM_DATA);
StringBody password = new StringBody("password", ContentType.MULTIPART_FORM_DATA);
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
multipartEntityBuilder.addPart("username", username);
multipartEntityBuilder.addPart("password", password);
String filename = (String) exchange.getIn().getHeader("fileName");
File file = new File(filename);
try(RandomAccessFile accessFile = new RandomAccessFile(file, "rw")) {
accessFile.write(bytes);
}
multipartEntityBuilder.addPart("upload", new FileBody(file, ContentType.MULTIPART_FORM_DATA, filename));
exchange.getIn().setBody(multipartEntityBuilder.build().getContent());
One more detail. If I change this:
exchange.getOut().setBody(multipartEntityBuilder.build().getContent());
To this:
exchange.getOut().setBody(multipartEntityBuilder.build());
I get the following exception on FUSE side (I see it through hawtio management console):
Execution of JMS message listener failed.
Caused by: [org.apache.camel.RuntimeCamelException - org.apache.camel.InvalidPayloadException:
No body available of type: java.io.InputStream but has value: org.apache.http.entity.mime.MultipartFormEntity#26ee73 of type:
org.apache.http.entity.mime.MultipartFormEntity on: JmsMessage#0x1cb83b9.
Caused by: No type converter available to convert from type: org.apache.http.entity.mime.MultipartFormEntity to the required type:
java.io.InputStream with value org.apache.http.entity.mime.MultipartFormEntity#26ee73. Exchange[ID-63-DP-TAV-55652-1531889677177-5-1]. Caused by:
[org.apache.camel.NoTypeConversionAvailableException - No type converter available to convert from type:
org.apache.http.entity.mime.MultipartFormEntity to the required type: java.io.InputStream with value org.apache.http.entity.mime.MultipartFormEntity#26ee73]]
I write a small servlet application and get the content in the doPost(...) method from the HttpServletRequest object.
The problem was with the WebScriptRequest object on the external system (Alfresco) side.
#Bedla, thanks for your advices!
On the Alfresco side the problem can be solved as follows:
public class QueueFileUploader extends DeclarativeWebScript {
protected Map<String, Object> executeImpl(WebScriptRequest req, Status status) {
HttpServletRequest httpServletRequest = WebScriptServletRuntime.getHttpServletRequest(req);
// calling methods of httpServletRequest object and retrieving the content
...
The route:
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
from("activemq:alfresco-queue")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
multipartEntityBuilder.addPart("file", new ByteArrayBody(exchange.getIn().getBody(byte[].class),
exchange.getIn().getHeader("fileName", String.class)));
exchange.getIn().setBody(multipartEntityBuilder.build().getContent());
}
})
.setHeader(Exchange.HTTP_METHOD, constant(org.apache.camel.component.http4.HttpMethods.POST))
.to("http4://localhost:8080/alfresco/s/someco/queuefileuploader?guest=true")
// .to("http4://localhost:8080/ServletApp/hello")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
System.out.println("The response code is: " +
exchange.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE));
}
});
}
}
Related
Can someone please help in writing the unit tests for this class.
==============================================================================================
#Component("edi820AdapterRouteBuilder")
public class EDI820AdapterRouteBuilder extends BaseRouteBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(EDI820AdapterRouteBuilder.class);
private static final String DIRECT_Q_RECEIVER = "direct:queueReceiver";
private static final String DIRECT_PROCESS_820 = "direct:process820";
private static final String DIRECT_TO_HIX = "direct:toHIX";
private static final String DIRECT_TO_CMS = "direct:toCMS";
private static final String MDC_TRANSACTIONID = "transactionId";
private static final String REQUEST_ID = "RequestID";
private static final String DIRECT_PUT_MDC = "direct:putMDC";
private static final String DIRECT_REMOVE_MDC = "direct:removeMDC";
#Override
public void configure() throws Exception {
super.configure();
LOGGER.debug("configure called.");
String queueName = appConfig.getInboundQueueName();
LOGGER.debug("inboundQueueName: {}", queueName);
String toHIXendpoint = appConfig.getEndpointWithOptions("toHIXendpoint");
LOGGER.debug("toHIXendpoint: {}", toHIXendpoint);
String toCMSendpoint = appConfig.getEndpointWithOptions("toCMSendpoint");
LOGGER.debug("toCMSendpoint: {}", toCMSendpoint);
String routeDelay = appConfig.getRouteDelay();
LOGGER.debug("routeDelay: {}",routeDelay);
from("timer://runOnce?repeatCount=1&delay="+routeDelay)
.to("bean:edi820AdapterRouteBuilder?method=addRoute")
.end();
from(DIRECT_Q_RECEIVER)
.to(PERSIST_EDI820_XDATA)
.to(EDI820_REQUEST_TRANSFORMER)
.to(DIRECT_PROCESS_820)
.log(LoggingLevel.INFO, LOGGER,"Executed "+DIRECT_Q_RECEIVER)
.end();
from(DIRECT_PROCESS_820)
.choice()
.when(header(TRANSACTION_SOURCE_STR).isEqualTo(HIX_SOURCE_SYSTEM))
.log(LoggingLevel.INFO, LOGGER,"Calling route for: "+HIX_SOURCE_SYSTEM)
.to(DIRECT_TO_HIX)
.when(header(TRANSACTION_SOURCE_STR).isEqualTo(CMS_SOURCE_SYSTEM))
.log(LoggingLevel.INFO, LOGGER,"Calling route for: "+CMS_SOURCE_SYSTEM)
.to(DIRECT_TO_CMS)
.otherwise()
.log(LoggingLevel.INFO, LOGGER,"Invalid "+TRANSACTION_SOURCE_STR+" ${header["+TRANSACTION_SOURCE_STR+"]}")
.end();
from(DIRECT_TO_HIX).routeId("edi820adapter-to-hix-producer-route")
.log(LoggingLevel.INFO, LOGGER,"Executing edi820adapter-to-hix-producer-route")
.marshal().json(JsonLibrary.Jackson)//Convert body to json string
.to(toHIXendpoint)
.log(LoggingLevel.DEBUG, LOGGER, "json body sent to edi820-hix: ${body}")
.log(LoggingLevel.INFO, LOGGER,"Executed edi820adapter-to-hix-producer-route")
.end();
from(DIRECT_TO_CMS).routeId("edi820adapter-to-cms-producer-route")
.log(LoggingLevel.INFO, LOGGER,"Executing edi820adapter-to-cms-producer-route")
.marshal().json(JsonLibrary.Jackson)//Convert body to json string
.to(toCMSendpoint)
.log(LoggingLevel.DEBUG, LOGGER, "json body sent to edi820-cms: ${body}")
.log(LoggingLevel.INFO, LOGGER,"Executed edi820adapter-to-cms-producer-route")
.end();
from(DIRECT_PUT_MDC).process(new Processor() {
public void process(Exchange exchange) throws Exception {
if (exchange.getIn().getHeader(REQUEST_ID) != null) {
MDC.put(MDC_TRANSACTIONID, (String) exchange.getIn().getHeader(REQUEST_ID));
}
}
}).end();
from(DIRECT_REMOVE_MDC).process(new Processor() {
public void process(Exchange exchange) throws Exception {
MDC.remove(MDC_TRANSACTIONID);
}
}).end();
}
public void addRoute(Exchange exchange) {
try {
CamelContext context = exchange.getContext();
ModelCamelContext modelContext = context.adapt(ModelCamelContext.class);
modelContext.addRouteDefinition(buildRouteDefinition());
} catch (Exception e) {
LOGGER.error("Exception in addRoute: {}", e.getMessage());
LOGGER.error(ExceptionUtils.getFullStackTrace(e));
}
}
private RouteDefinition buildRouteDefinition() {
String queueName = appConfig.getInboundQueueName();
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition
.from("jms:queue:" + queueName).routeId("edi820-adapter-jms-consumer-route")
.to(DIRECT_PUT_MDC)
.log(LoggingLevel.INFO, LOGGER,"Executing edi820-adapter-jms-consumer-route")
.log(LoggingLevel.DEBUG, LOGGER, "Received Message from queue: "+queueName)
.to(DIRECT_Q_RECEIVER)
.log(LoggingLevel.INFO, LOGGER,"Executed edi820-adapter-jms-consumer-route")
.to(DIRECT_REMOVE_MDC)
.end();
return routeDefinition;
}
}
==============================================================================
Please let me know if any additional information is needed. Thanks for the help in advance.
It is not possible to write entire test cases mentioning those classes. So I'm writing snippet code for understanding purposes.
Sample Example for Understanding:
Below is a snippet of code for the route builder class:
public class CheckRouteBuilder extends RouteBuilder{
#Override
public void configure() throws Exception {
from("timer://runOnce?repeatCount=1&delay="+routeDelay)
.to("bean:edi820AdapterRouteBuilder?method=addRoute")
.end();
}
}
Follow the test cases of the above class:
public class CheckRouteBuilderTest extends CamelTestSupport {
#Test
public void testConfigure() throws Exception {
context.addRoutes(new CheckRouteBuilder ());
RouteDefinition route = context.getRouteDefinitions().get(0);
AdviceWith.adviceWith(route, context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
replaceFromWith("direct:TestEndpoint"); // timer://runOnce?repeatCount=1&delay="+routeDelay replace with direct:TestEndpoint because during test timer component not support that's why need to replace with direct component.
weaveAddLast().to("mock:endPoint");
}
});
MockEndpoint mockEndpoint = getMockEndpoint("mock:endPoint");
mockEndpoint.expectedMessageCount(1);
template.sendBodyAndHeaders("direct:TestEndpoint", "#", "##");
mockEndpoint.assertIsSatisfied();
}
}
# -> Mention body like String, JSONObject, XML. If body is not required then simply pass the null value.
## -> Mention Header like Content-type:application/json to need to pass in Map<String, Object) format.
I have a route to poll the email from the server. In the FunctionRoute I am trying to pass the subject of the email I want to fetch by the IMAP protocol using pollEnrich method in the EmailPollingRoute. Currently I am facing problem to make a dynamic URI endpoint. I noticed the headers are being cleared before calling the pollEnrich method. Therefore I tried to use the ${exchangeProperty.subject} but there is no email is fetched. When I set the subject directly in the endpoint as String then the email is being fetched.
How can I set the subject dynamically in the pollEnrich method? The subject is being passed from the FunctionRoute to EmailPollingRoute route.
I appreciate any help
#Component
public class FunctionRoute extends EmailPollingRoute {
static final Logger LOGGER = LoggerFactory.getLogger(FunctionRoute.class);
#Override
public void configure() throws Exception {
from("direct:process_polling_email_function")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
subject = "ABCD - Test1";
exchange.setProperty("ABCD - Test1");
LOGGER.info("1. subject - " + subject);
}})
//.setProperty("subject", constant("ABCD - Test1"))
//.setHeader("subject", simple("ABCD - Test1"))
.to("direct:process_polling_email");
}
}
#Component
public class EmailPollingRoute extends BaseRouteBuilder {
static final Logger LOGGER = LoggerFactory.getLogger(EmailPollingRoute.class);
public String subject;
#Override
public void configure() throws Exception {
//super.configure();
//2362
//#formatter:off
from("direct:process_polling_email")
.routeId("routeId_EmailPollingRoute")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
subject = exchange.getProperty("subject", String.class);
LOGGER.info("0001 - subjectB: (" + subject + ")");
}})
.pollEnrich("imaps://XXXXXXX.info"
+ "?username=XXXXX&"
+ "password=XXXXXXX&"
+"folderName=Inbox/Test&"
+"searchTerm.fromSentDate=now-72h&"
+"searchTerm.subject=${exchangeProperty.subject}&"
+"fetchSize=1");
.to("log:newmail");
//#formatter:on
}
}
You can set pollEnrich URI dynamically using simple method, you can also specify timeout in similar way.
.pollEnrich()
.simple("imaps://XXXXXXX.info"
+ "?username=XXXXX&"
+ "password=XXXXXXX&"
+"folderName=Inbox/Test&"
+"searchTerm.fromSentDate=now-72h&"
+"searchTerm.subject=${exchangeProperty.subject}&"
+"fetchSize=1")
.timeout(1000);
I am trying to test a Camel route (polling messages from an SQS queue) containing
.bean("messageParserProcessor")
where messageParserProcessor is a Processor.
The test:
public class SomeTest extends CamelTestSupport {
private final String queueName = ...;
private final String producerTemplateUri = "aws-sqs://" + queueName + ...;
private static final String MESSAGE_PARSER_PROCESSOR_MOCK_ENDPOINT = "mock:messageParserProcessor";
#EndpointInject(uri = MESSAGE_PARSER_PROCESSOR_MOCK_ENDPOINT)
protected MockEndpoint messageParserProcessor;
#Override
public boolean isUseAdviceWith() {
return true;
}
#Before
public void setUpContext() throws Exception {
context.getRouteDefinitions().get(0).adviceWith(context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
interceptSendToEndpoint("bean:messageParserProcessor")
.skipSendToOriginalEndpoint()
.process(MESSAGE_PARSER_PROCESSOR_MOCK_ENDPOINT);
}
});
}
#Test
public void testParser() throws Exception {
context.start();
String expectedBody = "test";
messageParserProcessor.expectedBodiesReceived(expectedBody);
ProducerTemplate template = context.createProducerTemplate();
template.sendBody(producerTemplateUri, expectedBody);
messageParserProcessor.assertIsSatisfied();
context.stop();
}
}
When I run the test I get this error:
org.apache.camel.FailedToCreateRouteException:
Failed to create route route1 at:
>>> InterceptSendToEndpoint[bean:messageParserProcessor -> [process[ref:mock:messageParserProcessor]]] <<< in route: Route(route1)[[From[aws-sqs://xxx...
because of No bean could be found in the registry for: mock:messageParserProcessor of type: org.apache.camel.Processor
Same error if I replace interceptSendToEndpoint(...) with mockEndpointsAndSkip("bean:messageParserProcessor")
The test can be executed (but obviously doesn't pass) when I don't use a mock:
interceptSendToEndpoint("bean:messageParserProcessor")
.skipSendToOriginalEndpoint()
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {}
});
So the problem is the mock that is not found, what is wrong in the way I create it?
So I found a workaround to retrieve mocks from the registry:
interceptSendToEndpoint("bean:messageParserProcessor")
.skipSendToOriginalEndpoint()
.bean(getMockEndpoint(MESSAGE_PARSER_PROCESSOR_MOCK_ENDPOINT));
// Instead of
// .process(MESSAGE_PARSER_PROCESSOR_MOCK_ENDPOINT);
But I still don't understand why using .process("mock:someBean") doesn't work...
I'm trying to throw an exception if HTTP response code is anything other than 200 or 201. I tried to override http4.DefaultHttpBinding, but the class is never getting called. What am I doing wrong here?
public class CustomHttpBinding extends org.apache.camel.component.http4.DefaultHttpBinding {
Logger log = Logger.getLogger(CustomHttpBinding.class);
private static final Integer HTTP_OK = 200;
private static final Integer HTTP_CREATED = 201;
#Override
public void doWriteResponse(Message message, HttpServletResponse response, Exchange exchange) throws IOException {
Integer httpResponseCode = message.getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class);
if(!httpResponseCode.equals(HTTP_OK) && !httpResponseCode.equals(HTTP_CREATED) ) {
throw new IOException("HTTP error code for HLR response is " + httpResponseCode);
}
else {
super.doWriteResponse(message, response, exchange);
}
}
}
In xml:
<bean id="customHttpBinding" class="com.test.response.CustomHttpBinding" />
<endpoint uri="http4:${http.url.1}?httpBinding=#customHttpBinding&httpClient.socketTimeout=${http.notificationTimeout}" id="http4.1.to"/>
If the http response code is 300+, the binding will throw exception before it calls doWriteResponse(...). Try overriding writeResponse(...) in your custom binding, and verify that your binding is used.
I am looking to implement a route where reslet/cxfrs end point will accept file as multipart request and process. (Request may have some JSON data as well.
Thanks in advance.
Regards.
[EDIT]
Have tried following code. Also tried sending file using curl. I can see file related info in headers and debug output, but not able to retrieve attachment.
from("servlet:///hello").process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Message in = exchange.getIn();
StringBuffer v = new StringBuffer();
HttpServletRequest request = (HttpServletRequest) in
.getHeaders().get(Exchange.HTTP_SERVLET_REQUEST);
DiskFileItemFactory diskFile = new DiskFileItemFactory();
FileItemFactory factory = diskFile;
ServletFileUpload upload = new ServletFileUpload(factory);
List items = upload.parseRequest(request);
.....
curl :
curl -vvv -i -X POST -H "Content-Type: multipart/form-data" -F "image=#/Users/navaltiger/1.jpg; type=image/jpg" http://:8080/JettySample/camel/hello
following code works (but can't use as it embeds jetty, and we would like to deploy it on tomcat/weblogic)
public void configure() throws Exception {
// getContext().getProperties().put("CamelJettyTempDir", "target");
getContext().setStreamCaching(true);
getContext().setTracing(true);
from("jetty:///test").process(new Processor() {
// from("servlet:///hello").process(new Processor() {
public void process(Exchange exchange) throws Exception {
String body = exchange.getIn().getBody(String.class);
HttpServletRequest request = exchange.getIn().getBody(
HttpServletRequest.class);
StringBuffer v = new StringBuffer();
// byte[] picture = (request.getParameter("image")).getBytes();
v.append("\n Printing All Request Parameters From HttpSerlvetRequest: \n+"+body +" \n\n");
Enumeration<String> requestParameters = request
.getParameterNames();
while (requestParameters.hasMoreElements()) {
String paramName = (String) requestParameters.nextElement();
v.append("\n Request Paramter Name: " + paramName
+ ", Value - " + request.getParameter(paramName));
}
I had a similar problem and managed to resolve inspired by the answer of brentos. The rest endpoint in my case is defined via xml:
<restContext id="UploaderServices" xmlns="http://camel.apache.org/schema/spring">
<rest path="/uploader">
<post bindingMode="off" uri="/upload" produces="application/json">
<to uri="bean:UploaderService?method=uploadData"/>
</post>
</rest>
</restContext>
I had to use "bindingMode=off" to disable xml/json unmarshalling because the HttpRequest body contains multipart data (json/text+file) and obviously the standard unmarshaling process was unable to process the request because it's expecting a string in the body and not a multipart payload.
The file and other parameters are sent from a front end that uses the file upload angular module: https://github.com/danialfarid/ng-file-upload
To solve CORS problems I had to add a CORSFilter filter in the web.xml like the one here:
public class CORSFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException,
ServletException {
HttpServletResponse httpResp = (HttpServletResponse) resp;
HttpServletRequest httpReq = (HttpServletRequest) req;
httpResp.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH");
httpResp.setHeader("Access-Control-Allow-Origin", "*");
if (httpReq.getMethod().equalsIgnoreCase("OPTIONS")) {
httpResp.setHeader("Access-Control-Allow-Headers",
httpReq.getHeader("Access-Control-Request-Headers"));
}
chain.doFilter(req, resp);
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
#Override
public void destroy() {
}
}
Also, I had to modify a little bit the unmarshaling part:
public String uploadData(Message exchange) {
String contentType=(String) exchange.getIn().getHeader(Exchange.CONTENT_TYPE);
MediaType mediaType = MediaType.valueOf(contentType); //otherwise the boundary parameter is lost
InputRepresentation representation = new InputRepresentation(exchange
.getBody(InputStream.class), mediaType);
try {
List<FileItem> items = new RestletFileUpload(
new DiskFileItemFactory())
.parseRepresentation(representation);
for (FileItem item : items) {
if (!item.isFormField()) {
InputStream inputStream = item.getInputStream();
// Path destination = Paths.get("MyFile.jpg");
// Files.copy(inputStream, destination,
// StandardCopyOption.REPLACE_EXISTING);
System.out.println("found file in request:" + item);
}else{
System.out.println("found string in request:" + new String(item.get(), "UTF-8"));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return "200";
}
I'm using the Camel REST DSL with Restlet and was able to get file uploads working with the following code.
rest("/images").description("Image Upload Service")
.consumes("multipart/form-data").produces("application/json")
.post().description("Uploads image")
.to("direct:uploadImage");
from("direct:uploadImage")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
MediaType mediaType =
exchange.getIn().getHeader(Exchange.CONTENT_TYPE, MediaType.class);
InputRepresentation representation =
new InputRepresentation(
exchange.getIn().getBody(InputStream.class), mediaType);
try {
List<FileItem> items =
new RestletFileUpload(
new DiskFileItemFactory()).parseRepresentation(representation);
for (FileItem item : items) {
if (!item.isFormField()) {
InputStream inputStream = item.getInputStream();
Path destination = Paths.get("MyFile.jpg");
Files.copy(inputStream, destination,
StandardCopyOption.REPLACE_EXISTING);
}
}
} catch (FileUploadException | IOException e) {
e.printStackTrace();
}
}
});
you can do this with restdsl even if you are not using restlet (exemple jetty) for your restdsl component.
you need to turn restdinding of first for that route and reate two classes to handle the multipart that is in your body.
you need two classes :
DWRequestContext
DWFileUpload
and then you use them in your custom processor
here is the code :
DWRequestContext.java
import org.apache.camel.Exchange;
import org.apache.commons.fileupload.RequestContext;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public class DWRequestContext implements RequestContext {
private Exchange exchange;
public DWRequestContext(Exchange exchange) {
this.exchange = exchange;
}
public String getCharacterEncoding() {
return StandardCharsets.UTF_8.toString();
}
//could compute here (we have stream cache enabled)
public int getContentLength() {
return (int) -1;
}
public String getContentType() {
return exchange.getIn().getHeader("Content-Type").toString();
}
public InputStream getInputStream() throws IOException {
return this.exchange.getIn().getBody(InputStream.class);
}
}
DWFileUpload.java
import org.apache.camel.Exchange;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadException;
import java.util.List;
public class DWFileUpload extends
FileUpload {
public DWFileUpload() {
super();
}
public DWFileUpload(FileItemFactory fileItemFactory) {
super(fileItemFactory);
}
public List<FileItem> parseInputStream(Exchange exchange)
throws FileUploadException {
return parseRequest(new DWRequestContext(exchange));
}
}
you can define your processor like this:
routeDefinition.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
// Create a factory for disk-based file items
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
DWFileUpload upload = new DWFileUpload(factory);
java.util.List<FileItem> items = upload.parseInputStream(exchange);
//here I assume I have only one, but I could split it here somehow and link them to camel properties...
//with this, the first file sended with your multipart replaces the body
// of the exchange for the next processor to handle it
exchange.getIn().setBody(items.get(0).getInputStream());
}
});
I stumbled into the same requirement of having to consume a multipart request (containing file data including binary) through Apache Camel Restlet component.
Even though 2.17.x is out, since my project was part of a wider framework / application, I had to be using version 2.12.4.
Initially, my solution drew a lot from restlet-jdbc example yielded data in exchange that although was successfully retrieving text files but I was unable to retrieve correct binary content.
I attempted to dump the data directly into a file to inspect the content using following code (abridged).
from("restlet:/upload?restletMethod=POST")
.to("direct:save-files");
from("direct:save-files")
.process(new org.apache.camel.Processor(){
public void process(org.apache.camel.Exchange exchange){
/*
* Code to sniff exchange content
*/
}
})
.to("file:///C:/<path to a folder>");
;
I used org.apache.commons.fileupload.MultipartStream from apache fileuplaod library to write following utility class to parse Multipart request from a file. It worked successfully when the output of a mulitpart request from Postman was fed to it. However, failed to parse content of the file created by Camel (even through to eyes content of both files looked similar).
public class MultipartParserFileCreator{
public static final String DELIMITER = "\\r?\\n";
public static void main(String[] args) throws Exception {
// taking it from the content-type in exchange
byte[] boundary = "------5lXVNrZvONBWFXxd".getBytes();
FileInputStream fis = new FileInputStream(new File("<path-to-file>"));
extractFile(fis, boundary);
}
public static void extractFile(InputStream is, byte[] boundary) throws Exception {
MultipartStream multipartStream = new MultipartStream(is, boundary, 1024*4, null);
boolean nextPart = multipartStream.skipPreamble();
while (nextPart) {
String headers = multipartStream.readHeaders();
if(isFileContent(headers)) {
String filename = getFileName(headers);
File file = new File("<dir-where-file-created>"+filename);
if(!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
multipartStream.readBodyData(fos);
fos.flush();
fos.close();
}else {
multipartStream.readBodyData(System.out);
}
nextPart = multipartStream.readBoundary();
}
}
public static String[] getContentDispositionTokens(String headersJoined) {
String[] headers = headersJoined.split(DELIMITER, -1);
for(String header: headers) {
System.out.println("Processing header: "+header);
if(header != null && header.startsWith("Content-Disposition:")) {
return header.split(";");
}
}
throw new RuntimeException(
String.format("[%s] header not found in supplied headers [%s]", "Content-Disposition:", headersJoined));
}
public static boolean isFileContent(String header) {
String[] tokens = getContentDispositionTokens(header);
for (String token : tokens) {
if (token.trim().startsWith("filename")) {
return true;
}
}
return false;
}
public static String getFileName(String header) {
String[] tokens = getContentDispositionTokens(header);
for (String token : tokens) {
if (token.trim().startsWith("filename")) {
String filename = token.substring(token.indexOf("=") + 2, token.length()-1);
System.out.println("fileName is " + filename);
return filename;
}
}
return null;
}
}
On debugging through the Camel code, I noticed that at one stage Camel is converting the entire content into String. After a point I had to stop pursuing this approach as there was very little on net applicable for version 2.12.4 and my work was not going anywhere.
Finally, I resorted to following solution
Write an implementation of HttpServletRequestWrapper to allow
multiple read of input stream. One can get an idea from
How to read request.getInputStream() multiple times
Create a filter that uses the above to wrap HttpServletRequest object, reads and extract the file to a directory Convenient way to parse incoming multipart/form-data parameters in a Servlet and attach the path to the request using request.setAttribute() method. With web.xml, configure this filter on restlet servlet
In the process method of camel route, type cast the
exchange.getIn().getBody() in HttpServletRequest object, extract the
attribute (path) use it to read the file as ByteStreamArray for
further processing
Not the cleanest, but I could achieve the objective.