Using #ExceptionHandler or some other annotation that would work like a Spring 4.1 AsyncUncaughtExceptionHandler - spring

I would like to configure and use a Spring 4.1 AsyncUncaughtExceptionHandler. According to the Spring team (see relevant comment here) one will be able to configure an AsyncUncaughtExceptionHandler either by with the <task:annotation-driven> or by implementing AsyncConfigurer as shown here:
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler() ;
}
Now my question is as follows: Is there another web-layer annotation similar to #ExceptionHandler that would work like a AsyncUncaughtExceptionHandler?

As stated in the comment, here's an approach I've taken:
It's about async data imports so all classes are called Import...
What I did not do (yet) is the uncaught exception handling, but reading your post made me think about it and it should be straight forward with Spring-AOP wrapping the Importer.process() methods. This will not be global solution but it would be adaptable for a complete application by using a more generalized Result object.
The Controller uses the ImportRequests to get processing (or done) messages. The Importer itself is not removing the results from the map but this is delegated to the controller instead (A user is clicking delete). We also have a #Scheduled task which cleans up done results after 1 hour to ensure there are not left-overs.
So here's part of the code that the Controller is able to get import results during processing:
#Service
public class ImportRequests {
private final Map<User, ImportResult> importRequests = new ConcurrentHashMap<>();
/** Add, remove, get methods for current user omitted */
}
public class ImportResult {
/** The done. */
private Future<Boolean> done;
/** The error messages. */
private List<String> messages = Collections.synchronizedList(new ArrayList<String>());;
}
#Service
public class ImportService {
#Autowired
private ImportRequests importRequests;
#Autowired
private Importer importer;
public ImportResult doImport(final ImportForm importForm) {
ImportResult result = new ImportResult();
importRequests.addImportResultForCurrentUser(result);
/* This is the actual Async call (process) */
result.setDone(importer.process(result));
return result;
}
}
#Service
public class ImporterImpl implements Importer {
/**
* doProcess will import the *big* file and update the result object with the necessary messages
*/
#Async
public Future<Boolean> process(ImportResult result) {
Boolean done = doProcess(result);
return new AsyncResult<Boolean>(done);
}
}
Hope this helps.
Original Text:
One possibility that I have used is the "#ControllerAdvice" on a class scanned by the servletcontext.
You simply create a method with the exception as a parameter and annotate that method with "#ExceptionHandler". You can even have multiple handlers for specific exception types.
The result of these methods are again handled by the DispatcherServlet, so you can render a view the same way as with request mappings.

Related

Overriding repository endpoints automatically created by Spring Data Rest

