I want to create a ENUM which holds different statuses for possible database values and use them also to generate possible drop down statuses in FE:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
public enum BusinessCustomersStatus {
A("active", "Active"),
O("onboarding", "Onboarding"),
NV("not_verified", "Not Verified"),
V("verified", "Verified"),
S("suspended", "Suspended"),
I("inactive", "Inactive");
private String shortName;
private String fullName;
BusinessCustomersStatus(String shortName, String fullName) {
this.shortName = shortName;
this.fullName = fullName;
}
// Define the status field as the enum representation by using #JsonValue
#JsonValue
public String getShortName() {
return shortName;
}
#JsonValue
public String getFullName() {
return fullName;
}
// Use the fromStatus method as #JsonCreator
#JsonCreator
public static BusinessCustomersStatus fromStatus(String statusText) {
for (BusinessCustomersStatus status : values()) {
if (status.getShortName().equalsIgnoreCase(statusText)) {
return status;
}
}
throw new UnsupportedOperationException(String.format("Unknown status: '%s'", statusText));
}
}
Full code:
https://github.com/rcbandit111/Search_specification_POC/blob/main/src/main/java/org/merchant/database/service/businesscustomers/BusinessCustomersStatus.java
But when I make a POST request to add a new record I get this error:
21:36:52.033 [http-nio-8000-exec-1] DEBUG HttpEntityMethodProcessor[traceDebug:91] - Writing [org.merchant.dto.tickets.TicketsFullDTO#1c7e1a52]
21:36:52.034 [http-nio-8000-exec-1] WARN DefaultHandlerExceptionResolver[logException:207] - Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Problem with definition of [AnnotedClass org.merchant.database.service.tickets.TicketStatus]: Multiple 'as-value' properties defined ([method org.merchant.database.service.tickets.TicketStatus#getFullName()] vs [method org.merchant.database.service.tickets.TicketStatus#getShortName()]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Problem with definition of [AnnotedClass org.merchant.database.service.tickets.BusinessCustomersStatus]: Multiple 'as-value' properties defined ([method org.merchant.database.service.tickets.BusinessCustomersStatus#getFullName()] vs [method org.merchant.database.service.tickets.BusinessCustomersStatus#getShortName()]) (through reference chain: org.merchant.dto.tickets.TicketsFullDTO["status"])]
21:36:52.034 [http-nio-8000-exec-1] DEBUG OpenEntityManagerInViewInterceptor[afterCompletion:111] - Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
21:36:52.034 [http-nio-8000-exec-1] DEBUG DispatcherServlet[logResult:1131] - Completed 500 INTERNAL_SERVER_ERROR
21:36:52.035 [http-nio-8000-exec-1] DEBUG DispatcherServlet[traceDebug:91] - "ERROR" dispatch for POST "/api/error", parameters={}
21:36:52.035 [http-nio-8000-exec-1] DEBUG RequestMappingHandlerMapping[getHandler:522] - Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
21:36:52.035 [http-nio-8000-exec-1] DEBUG OpenEntityManagerInViewInterceptor[preHandle:86] - Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
21:36:52.036 [http-nio-8000-exec-1] DEBUG HttpEntityMethodProcessor[writeWithMessageConverters:268] - Using 'application/json', given [application/json, text/plain, */*] and supported [application/json, application/*+json, application/json, application/*+json]
21:36:52.036 [http-nio-8000-exec-1] DEBUG HttpEntityMethodProcessor[traceDebug:91] - Writing [{timestamp=Wed Oct 27 21:36:52 UTC 2021, status=500, error=Internal Server Error, path=/api/manageme (truncated)...]
21:36:52.036 [http-nio-8000-exec-1] DEBUG OpenEntityManagerInViewInterceptor[afterCompletion:111] - Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
21:36:52.037 [http-nio-8000-exec-1] DEBUG DispatcherServlet[logResult:1127] - Exiting from "ERROR" dispatch, status 500
Multiple 'as-value' properties defined ^C22:18:41.117 [SpringApplicationShutdownHook] DEBUG ApplicationAvailabilityBean[onApplicationEvent:77] - Application availability state ReadinessState changed from ACCEPTING_TRAFFIC to REFUSING_TRAFFIC
Note that I have changed TicketStatus with BusinessCustomersStatus into the error stack.
When I make a request I send this payload:
{"title":"ascascasc","description":"ascascasc","status":"assigned","submitDate":"2021-10-13T00:00:00.000Z","category":"service","severity":"normal"}
Do you know how I can solve this issue?
it may work if changed like this
public String getShortName() {
return shortName;
}
#JsonValue
public String getFullName() {
return fullName;
}
Related
I am using reactive MongoDb, and trying to implement Free Text search based on the weight
implementation("io.micronaut.mongodb:micronaut-mongo-reactive")
on below POJO
public class Product {
#BsonProperty("_id")
#BsonId
private ObjectId id;
private String name;
private float price;
private String description;
}
Tried this simple example
public Flowable<List<Product>> findByFreeText(String text) {
LOG.info(String.format("Listener --> Listening value = %s", text));
Flowable.fromPublisher(this.repository.getCollection("product", List.class)
.find(new Document("$text", new Document("$search", text)
.append("$caseSensitive", false)
.append("$diacriticSensitive", false)))).subscribe(item -> {
System.out.println(item);
}, error -> {
System.out.println(error);
});
return Flowable.just(List.of(new Product()));
}
I don't think this is the correct way of implementing the Free Text Search.
At first you don't need to have Flowable with List of Product because Flowable can manage more then one value unlike Single. So, it is enough to have Flowable<Product>. Then you can simply return the Flowable instance from find method.
Text search can be then implemented like this:
public Flowable<Product> findByFreeText(final String query) {
return Flowable.fromPublisher(repository.getCollection("product", Product.class)
.find(new Document("$text",
new Document("$search", query)
.append("$caseSensitive", false)
.append("$diacriticSensitive", false)
)));
}
Then it is up to the consumer of the method how it subscribes to the result Flowable. In controller you can directly return the Flowable instance. If you need to consume it somewhere in your code you can do subscribe() or blockingSubscribe() and so on.
And you can of course test it by JUnit like this:
#MicronautTest
class SomeServiceTest {
#Inject
SomeService service;
#Test
void findByFreeText() {
service.findByFreeText("test")
.test()
.awaitCount(1)
.assertNoErrors()
.assertValue(p -> p.getName().contains("test"));
}
}
Update: you can debug communication with MongoDB by setting this in logback.xml (Micronaut is using Logback as a default logging framework) logging config file:
<configuration>
....
<logger name="org.mongodb" level="debug"/>
</configuration>
Then you will see this in the log file:
16:20:21.257 [Thread-5] DEBUG org.mongodb.driver.protocol.command - Sending command '{"find": "product", "filter": {"$text": {"$search": "test", "$caseSensitive": false, "$diacriticSensitive": false}}, "batchSize": 2147483647, "$db": "some-database"}' with request id 6 to database some-database on connection [connectionId{localValue:3, serverValue:1634}] to server localhost:27017
16:20:21.258 [Thread-8] DEBUG org.mongodb.driver.protocol.command - 16:20:21.258 [Thread-7] DEBUG org.mongodb.driver.protocol.command - Execution of command with request id 6 completed successfully in 2.11 ms on connection [connectionId{localValue:3, serverValue:1634}] to server localhost:27017
Then you can copy the command from log and try it in MongoDB CLI or you can install MongoDB Compass where you can play with that more and see whether the command is correct or not.
I am new in the world of Spring Boot and MongoDB so this could be a stupid question.
I created a Spring Boot project linked to a MondoDB database. In the Controller I've defined these methods: get, getAll, add, update and delete.
Everything works fine while I test my app on PostMan, except for the update method. Indeed in PostMan, using the PUT command, I get this error:
"status": 405,
"error": "Method Not Allowed"
Looking for a solution, I found these lines in PostMan:
PUT non allowed
where the value of "Allow"only contains "GET, DELETE" and not PUT.
Maybe this fact is linked to my error? How can I fix it?
Thank you and sorry for my bad english and lack of knowledge of SpringBoot!
EDIT 1: Controller code:
#PutMapping("/{id}")
public ResponseEntity <Cliente> updateCliente(#PathVariable(value = "id") String id, #RequestBody Cliente cliente){
Optional<Cliente> c = clienteRepo.findById(id);
Cliente _c = new Cliente();
if(c.isPresent()) {
_c = c.get();
_c.setId(cliente.getId());
_c.setNome(cliente.getNome());
}
final Cliente updatedCliente = clienteRepo.save(_c);
return ResponseEntity.ok(updatedCliente);
}
EDIT 2: PostMan request:
PostMan
You can check the mapped api in the log by adding the following config in the application.properties file:
logging.level.org.springframework.web.servlet.mvc.method.annotation=TRACE
For example:
I have a controller:
#RestController
#RequestMapping("/client")
public class HomeRestController {
#PutMapping("/{id}")
public void put(#PathVariable(value = "id") String id, #RequestBody TestingModel model) {
System.out.println(id);
System.out.println(model.getName());
}
}
When starting the application you can see the mapped API in the console log as below:
2020-07-14 09:36:49.287 TRACE 13224 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping :
c.e.e.c.HomeController:
{ /index}: home()
2020-07-14 09:36:49.288 TRACE 13224 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping :
c.e.e.c.HomeRestController:
{PUT /client/{id}}: put(String,TestingModel)
2020-07-14 09:36:49.293 TRACE 13224 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping :
o.s.b.a.w.s.e.BasicErrorController:
{ /error}: error(HttpServletRequest)
{ /error, produces [text/html]}: errorHtml(HttpServletRequest,HttpServletResponse)
I am participating into a code swap type challenge, for Java and Spring, and I am having some issues adding in a Search feature for this shopping list app. Before I get into depth of code, by search feature I mean search the local database, not something like google. Now I'l show the code and then explain what I'm trying to do underneath it all.
This is the error message:
2016-12-14 10:00:40.476 WARN 10452 --- [ main] o.h.b.i.SessionFactoryBuilderImpl : Unrecognized hbm2ddl_auto value : auto. Supported values include create, create-drop, update, and validate. Ignoring
2016-12-14 10:00:42.300 INFO 10452 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2016-12-14 10:00:46.009 WARN 10452 --- [ main] ationConfigEmbeddedWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'shoppingListController': Unsatisfied dependency expressed through field 'shoppingListRepo'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'shoppingListRepository': Invocation of init method failed; nested exception is org.springframework.data.mapping.PropertyReferenceException: No property category found for type ShoppingList!
2016-12-14 10:00:46.014 INFO 10452 --- [ main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2016-12-14 10:00:46.071 INFO 10452 --- [ main] o.apache.catalina.core.StandardService : Stopping service Tomcat
2016-12-14 10:00:46.393 INFO 10452 --- [ main] utoConfigurationReportLoggingInitializer :
Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.
2016-12-14 10:00:46.455 ERROR 10452 --- [ main] o.s.boot.SpringApplication : Application startup failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'shoppingListController': Unsatisfied dependency expressed through field 'shoppingListRepo'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'shoppingListRepository': Invocation of init method failed; nested exception is org.springframework.data.mapping.PropertyReferenceException: No property category found for type ShoppingList!
This is the part of the controller I am editing:
#GetMapping("/lists")
public String lists(Model model, #RequestParam(name = "srch-term", required = false) String searchTerm) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String email = auth.getName();
User u = userRepo.findOneByEmail(email);
if (searchTerm == null || "".equals(searchTerm)) {
model.addAttribute("lists", shoppingListRepo.findAllByUser(u));
} else {
ArrayList<ShoppingList> userLists = new ArrayList<ShoppingList>();
ArrayList<ShoppingList> lists = shoppingListRepo.findByCategoryContainsOrNameContainsAllIgnoreCase(searchTerm,
searchTerm);
for (ShoppingList list : lists) {
if (list.getUser() == u) {
userLists.add(list);
}
}
model.addAttribute("lists", shoppingListRepo.findAllByUser(u));
model.addAttribute("user", u);
}
return "lists";
}
This is the controller before being edited by me
#GetMapping("/lists")
public String lists(Model model) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String email = auth.getName();
User u = userRepo.findOneByEmail(email);
model.addAttribute("lists", shoppingListRepo.findAllByUser(u));
model.addAttribute("user", u);
return "lists";
}
This is my Repository
package org.elevenfifty.shoppinglist.repositories;
import java.util.ArrayList;
import org.elevenfifty.shoppinglist.beans.ShoppingList;
import org.elevenfifty.shoppinglist.beans.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface ShoppingListRepository extends CrudRepository<ShoppingList, Long> {
ArrayList<ShoppingList> findAllByUser(User u);
ArrayList<ShoppingList> findByCategoryContainsOrNameContainsAllIgnoreCase(String categoryPart, String namePart);
}
Now like I said I am getting a search feature working. I am not sure where the property is in my error message I am hoping some fresh eyes can help find what I am clearly missing.
Add category field and its getters and setters to the ShoppingList class.
private String category
public void setCategory(String category){
this.category = category;
}
public String getCategory(){
return category;
}
I have completed a "happy-path" (as below).
How I can advise a .transform call to have it invoke an error flow (via errorChannel) w/o interrupting the mainFlow?
Currently the mainFlow terminates on first failure occurrence in second .transform (when payload cannot be deserialized to type). My desired behavior is that I'd like to log and continue processing.
I've read about ExpressionEvaluatingRequestHandlerAdvice. Would I just add a second param to each .transform call like e -> e.advice(myAdviceBean) and declare such a bean with success and error channels? Assuming I'd need to break up my mainFlow to receive success from each transform.
On some commented direction I updated the original code sample. But I'm still having trouble taking this "all the way home".
2015-09-08 11:49:19,664 [pool-3-thread-1] org.springframework.integration.handler.ServiceActivatingHandler DEBUG handler 'ServiceActivator for [org.springframework.integration.dsl.support.BeanNameMessageProcessor#5f3839ad] (org.springframework.integration.handler.ServiceActivatingHandler#0)' produced no reply for request Message: ErrorMessage [payload=org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice$MessageHandlingExpressionEvaluatingAdviceException: Handler Failed; nested exception is org.springframework.integration.transformer.MessageTransformationException: failed to transform message; nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "hasDoaCostPriceChanged" (class com.xxx.changehistory.jdbc.data.RatePlanLevelRestrictionLog), not marked as ignorable (18 known properties: "supplierUpdateDate", "fPLOSMaskArrival", "createDate", "endAllowed", "sellStateId", "ratePlanLevel", "ratePlanId", "startAllowed", "stayDate", "doaCostPriceChanged", "hotelId", "logActionTypeId" [truncated]])
at [Source: java.util.zip.GZIPInputStream#242017b8; line: 1, column: 32] (through reference chain: com.xxx.changehistory.jdbc.data.RatePlanLevelRestrictionLog["hasDoaCostPriceChanged"]), headers={id=c054d976-5750-827f-8894-51aba9655c77, timestamp=1441738159660}]
2015-09-08 11:49:19,664 [pool-3-thread-1] org.springframework.integration.channel.DirectChannel DEBUG postSend (sent=true) on channel 'errorChannel', message: ErrorMessage [payload=org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice$MessageHandlingExpressionEvaluatingAdviceException: Handler Failed; nested exception is org.springframework.integration.transformer.MessageTransformationException: failed to transform message; nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "hasDoaCostPriceChanged" (class com.xxx.changehistory.jdbc.data.RatePlanLevelRestrictionLog), not marked as ignorable (18 known properties: "supplierUpdateDate", "fPLOSMaskArrival", "createDate", "endAllowed", "sellStateId", "ratePlanLevel", "ratePlanId", "startAllowed", "stayDate", "doaCostPriceChanged", "hotelId", "logActionTypeId" [truncated]])
at [Source: java.util.zip.GZIPInputStream#242017b8; line: 1, column: 32] (through reference chain: com.xxx.changehistory.jdbc.data.RatePlanLevelRestrictionLog["hasDoaCostPriceChanged"]), headers={id=c054d976-5750-827f-8894-51aba9655c77, timestamp=1441738159660}]
2015-09-08 11:49:19,664 [pool-3-thread-1] org.springframework.integration.channel.DirectChannel DEBUG preSend on channel 'mainFlow.channel#3', message: GenericMessage [payload=java.util.zip.GZIPInputStream#242017b8, headers={id=b80106f9-7f4c-1b92-6aca-6e73d3bf8792, timestamp=1441738159664}]
2015-09-08 11:49:19,664 [pool-3-thread-1] org.springframework.integration.aggregator.AggregatingMessageHandler DEBUG org.springframework.integration.aggregator.AggregatingMessageHandler#0 received message: GenericMessage [payload=java.util.zip.GZIPInputStream#242017b8, headers={id=b80106f9-7f4c-1b92-6aca-6e73d3bf8792, timestamp=1441738159664}]
2015-09-08 11:49:19,665 [pool-3-thread-1] org.springframework.integration.channel.DirectChannel DEBUG preSend on channel 'errorChannel', message: ErrorMessage [payload=org.springframework.messaging.MessageHandlingException: error occurred in message handler [org.springframework.integration.aggregator.AggregatingMessageHandler#0]; nested exception is java.lang.IllegalStateException: Null correlation not allowed. Maybe the CorrelationStrategy is failing?, headers={id=24e3a1c7-af6b-032c-6a29-b55031fba0d7, timestamp=1441738159665}]
2015-09-08 11:49:19,665 [pool-3-thread-1] org.springframework.integration.handler.ServiceActivatingHandler DEBUG ServiceActivator for [org.springframework.integration.dsl.support.BeanNameMessageProcessor#5f3839ad] (org.springframework.integration.handler.ServiceActivatingHandler#0) received message: ErrorMessage [payload=org.springframework.messaging.MessageHandlingException: error occurred in message handler [org.springframework.integration.aggregator.AggregatingMessageHandler#0]; nested exception is java.lang.IllegalStateException: Null correlation not allowed. Maybe the CorrelationStrategy is failing?, headers={id=24e3a1c7-af6b-032c-6a29-b55031fba0d7, timestamp=1441738159665}]
2015-09-08 11:49:19,665 [pool-3-thread-1] com.xxx.DataMigrationModule$ErrorService ERROR org.springframework.messaging.MessageHandlingException: error occurred in message handler [org.springframework.integration.aggregator.AggregatingMessageHandler#0]; nested exception is java.lang.IllegalStateException: Null correlation not allowed. Maybe the CorrelationStrategy is failing?
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:84)
at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116)
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:101)
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:97)
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:287)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:245)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:45)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:95)
at org.springframework.integration.handler.AbstractMessageProducingHandler.sendOutput(AbstractMessageProducingHandler.java:231)
at org.springframework.integration.handler.AbstractMessageProducingHandler.produceOutput(AbstractMessageProducingHandler.java:154)
at org.springframework.integration.handler.AbstractMessageProducingHandler.sendOutputs(AbstractMessageProducingHandler.java:102)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:105)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:78)
at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116)
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:101)
at org.springframework.integration.dispatcher.UnicastingDispatcher.access$000(UnicastingDispatcher.java:48)
at org.springframework.integration.dispatcher.UnicastingDispatcher$1.run(UnicastingDispatcher.java:92)
at org.springframework.integration.util.ErrorHandlingTaskExecutor$1.run(ErrorHandlingTaskExecutor.java:52)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalStateException: Null correlation not allowed. Maybe the CorrelationStrategy is failing?
at org.springframework.util.Assert.state(Assert.java:385)
at org.springframework.integration.aggregator.AbstractCorrelatingMessageHandler.handleMessageInternal(AbstractCorrelatingMessageHandler.java:369)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:78)
... 22 more
UPDATED (09-08-2015)
code sample
#Bean
public IntegrationFlow mainFlow() {
// #formatter:off
return IntegrationFlows
.from(
amazonS3InboundSynchronizationMessageSource(),
e -> e.poller(p -> p.trigger(this::nextExecutionTime))
)
.transform(unzipTransformer())
.split(f -> new FileSplitter())
.channel(MessageChannels.executor(Executors.newCachedThreadPool()))
.transform(Transformers.fromJson(persistentType()), , e -> e.advice(handlingAdvice()))
// #see http://docs.spring.io/spring-integration/reference/html/messaging-routing-chapter.html#agg-and-group-to
.aggregate(a ->
a.releaseStrategy(g -> g.size() == persistenceBatchSize)
.expireGroupsUponCompletion(true)
.sendPartialResultOnExpiry(true)
.groupTimeoutExpression("size() ge 2 ? 10000 : -1")
, null
)
.handle(jdbcRepositoryHandler())
// TODO add advised PollableChannel to deal with possible persistence issue and retry with partial batch
.get();
// #formatter:on
}
#Bean
public ErrorService errorService() {
return new ErrorService();
}
#Bean
public MessageChannel customErrorChannel() {
return MessageChannels.direct().get();
}
#Bean
public IntegrationFlow customErrorFlow() {
// #formatter:off
return IntegrationFlows
.from(customErrorChannel())
.handle("errorService", "handleError")
.get();
// #formatter:on
}
#Bean
ExpressionEvaluatingRequestHandlerAdvice handlingAdvice() {
ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
advice.setOnFailureExpression("payload");
advice.setFailureChannel(customErrorChannel());
advice.setReturnFailureExpressionResult(true);
advice.setTrapException(true);
return advice;
}
protected class ErrorService implements ErrorHandler {
private final Logger log = LoggerFactory.getLogger(getClass());
#Override
public void handleError(Throwable t) {
stopEndpoints(t);
}
private void stopEndpoints(Throwable t) {
log.error(ExceptionUtils.getStackTrace(t));
}
}
Turns out I had things wrong in a few places, like:
I had to autowire a Jackson2 ObjectMapper (that I get from Sprint Boot auto-config) and construct an instance of JsonObjectMapper to be added as second arg in Transformers.fromJson; made for more lenient unmarshalling to persistent type (stops UnrecognizedPropertyException); and thus waived need for ExpressionEvaluatingRequestHandlerAdvice
Choosing the proper variant of .split method in IntegrationFlowDefinition in order to employ the FileSplitter, otherwise you don't get this splitter rather a DefaultMessageSplitter which pre-maturely terminates flow after first record read from InputStream
Moved transform, aggregate, handle to a its own pubsub channel employing an async task executor
Still not 100% of what I need, but it's much further along.
See what I ended up w/ below...
#Configuration
#EnableIntegration
#IntegrationComponentScan
public class DataMigrationModule {
private final Logger log = LoggerFactory.getLogger(getClass());
#Value("${cloud.aws.credentials.accessKey}")
private String accessKey;
#Value("${cloud.aws.credentials.secretKey}")
private String secretKey;
#Value("${cloud.aws.s3.bucket}")
private String bucket;
#Value("${cloud.aws.s3.max-objects-per-batch:1024}")
private int maxObjectsPerBatch;
#Value("${cloud.aws.s3.accept-subfolders:false}")
private String acceptSubFolders;
#Value("${cloud.aws.s3.remote-directory}")
private String remoteDirectory;
#Value("${cloud.aws.s3.local-directory-ref:java.io.tmpdir}")
private String localDirectoryRef;
#Value("${cloud.aws.s3.local-subdirectory:target/s3-dump}")
private String localSubdirectory;
#Value("${cloud.aws.s3.filename-wildcard:}")
private String fileNameWildcard;
#Value("${app.persistent-type:}")
private String persistentType;
#Value("${app.repository-type:}")
private String repositoryType;
#Value("${app.persistence-batch-size:2500}")
private int persistenceBatchSize;
#Value("${app.persistence-batch-release-timeout-in-milliseconds:5000}")
private int persistenceBatchReleaseTimeoutMillis;
#Autowired
private ListableBeanFactory beanFactory;
#Autowired
private ObjectMapper objectMapper;
private final AtomicBoolean invoked = new AtomicBoolean();
private Class<?> repositoryType() {
try {
return Class.forName(repositoryType);
} catch (ClassNotFoundException cnfe) {
log.error("Unknown repository implementation!", cnfe);
System.exit(0);
}
return null;
}
private Class<?> persistentType() {
try {
return Class.forName(persistentType);
} catch (ClassNotFoundException cnfe) {
log.error("Unsupported type!", cnfe);
System.exit(0);
}
return null;
}
public Date nextExecutionTime(TriggerContext triggerContext) {
return this.invoked.getAndSet(true) ? null : new Date();
}
#Bean
public FileToInputStreamTransformer unzipTransformer() {
FileToInputStreamTransformer transformer = new FileToInputStreamTransformer();
transformer.setDeleteFiles(true);
return transformer;
}
#Bean
public MessageSource<?> amazonS3InboundSynchronizationMessageSource() {
AWSCredentials credentials = new BasicAWSCredentials(this.accessKey, this.secretKey);
AmazonS3InboundSynchronizationMessageSource messageSource = new AmazonS3InboundSynchronizationMessageSource();
messageSource.setCredentials(credentials);
messageSource.setBucket(bucket);
messageSource.setMaxObjectsPerBatch(maxObjectsPerBatch);
messageSource.setAcceptSubFolders(Boolean.valueOf(acceptSubFolders));
messageSource.setRemoteDirectory(remoteDirectory);
if (!fileNameWildcard.isEmpty()) {
messageSource.setFileNameWildcard(fileNameWildcard);
}
String directory = System.getProperty(localDirectoryRef);
if (!localSubdirectory.startsWith("/")) {
localSubdirectory = "/" + localSubdirectory;
}
if (!localSubdirectory.endsWith("/")) {
localSubdirectory = localSubdirectory + "/";
}
directory = directory + localSubdirectory;
FileUtils.mkdir(directory);
messageSource.setDirectory(new LiteralExpression(directory));
return messageSource;
}
#Bean
public IntegrationFlow mainFlow() {
// #formatter:off
return IntegrationFlows
.from(
amazonS3InboundSynchronizationMessageSource(),
e -> e.poller(p -> p.trigger(this::nextExecutionTime))
)
.transform(unzipTransformer())
.split(new FileSplitter(), null)
.publishSubscribeChannel(new SimpleAsyncTaskExecutor(), p -> p.subscribe(persistenceSubFlow()))
.get();
// #formatter:on
}
#Bean
public IntegrationFlow persistenceSubFlow() {
JsonObjectMapper<?, ?> jsonObjectMapper = new Jackson2JsonObjectMapper(objectMapper);
ReleaseStrategy releaseStrategy = new TimeoutCountSequenceSizeReleaseStrategy(persistenceBatchSize,
persistenceBatchReleaseTimeoutMillis);
// #formatter:off
return f -> f
.transform(Transformers.fromJson(persistentType(), jsonObjectMapper))
// #see http://docs.spring.io/spring-integration/reference/html/messaging-routing-chapter.html#agg-and-group-to
.aggregate(
a -> a
.releaseStrategy(releaseStrategy)
.correlationStrategy(m -> m.getHeaders().get("id"))
.expireGroupsUponCompletion(true)
.sendPartialResultOnExpiry(true)
, null
)
.handle(jdbcRepositoryHandler());
// #formatter:on
}
#Bean
public JdbcRepositoryHandler jdbcRepositoryHandler() {
return new JdbcRepositoryHandler(repositoryType(), beanFactory);
}
protected class JdbcRepositoryHandler extends AbstractMessageHandler {
#SuppressWarnings("rawtypes")
private Insertable repository;
public JdbcRepositoryHandler(Class<?> repositoryClass, ListableBeanFactory beanFactory) {
repository = (Insertable<?>) beanFactory.getBean(repositoryClass);
}
#Override
protected void handleMessageInternal(Message<?> message) {
repository.insert((List<?>) message.getPayload());
}
}
protected class FileToInputStreamTransformer extends AbstractFilePayloadTransformer<InputStream> {
#Override
protected InputStream transformFile(File payload) throws Exception {
return new GZIPInputStream(new FileInputStream(payload));
}
}
}
Yes, you are correct. To advice the handle() method of Transformer's MessageHandler you should use exactly that e.advice method of the second parameter of .transform() EIP-method. And yes: you should define ExpressionEvaluatingRequestHandlerAdvice bean for your purpose.
You can reuse that Advice bean for different goals to handle successes and failures the same manner.
UPDATE
Although it isn't clear to me how you'd like to continue the flow with the wrong message, but you you can use onFailureExpression and returnFailureExpressionResult=true of the ExpressionEvaluatingRequestHandlerAdvice to return something after the unzipErrorChannel().
BTW the failureChannel logic doesn't work without onFailureExpression:
if (this.onFailureExpression != null) {
Object evalResult = this.evaluateFailureExpression(message, actualException);
if (this.returnFailureExpressionResult) {
return evalResult;
}
}
I'm stuck trying to send JSON data to by Struts2 REST server using the struts2-rest-plugin.
It works with XML, but I can't seem to figure out the right JSON format to send it in.
Anybody has any experience with this?
Thanks,
Shaun
Update:
Sorry I wasn't clear. The problem is that Struts2 doesn't seem to be mapping the JSON data I send in to my model in the controller.
Here's the code:
Controller:
public class ClientfeatureController extends ControllerParent implements ModelDriven<Object> {
private ClientFeatureService clientFeatureService;
private ClientFeature clientFeature = new ClientFeature();
private List<ClientFeature> clientFeatureList;
//Client ID
private String id;
public ClientfeatureController() {
super(ClientfeatureController.class);
}
#Override
public Object getModel() {
return (clientFeatureList != null ? clientFeatureList : clientFeature);
}
/**
* #return clientFeatureList through Struts2 model-driven design
*/
public HttpHeaders show() {
//logic to return all client features here. this works fine..
//todo: add ETag and lastModified information for client caching purposes
return new DefaultHttpHeaders("show").disableCaching();
}
// PUT request
public String update() {
logger.info("client id: " + clientFeature.getClientId());
logger.info("clientFeature updated: " + clientFeature.getFeature().getDescription());
return "update";
}
public HttpHeaders create() {
logger.info("client id: " + clientFeature.getClientId());
logger.info("feature description: " + clientFeature.getFeature().getDescription());
return new DefaultHttpHeaders("create");
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public void setClientFeatureService(ClientFeatureService clientFeatureService) {
this.clientFeatureService = clientFeatureService;
}
public List<ClientFeature> getClientFeatureList() {
return clientFeatureList;
}
public void setClientFeatureList(List<ClientFeature> clientFeatureList) {
this.clientFeatureList = clientFeatureList;
}
public ClientFeature getClientFeature() {
return clientFeature;
}
public void setClientFeature(ClientFeature clientFeature) {
this.clientFeature = clientFeature;
}
}
This is the URL I'm making the request to:
..http://localhost:8080/coreserviceswrapper/clientfeature.json
-Method: POST or PUT (tried both, POST maps to create() and PUT maps to update())
-Header: Content-Type: application/json
Payload:
{"clientFeature":{
"feature": {
"id": 2,
"enabled": true,
"description": "description1",
"type": "type1"
},
"countries": ["SG"],
"clientId": 10}
}
And the output in the Struts2 logs when I make the request:
1356436 [http-bio-8080-exec-5] WARN net.sf.json.JSONObject - Tried to assign property clientFeature:java.lang.Object to bean of class com.foo.bar.entity.ClientFeature
1359043 [http-bio-8080-exec-5] INFO com.foo.bar.rest.ClientfeatureController - client id: null
Let me also add that XML requests work just fine:
URL: ..http://localhost:8080/coreserviceswrapper/clientfeature.xml
Method: POST/PUT
Content-Type: text/xml
Payload:
<com.foo.bar.entity.ClientFeature>
<clientId>100</clientId>
<feature>
<description>test</description>
</feature>
</com.foo.bar.entity.ClientFeature>
Output:
1738685 [http-bio-8080-exec-7] INFO com.foo.bar.rest.ClientfeatureController - client id: 100
1738685 [http-bio-8080-exec-7] INFO com.foo.bar.rest.ClientfeatureController - feature description: test
1738717 [http-bio-8080-exec-7] INFO org.apache.struts2.rest.RestActionInvocation - Executed action [/clientfeature!create!xml!200] took 1466 ms (execution: 1436 ms, result: 30 ms)
I also encounter same issue, my environment is:
Structs 2.3.16.3, Jquery 1.11, Struts-rest-plugin
symptom: post json data, rest controller not parse json data to model.
solution:
since the controller is modeldriven, browser client just post Json string is OK. but seems you have to force jquery to change conenttype of ajax call.
_self.update= function(model, callback) {
$.ajax({
beforeSend: function(xhrObj){
xhrObj.setRequestHeader("Content-Type","application/json");
xhrObj.setRequestHeader("Accept","application/json");
},
type: 'PUT',
url: this.svrUrl+"/"+ model.id + this.extension,
data: JSON.stringify(model), // '{"name":"' + model.name + '"}',
//contentType: this.contentType,
//dataType: this.dataType,
processData: false,
success: callback,
error: function(req, status, ex) {},
timeout:60000
});
};
the model data format is :
var model = {"id":"2",
"name":"name2",
"author":"author2",
"key":"key2"
}
when you put or post data whit "Content-Type"="application/json", the plugin will handle it with Jsonhandler automatically.
I got such a problem. Strange but got solved by changing the name 'clientFeature' to 'model'