Skip to content

Commit d8e2dd4

Browse files
Support REST for despatchAdvice (#55)
1 parent b8e915d commit d8e2dd4

File tree

31 files changed

+1448
-127
lines changed

31 files changed

+1448
-127
lines changed

core/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@
7171
<groupId>org.apache.camel</groupId>
7272
<artifactId>camel-bean</artifactId>
7373
</dependency>
74+
<dependency>
75+
<groupId>org.apache.camel</groupId>
76+
<artifactId>camel-log</artifactId>
77+
</dependency>
7478

7579
<dependency>
7680
<groupId>org.apache.camel</groupId>
@@ -80,6 +84,14 @@
8084
<groupId>org.apache.camel</groupId>
8185
<artifactId>camel-cxf-soap</artifactId>
8286
</dependency>
87+
<dependency>
88+
<groupId>org.apache.camel</groupId>
89+
<artifactId>camel-http</artifactId>
90+
</dependency>
91+
<dependency>
92+
<groupId>org.apache.camel</groupId>
93+
<artifactId>camel-jackson</artifactId>
94+
</dependency>
8395

8496
<dependency>
8597
<groupId>org.apache.camel</groupId>

core/src/main/java/io/github/project/openubl/xsender/Constants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ public class Constants {
2222
public static final String XSENDER_BILL_CONSULT_SERVICE_URI = "direct:xsender-billConsultService";
2323
public static final String XSENDER_BILL_VALID_SERVICE_URI = "direct:xsender-billValidService";
2424

25+
public static final String XSENDER_CREDENTIALS_API_URI = "direct:xsender-credentialsApi";
2526
}

core/src/main/java/io/github/project/openubl/xsender/camel/routes/CxfRouteBuilder.java

Lines changed: 0 additions & 42 deletions
This file was deleted.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2019 Project OpenUBL, Inc. and/or its affiliates
3+
* and other contributors as indicated by the @author tags.
4+
*
5+
* Licensed under the Apache License - 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package io.github.project.openubl.xsender.camel.routes;
18+
19+
import io.github.project.openubl.xsender.models.Metadata;
20+
import io.github.project.openubl.xsender.models.Status;
21+
import io.github.project.openubl.xsender.models.SunatResponse;
22+
import io.github.project.openubl.xsender.models.rest.ResponseDocumentErrorDto;
23+
import org.apache.camel.Exchange;
24+
import org.apache.camel.Processor;
25+
import org.apache.camel.http.base.HttpOperationFailedException;
26+
27+
import java.util.List;
28+
import java.util.stream.Collectors;
29+
30+
public class RestSunatErrorResponseProcessor implements Processor {
31+
32+
@Override
33+
public void process(Exchange exchange) throws Exception {
34+
HttpOperationFailedException httpException = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, HttpOperationFailedException.class);
35+
ResponseDocumentErrorDto responseDocumentErrorDto = exchange.getIn().getBody(ResponseDocumentErrorDto.class);
36+
37+
int errorCodeInt = Integer.parseInt(responseDocumentErrorDto.getCod());
38+
List<String> notes = responseDocumentErrorDto.getErrors().stream()
39+
.map(error -> error.getCod() + " - " + error.getMsg())
40+
.collect(Collectors.toList());
41+
42+
Metadata metadata = Metadata.builder()
43+
.notes(notes)
44+
.responseCode(Integer.parseInt(responseDocumentErrorDto.getCod()))
45+
.description(responseDocumentErrorDto.getMsg())
46+
.build();
47+
48+
SunatResponse sunatResponse = SunatResponse.builder()
49+
.status(Status.fromCode(errorCodeInt))
50+
.metadata(metadata)
51+
.build();
52+
exchange.getIn().setBody(sunatResponse);
53+
}
54+
55+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2019 Project OpenUBL, Inc. and/or its affiliates
3+
* and other contributors as indicated by the @author tags.
4+
*
5+
* Licensed under the Apache License - 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package io.github.project.openubl.xsender.camel.routes;
18+
19+
import io.github.project.openubl.xsender.models.Metadata;
20+
import io.github.project.openubl.xsender.models.Status;
21+
import io.github.project.openubl.xsender.models.Sunat;
22+
import io.github.project.openubl.xsender.models.SunatResponse;
23+
import io.github.project.openubl.xsender.models.rest.ResponseDocumentSuccessDto;
24+
import io.github.project.openubl.xsender.utils.CdrReader;
25+
import org.apache.camel.Exchange;
26+
import org.apache.camel.Processor;
27+
import org.apache.commons.codec.binary.Hex;
28+
29+
import java.util.Base64;
30+
import java.util.Collections;
31+
import java.util.Optional;
32+
33+
public class RestSunatResponseProcessor implements Processor {
34+
35+
@Override
36+
public void process(Exchange exchange) throws Exception {
37+
ResponseDocumentSuccessDto responseDto = exchange.getIn().getBody(ResponseDocumentSuccessDto.class);
38+
if (responseDto != null) {
39+
SunatResponse sunatResponse = null;
40+
41+
if (responseDto.getNumTicket() != null) {
42+
sunatResponse = SunatResponse.builder()
43+
.sunat(Sunat.builder()
44+
.ticket(responseDto.getNumTicket())
45+
.build()
46+
)
47+
.build();
48+
} else if (responseDto.getArcCdr() != null) {
49+
String cdrBase64Hex = responseDto.getArcCdr();
50+
byte[] bytes = Hex.decodeHex(cdrBase64Hex);
51+
byte[] cdrBytes = Base64.getDecoder().decode(bytes);
52+
CdrReader cdrReader = new CdrReader(cdrBytes);
53+
54+
SunatResponse.builder()
55+
.status(cdrReader.getStatus())
56+
.metadata(cdrReader.getMetadata())
57+
.sunat(Sunat.builder()
58+
.cdr(cdrBytes)
59+
.build()
60+
);
61+
} else if (responseDto.getCodRespuesta() != null) {
62+
int statusCode = Integer.parseInt(responseDto.getCodRespuesta());
63+
64+
Optional<String> responseErrorCode = responseDto.getError() != null ? Optional.ofNullable(responseDto.getError().getNumError()) : Optional.empty();
65+
Optional<String> responseErrorDescription = responseDto.getError() != null ? Optional.ofNullable(responseDto.getError().getDesError()) : Optional.empty();
66+
67+
Metadata metadata = Metadata.builder()
68+
.responseCode(responseErrorCode.map(Integer::parseInt).orElse(statusCode))
69+
.description(responseErrorDescription.orElseGet(null))
70+
.notes(Collections.emptyList())
71+
.build();
72+
73+
sunatResponse = SunatResponse.builder()
74+
.status(Status.fromCode(statusCode))
75+
.metadata(metadata)
76+
.sunat(null)
77+
.build();
78+
}
79+
80+
exchange.getIn().setBody(sunatResponse);
81+
}
82+
}
83+
84+
}

core/src/main/java/io/github/project/openubl/xsender/camel/routes/SunatErrorResponseProcessor.java renamed to core/src/main/java/io/github/project/openubl/xsender/camel/routes/SoapSunatErrorResponseProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import java.util.Collections;
2929
import java.util.regex.Pattern;
3030

31-
public class SunatErrorResponseProcessor implements Processor {
31+
public class SoapSunatErrorResponseProcessor implements Processor {
3232
private static Pattern pattern = Pattern.compile("-?\\d+(\\.\\d+)?");
3333

3434
public boolean isNumeric(String strNum) {

core/src/main/java/io/github/project/openubl/xsender/camel/routes/SunatResponseProcessor.java renamed to core/src/main/java/io/github/project/openubl/xsender/camel/routes/SoapSunatResponseProcessor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@
2929
import java.util.Collections;
3030
import java.util.Optional;
3131

32-
public class SunatResponseProcessor implements Processor {
32+
public class SoapSunatResponseProcessor implements Processor {
3333

3434
@Override
3535
public void process(Exchange exchange) throws Exception {
36-
MessageContentsList messageContentsList = (MessageContentsList) exchange.getIn().getBody();
36+
MessageContentsList messageContentsList = exchange.getIn().getBody(MessageContentsList.class);
3737
if (messageContentsList != null && !messageContentsList.isEmpty()) {
3838
Object messageContent = messageContentsList.get(0);
3939

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Copyright 2019 Project OpenUBL, Inc. and/or its affiliates
3+
* and other contributors as indicated by the @author tags.
4+
*
5+
* Licensed under the Apache License - 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package io.github.project.openubl.xsender.camel.routes;
18+
19+
import io.github.project.openubl.xsender.Constants;
20+
import io.github.project.openubl.xsender.models.rest.ResponseAccessTokenSuccessDto;
21+
import io.github.project.openubl.xsender.models.rest.ResponseDocumentErrorDto;
22+
import io.github.project.openubl.xsender.models.rest.ResponseDocumentSuccessDto;
23+
import org.apache.camel.Exchange;
24+
import org.apache.camel.LoggingLevel;
25+
import org.apache.camel.builder.RouteBuilder;
26+
import org.apache.camel.component.cxf.common.message.CxfConstants;
27+
import org.apache.camel.component.http.HttpConstants;
28+
import org.apache.camel.component.http.HttpMethods;
29+
import org.apache.camel.http.base.HttpOperationFailedException;
30+
import org.apache.camel.model.dataformat.JsonLibrary;
31+
import org.apache.camel.util.URISupport;
32+
import org.apache.cxf.binding.soap.SoapFault;
33+
34+
import java.net.URISyntaxException;
35+
import java.time.ZonedDateTime;
36+
import java.util.List;
37+
import java.util.Map;
38+
import java.util.Objects;
39+
40+
public class SunatRouteBuilder extends RouteBuilder {
41+
42+
@Override
43+
public void configure() {
44+
from(Constants.XSENDER_BILL_SERVICE_URI)
45+
.id("xsender-billService")
46+
.choice()
47+
// SOAP
48+
.when(header(CxfConstants.OPERATION_NAME).isNotNull())
49+
.to("cxf://bean:cxfBillServiceEndpoint?dataFormat=POJO")
50+
.process(new SoapSunatResponseProcessor())
51+
.endChoice()
52+
// REST
53+
.when(header(HttpConstants.HTTP_METHOD).isNotNull())
54+
.marshal().json(JsonLibrary.Jackson)
55+
.to("https://api-cpe.sunat.gob.pe")
56+
.convertBodyTo(ResponseDocumentSuccessDto.class)
57+
.process(new RestSunatResponseProcessor())
58+
.endChoice()
59+
// Otherwise
60+
.otherwise()
61+
.throwException(new RuntimeException("Not supported protocol identified"))
62+
.endChoice()
63+
.end()
64+
65+
// SOAP Exception
66+
.onException(SoapFault.class)
67+
.handled(true)
68+
.process(new SoapSunatErrorResponseProcessor())
69+
.end()
70+
// REST exception
71+
.onException(HttpOperationFailedException.class)
72+
.handled(exchange -> {
73+
HttpOperationFailedException httpException = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, HttpOperationFailedException.class);
74+
String contentType = httpException.getResponseHeaders().getOrDefault("Content-Type", "");
75+
76+
exchange.getIn().setHeader("HttpResponseHeader_ContentType", contentType);
77+
return Objects.equals(contentType, "application/json");
78+
})
79+
.choice()
80+
.when(header("HttpResponseHeader_ContentType").isEqualTo("application/json"))
81+
.convertBodyTo(ResponseDocumentErrorDto.class)
82+
.process(new RestSunatErrorResponseProcessor())
83+
.endChoice()
84+
.otherwise()
85+
.log(LoggingLevel.WARN, "Response from server is not JSON, something went wrong while connecting to the remote server")
86+
.endChoice()
87+
.end()
88+
.end();
89+
90+
from(Constants.XSENDER_BILL_CONSULT_SERVICE_URI)
91+
.id("xsender-billConsultService")
92+
.to("cxf://bean:cxfBillConsultServiceEndpoint?dataFormat=POJO");
93+
94+
from(Constants.XSENDER_BILL_VALID_SERVICE_URI)
95+
.id("xsender-billValidService")
96+
.to("cxf://bean:cxfBillValidServiceEndpoint?dataFormat=POJO");
97+
98+
// Requires a List as body.
99+
// Where the first element (List(0)) is the prev AccessTokenDto (NULL if there is no prev value)
100+
// Second element of the list (List(1)) is the 'x-www-form-urlencoded' form as a Map object
101+
from(Constants.XSENDER_CREDENTIALS_API_URI)
102+
.id("xsender-credentialsApi")
103+
.choice()
104+
// Refresh token
105+
.when(exchange -> {
106+
List<?> body = exchange.getIn().getBody(List.class);
107+
ResponseAccessTokenSuccessDto prevToken = (ResponseAccessTokenSuccessDto) body.get(0);
108+
109+
ZonedDateTime prevTokenCreatedIn = prevToken.getCreated_in();
110+
ZonedDateTime expirationDate = prevTokenCreatedIn.plusSeconds(prevToken.getExpires_in())
111+
.plusSeconds(30); // adding seconds to give time to the next operation to perform
112+
113+
return ZonedDateTime.now().compareTo(expirationDate) >= 0;
114+
})
115+
.setHeader(HttpConstants.HTTP_METHOD, constant(HttpMethods.POST))
116+
.setHeader(HttpConstants.CONTENT_TYPE, constant("application/x-www-form-urlencoded"))
117+
.setBody(exchange -> {
118+
List<?> body = exchange.getIn().getBody(List.class);
119+
Map<String, Object> map = (Map<String, Object>) body.get(1);
120+
try {
121+
return URISupport.createQueryString(map);
122+
} catch (URISyntaxException e) {
123+
throw new RuntimeException(e);
124+
}
125+
})
126+
.to("https://api-seguridad.sunat.gob.pe")
127+
.convertBodyTo(ResponseAccessTokenSuccessDto.class)
128+
.process(exchange -> {
129+
ResponseAccessTokenSuccessDto response = exchange.getIn().getBody(ResponseAccessTokenSuccessDto.class);
130+
response.setCreated_in(ZonedDateTime.now());
131+
})
132+
.endChoice()
133+
// Reuse previous token
134+
.otherwise()
135+
.setBody(exchange -> {
136+
List<?> body = exchange.getIn().getBody(List.class);
137+
return (ResponseAccessTokenSuccessDto) body.get(0);
138+
})
139+
.endChoice()
140+
.end();
141+
}
142+
}

0 commit comments

Comments
 (0)