I have a Spring project with spring-data-rest as a dependency. I have quite a number of repositories in my project, which spring-data-rest automatically created REST API endpoints for. This suited my needs pretty well until now. Now I have a requirement to change the default functionality of one endpoint for all my repositories, specifically, /BASE_PATH/REPOSITORY. This path responds with a paged list of all records of my db.
Now I want to reimplement this endpoint for all my repositories. This is where I am hitting a roadblock. I tried
#RestController
public class MyTableResource {
private MyTableService myTableService;
#Autowired
public MyTableResource(MyTableService myTableService) {
this.myTableService = myTableService;
}
#GetMapping(value = "/api/v1/myTables", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity getMyTables(#QuerydslPredicate(root = MyTable.class) Predicate predicate) throws NoSuchMethodException {
// My custom implementation
}
}
Now this somewhat works but the problem is I need to write almost the same code for all my repositories. I tried #GetMapping(value = "/api/v1/{repository}", produces = MediaTypes.HAL_JSON_VALUE) but this is also matching /api/v1/notarepository which I have implemented separately.
Also, even if I do #GetMapping(value = "/api/v1/{repository}", produces = MediaTypes.HAL_JSON_VALUE) I would like to get a handle to a repository object (MyTable) using {repository} path variable, which would be myTables in this case.
In short, I want to write a single custom controller for all my repositories, since the logic would be the same for each of them, while making sure the correct repository is called based on the path called also making sure that any path variables I introduce does not hide other controller classes I have written.
More things I have tried
I was attempting to get paged HATEOAS resource objects automatically from my list of entities. For this I found that I can use PagedResourceAssembler
#RestController
public class MyTableResource {
private MyTableService myTableService;
#Autowired
public MyTableResource(MyTableService myTableService) {
this.myTableService = myTableService;
}
#GetMapping(value = "/api/v1/myTables", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity getMyTables(#QuerydslPredicate(root = MyTable.class) Predicate predicate, PagedResourcesAssembler<Object> pagedResourcesAssembler) throws NoSuchMethodException {
// My custom implementation
return ResponseEntity.ok(pagedResourcesAssembler.toResource(myTableList);
}
}
This gives me a good response with the required links for the page but does not give links per entity. Then I found I can hook up PersistentEntityResourceAssembler and pass it to toResource above so I did
#RestController
public class MyTableResource {
private MyTableService myTableService;
#Autowired
public MyTableResource(MyTableService myTableService) {
this.myTableService = myTableService;
}
#GetMapping(value = "/api/v1/myTables", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity getMyTables(#QuerydslPredicate(root = MyTable.class) Predicate predicate, PagedResourcesAssembler<Object> pagedResourcesAssembler, PersistentEntityResourceAssembler assembler) throws NoSuchMethodException {
// My custom implementation
return ResponseEntity.ok(pagedResourcesAssembler.toResource(myTableList, assembler);
}
}
This does not work as reported in How to have PersistentEntityResourceAssembler injected into request methods of custom #RepositoryRestController in a #WebMvcTest unit test .
It kind of works if I replace #RestController with RepositoryRestController but then Predicate stops working as mentioned in https://jira.spring.io/browse/DATAREST-838 .
So, I tried using #QuerydslPredicate RootResourceInformation resourceInformation instead of #QuerydslPredicate(root = MyTable.class) Predicate predicate. This also did not work as my controller endpoint does not have /{repository} in it.
Then I tried setting #GetMapping(value = "/{repository}" produces = MediaTypes.HAL_JSON_VALUE). This threw a mapping conflict error.
So I am completely stuck as to what to do next.
You can extend the default behavior provided by Spring Data Rest by extending RepositoryRestMvcConfiguration.
RepositoryRestMvcConfiguration has a DelegatingHandlerMapping bean which holds a list of HandlerMapping. Spring iterates over this list and tries to find a handler for the request. The order of this list is important. The first one gets picked up first for the execution. So if we add a new handler in front of the ones we already have then our HandlerMapping will be called.
You can use whatever logic you want to find the handler for the request. In your case, this would be if the path variable is a repository name.
The following code adds a new handler:
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
#Configuration
public class CustomRestMvcConfiguration extends RepositoryRestMvcConfiguration {
public CustomRestMvcConfiguration(ApplicationContext context,
ObjectFactory<ConversionService> conversionService) {
super(context, conversionService);
}
#Override public DelegatingHandlerMapping restHandlerMapping() {
DelegatingHandlerMapping delegatingHandlerMapping = super.restHandlerMapping();
List<HandlerMapping> delegates = delegatingHandlerMapping.getDelegates();
delegates.add(0, new HandlerMapping() {
#Override public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//Your custom logic to decide if you should handle the request
//If you don't want to handle the request return null
return null;
}
});
return new DelegatingHandlerMapping(delegates);
}
}
Hope this helps!
Note: RepositoryRestHandlerMapping is the default one you can check it while writing your logic. It might be helpful.

JPA - Spanning a transaction over multiple JpaRepository method calls

I'm using SpringBoot 2.x with SpringData-JPA accessing the database via a CrudRepository.
Basically, I would like to call the CrudRepository's methods to update or persist the data. In one use case, I would like to delete older entries from the database (for the brevity of this example assume: delete all entries from the table) before I insert a new element.
In case persisting the new element fails for any reason, the delete operation shall be rolled back.
However, the main problem seems to be that new transactions are opened for every method called from the CrudRepository. Even though, a transaction was opened by the method from the calling service. I couldn't get the repository methods to use the existing transaction.
Getting transaction for [org.example.jpatrans.ChairUpdaterService.updateChairs]
Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.deleteWithinGivenTransaction]
Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.deleteWithinGivenTransaction]
I've tried using different Propagation. (REQUIRED, SUPPORTED, MANDATORY) on different methods (service/repository) to no avail.
Changing the methods #Transactional annoation to #Transactional(propagation = Propagation.NESTED) sounded that this would just do that, but didn't help.
JpaDialect does not support savepoints - check your JPA provider's capabilities
Can I achieve the expected behaviour, not using an EntityManager directly?
I also would like to avoid to having to be using native queries as well.
Is there anything I have overlooked?
For demonstration purposes, I've created a very condensed example.
The complete example can be found at https://gitlab.com/cyc1ingsir/stackoverlow_jpa_transactions
Here are the main (even more simplified) details:
First I've got a very simple entity defined:
#Entity
#Table(name = "chair")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Chair {
// Not auto generating the id is on purpose
// for later testing with non unique keys
#Id
private int id;
#Column(name = "legs", nullable = false)
private Integer legs;
}
The connection to the database is made via the CrudRepository:
#Repository
public interface ChairRepository extends CrudRepository<Chair, Integer> {
}
This is being called from another bean (main methods here are updateChairs and doUpdate):
#Slf4j
#Service
#AllArgsConstructor
#Transactional
public class ChairUpdater {
ChairRepository repository;
/*
* Initialize the data store with some
* sample data
*/
public void initializeChairs() {
repository.deleteAll();
Chair chair4 = new Chair(1, 4);
Chair chair3 = new Chair(2, 3);
repository.save(chair4);
repository.save(chair3);
}
public void addChair(int id, Integer legCount) {
repository.save(new Chair(id, legCount));
}
/*
* Expected behaviour:
* when saving a given chair fails ->
* deleting all other is rolled back
*/
#Transactional
public void updateChairs(int id, Integer legCount) {
Chair chair = new Chair(id, legCount);
repository.deleteAll();
repository.save(chair);
}
}
The goal, I want to achieve is demonstrated by these two test cases:
#Slf4j
#RunWith(SpringRunner.class)
#DataJpaTest
#Import(ChairUpdater.class)
public class ChairUpdaterTest {
private static final int COUNT_AFTER_ROLLBACK = 3;
#Autowired
private ChairUpdater updater;
#Autowired
private ChairRepository repository;
#Before
public void setup() {
updater.initializeChairs();
}
#Test
public void positiveTest() throws UpdatingException {
updater.updateChairs(3, 10);
}
#Test
public void testRollingBack() {
// Trying to update with an invalid element
// to force rollback
try {
updater.updateChairs(3, null);
} catch (Exception e) {
LOGGER.info("Rolled back?", e);
}
// Adding a valid element after the rollback
// should succeed
updater.addChair(4, 10);
assertEquals(COUNT_AFTER_ROLLBACK, repository.findAll().spliterator().getExactSizeIfKnown());
}
}
Update:
It seems to work, if the repository is not extended from either CrudRepository or JpaRepository but from a plain Repository, definening all needed methods explicitly. For me, that seems to be a workaround rather than beeing a propper solution.
The question it boils down to seems to be: Is it possible to prevent SimpleJpaRepository from opening new transactions for every (predefined) method used from the repository interface? Or, if that is not possible, how to "force" the transaction manager to reuse the transaction, opened in the service to make a complete rollback possible?
Hi I found this documentation that looks will help you:
https://www.logicbig.com/tutorials/spring-framework/spring-data/transactions.html
Next an example take from the previous web site:
#Configuration
**#ComponentScan
#EnableTransactionManagement**
public class AppConfig {
....
}
Then we can use transactions like this:
#Service
public class MyExampleBean{
**#Transactional**
public void saveChanges() {
**repo.save(..);
repo.deleteById(..);**
.....
}
}
Yes this is possible. First alter the #Transactional annotation so that it includes rollBackFor = Exception.class.
/*
* Expected behaviour:
* when saving a given chair fails ->
* deleting all other is rolled back
*/
#Transactional(rollbackFor = Exception.class)
public void updateChairs(int id, Integer legCount) {
Chair chair = new Chair(id, legCount);
repository.deleteAll();
repository.save(chair);
}
This will cause the transaction to roll back for any exception and not just RuntimeException or Error.
Next you must add enableDefaultTransactions = false to #EnableJpaRepositories and put the annotation on one of your configuration classes if you hadn't already done so.
#Configuration
#EnableJpaRepositories(enableDefaultTransactions = false)
public class MyConfig{
}
This will cause all inherited jpa methods to stop creating a transaction by default whenever they're called. If you want custom jpa methods that you've defined yourself to also use the transaction of the calling service method, then you must make sure that you didn't annotate any of these custom methods with #Transactional. Because that would prompt them to start their own transactions as well.
Once you've done this all of the repository methods should be executed using the service method transaction only. You can test this by creating and using a custom update method that is annotated with #Modifying. For more on testing please see my answer in this SO thread. Spring opens a new transaction for each JpaRepository method that is called within an #Transactional annotated method

Does Spring Data REST event hanlders use separate database transactions?

Spring Data REST has the following Event handlers which are fired on HTTP requests like POST, PUT etc.
#RepositoryEventHandler(Author.class)
public class AuthorEventHandler {
Logger logger = Logger.getLogger("Class AuthorEventHandler");
#HandleBeforeCreate
public void handleAuthorBeforeCreate(Author author){
logger.info("Inside Author Before Create....");
}
#HandleAfterCreate
public void handleAuthorAfterCreate(Author author){
logger.info("Inside Author After Create ....");
}
}
My question is if I access another database entity, eg.Book, within #HandleBeforeCreate and modify it, would it occur in a separate transaction or will it occur in the same transaction as the creation of the Author entity?
I already check the Spring Data REST docs but it is not mentioned there.
From my experience, those handlers are performed beyond the main transaction. Literally 'before' and 'after' it. About 'separate' transaction - if you mark your event handler as #Transactional it will be executed in its individual transaction.
Publishing domain events from the aggregate root
If you need to perform some extra actions within the main transaction you can use publishing events from the aggregate root. In this case, you should extend your entity from AbstractAggregateRoot and add to it some methods that register appropriate events, for example:
#Entity
public class Model extends AbstractAggregateRoot {
// entity stuff...
public Model initExtraAction(SomeData payload) {
registerEvent(new ExtraActionEvent(this, payload));
return this;
}
}
where registerEvent is the AbstractAggregateRoot method, and ExtraActionEvent is your custom event, like the folowing:
#Value
public class ExtraActionEvent {
private Model model;
private SomeData payload;
}
Then you can implement an ordinary event handler
#Service
public class EventHandler {
#EventListener
#Transactional(propagation = MANDATORY) // optional
public void handleExtraActionEvent (ExtraActionEvent e) {
Model model = e.getModel();
SomeData payload = e.getPayload();
// Extra actions...
}
}
that will be called in the same transaction as the main one (which saves your entity) if you call initExtendAction method before invoking the save method of your repo (to make sure that this will be done in the same transaction you can use an optional #Transactional(propagation = MANDATORY) annotation):
modelRepo.save(model.initExtraAction(payload));
In the Spring Data REST project we can call initExtraAction method in the 'RepositoryEventHandler' before the entity will be created or updated:
#RepositoryEventHandler(Model.class)
public class ModelEventHandler {
#HandleBeforeCreate
#HandleBeforeSave
public void handleBeforeCreateOrSave(Model model){
// Some manipulations...
model.initExtraAction(...);
}
}
You can find a full example of using AbstractAggregateRoot in the Oliver Gierke Spring RestBucks demo project.
Additional info: DDD Aggregates and #DomainEvents

Why does Spring Data MongoDB not expose events for update…(…) methods?

It appears that the update for mongoOperations do not trigger the events in AbstractMongoEventListener.
This post indicates that was at least the case in Nov 2014
Is there currently any way to listen to update events like below? This seems to be quite a big omission if it is the case.
MongoTemplate.updateMulti()
Thanks!
This is no oversight. Events are designed around the lifecycle of a domain object or a document at least, which means they usually contain an instance of the domain object you're interested in.
Updates on the other hand are completely handled in the database. So there are no documents or even domain objects handled in MongoTemplate. Consider this basically the same way JPA #EntityListeners are only triggered for entities that are loaded into the persistence context in the first place, but not triggered when a query is executed as the execution of the query is happening in the database.
I know it's too late to answer this Question, I have the same situation with MongoTemplate.findAndModify method and the reason I needed events is for Auditing purpose. here is what i did.
1.EventPublisher (which is ofc MongoTemplate's methods)
public class CustomMongoTemplate extends MongoTemplate {
private ApplicationEventPublisher applicationEventPublisher;
#Autowired
public void setApplicationEventPublisher(ApplicationEventPublisher
applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
//Default Constructor here
#Override
public <T> T findAndModify(Query query, Update update, Class<T> entityClass) {
T result = super.findAndModify(query, update, entityClass);
//Publishing Custom Event on findAndModify
if(result!=null && result instanceof Parent)//All of my Domain class extends Parent
this.applicationEventPublisher.publishEvent(new AfterFindAndModify
(this,((Parent)result).getId(),
result.getClass().toString())
);
return result;
} }
2.Application Event
public class AfterFindAndModify extends ApplicationEvent {
private DocumentAuditLog documentAuditLog;
public AfterFindAndModify(Object source, String documentId,
String documentObject) {
super(source);
this.documentAuditLog = new DocumentAuditLog(documentId,
documentObject,new Date(),"UPDATE");
}
public DocumentAuditLog getDocumentAuditLog() {
return documentAuditLog;
}
}
3.Application Listener
public class FindandUpdateMongoEventListner implements ApplicationListener<AfterFindAndModify> {
#Autowired
MongoOperations mongoOperations;
#Override
public void onApplicationEvent(AfterFindAndModify event) {
mongoOperations.save(event.getDocumentAuditLog());
}
}
and then
#Configuration
#EnableMongoRepositories(basePackages = "my.pkg")
#ComponentScan(basePackages = {"my.pkg"})
public class MongoConfig extends AbstractMongoConfiguration {
//.....
#Bean
public FindandUpdateMongoEventListner findandUpdateMongoEventListner(){
return new FindandUpdateMongoEventListner();
}
}
You can listen to database changes, even the changes completely outside your program (MongoDB 4.2 and newer).
(code is in kotlin language. same for java)
#Autowired private lateinit var op: MongoTemplate
#PostConstruct
fun listenOnExternalChanges() {
Thread {
op.getCollection("Item").watch().onEach {
if(it.updateDescription.updatedFields.containsKey("name")) {
println("name changed on a document: ${it.updateDescription.updatedFields["name"]}")
}
}
}.start()
}
This code only works when replication is enabled. You can enable it even when you have a single node:
Add the following replica set details to mongodb.conf (/etc/mongodb.conf or /usr/local/etc/mongod.conf or C:\Program Files\MongoDB\Server\4.0\bin\mongod.cfg) file
replication:
replSetName: "local"
Restart mongo service, Then open mongo console and run this command:
rs.initiate()

spring-security global-method-security protect-pointcut with #EnableGlobalMethodSecurity

How does one port from
<sec:global-method-security secured-annotations="disabled">
<sec:protect-pointcut expression='execution(* x.y.z.end*(..))' access='...' />
to spring java-config
#EnableGlobalMethodSecurity
#Configuration
public class MyConfiguration extends WebSecurityConfigurerAdapter {
?
There is a simmilar question here http://forum.spring.io/forum/spring-projects/security/726615-protect-pointcut-in-java-configuration
There's a workaround for it. The security points information is kept in MethodSecurityMetadataSource implementations (which are then used by MethodInterceptor) so we have to create an additional MethodSecurityMetadataSource. As mentioned in the spring forum post the xml pointcut configuration is kept in MapBasedMethodSecurityMetadataSource and processed by ProtectPointcutPostProcessor. we also need an instance of ProtectPointcutPostProcessor. Unfortunately this class is final and package-private so there are 2 options:
create your own class and copy/paste the whole content of the original one (that's what I did)
change the class modifiers with reflection and create an instance of the original one (haven't done that so no idea if it would work fine)
then create the following beans in your context:
#Bean
public Map<String, List<ConfigAttribute>> protectPointcutMap() {
Map<String, List<ConfigAttribute>> map = new HashMap<>();
// all the necessary rules go here
map.put("execution(* your.package.service.*Service.*(..))", SecurityConfig.createList("ROLE_A", "ROLE_B"));
return map;
}
#Bean
public MethodSecurityMetadataSource mappedMethodSecurityMetadataSource() {
// the key is not to provide the above map here. this class will be populated later by ProtectPointcutPostProcessor
return new MapBasedMethodSecurityMetadataSource();
}
// it's either the original spring bean created with reflection or your own copy of it
#Bean
public ProtectPointcutPostProcessor pointcutProcessor() {
ProtectPointcutPostProcessor pointcutProcessor = new ProtectPointcutPostProcessor((MapBasedMethodSecurityMetadataSource) mappedMethodSecurityMetadataSource());
pointcutProcessor.setPointcutMap(protectPointcutMap());
return pointcutProcessor;
}
we've created the necessary beans, now we have to tell spring to use them. I'm assuming you're extending GlobalMethodSecurityConfiguration. by default it creates DelegatingMethodSecurityMetadataSource which contains a list of other MethodSecurityMetadataSources. Depending on what you want to achieve you have following options:
if you want to keep all the other MethodSecurityMetadataSources (like the ones for parsing the #Secured annotations) you can extend the list in the delegating metadata source by overriding the following method:
#Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return mappedMethodSecurityMetadataSource();
}
it would inject it on first place in the list though which may cause some problems.
if you want to keep the other sources but want yours to be the last in the list then override the following method:
#Override
public MethodSecurityMetadataSource methodSecurityMetadataSource() {
DelegatingMethodSecurityMetadataSource metadataSource = (DelegatingMethodSecurityMetadataSource) super.methodSecurityMetadataSource();
metadataSource.getMethodSecurityMetadataSources().add(mappedMethodSecurityMetadataSource());
return metadataSource;
}
if you want your source to be the only one (you don't want to use #Secured or any other annotations) then you can override the same method, just with different content
#Override
public MethodSecurityMetadataSource methodSecurityMetadataSource() {
return mappedMethodSecurityMetadataSource();
}
that's it! I hope it will help
I followed #marhewa comments and have been able to use the Spring version of class ProtectPointcutPostProcessor by defining the following bean
/**
* Needed to use reflection because I couldn't find a way to instantiate a
* ProtectPointcutPostProcessor via a BeanFactory or ApplicationContext. This bean will process
* the AspectJ pointcut defined in the map; check all beans created by Spring; store the matches
* in the MapBasedMethodSecurityMetadataSource bean so Spring can use it during its checks
*
* #return
* #throws Exception
*/
#Bean(name = "protectPointcutPostProcessor")
Object protectPointcutPostProcessor() throws Exception {
Class<?> clazz =
Class.forName("org.springframework.security.config.method.ProtectPointcutPostProcessor");
Constructor<?> declaredConstructor =
clazz.getDeclaredConstructor(MapBasedMethodSecurityMetadataSource.class);
declaredConstructor.setAccessible(true);
Object instance = declaredConstructor.newInstance(pointcutMethodMetadataSource());
Method setPointcutMap = instance.getClass().getMethod("setPointcutMap", Map.class);
setPointcutMap.setAccessible(true);
setPointcutMap.invoke(instance, pointcuts());
return instance;
}
This way I don't need to duplicate the code of this Spring class.
Cheers

Resources