Can I use mongock with Spring Data Repositories? - spring-data-mongodb

I tried to inject with #Autowired annotation a repository into changelog
and it doesn't get injected.
Config uses spring application context
#Bean
public SpringBootMongock mongock(ApplicationContext springContext, MongoClient mongoClient) {
return new SpringBootMongockBuilder(mongoClient, "yourDbName", "com.package.to.be.scanned.for.changesets")
.setApplicationContext(springContext)
.setLockQuickConfig()
.build();
}
And the changelog
#ChangeLog(order = "001")
public class MyMigration {
#Autowired
private MyRepository repo;
#ChangeSet(order = "001", id = "someChangeId", author = "testAuthor")
public void importantWorkToDo(DB db){
repo.findAll() // here null pointer
}
}

firstly, notice that if you are using repositories in your changelogs, it's a bad practice to use it for writes, as it won't be covered by the lock mechanism(this is feature is coming soon), only for reads.
To inject your repository(or any other dependency) you simply need to inject it in your changeSet method signature, like this:
#ChangeLog(order = "001")
public class MyMigration {
#ChangeSet(order = "001", id = "someChangeId", author = "testAuthor")
public void importantWorkToDo(MongoTemplate template, MyRepository repo){
repo.findAll(); this should work
}
}
Notice that you should use the last version(at this moment 3.2.4) and DB class is not supported anymore. Please use MongoDatabase or MongoTemplate(preferred).
Documentation to Mongock

we have recently released the version 4.0.7.alpha, which among other things allows you to use Spring repositories(and any other custom bean you wish) in your changeSets with no problem. You can insert, update, delete and read. It will be safely covered by the lock.
The only restriction is that it needs to be an interface, which should be the common case for Spring repositories.
Please take a look to this example

Related

Quarkus extension - How to have an interceptor working on beans provided by the extension itself

I'm working on a Quarkus extension that provides an interceptor (and its annotation) to add some retry logic around business methods this extension offers. Nothing new in there, and this is working when i annotate a public method of a bean in an application that uses this extension.
But the extension also provides some #ApplicationScoped beans that are also annotated, but the interceptor is not intercepting any of these.
Seems like an interceptor does not check / apply on the extension itself.
I would like to know if this is an intended behavior, or an issue in my extension setup, and if so how to fix it. Could not find anything about this in the documentation, but there is so much dos that i may have missed something.
Any idea about this ?
I finally found a way to make this work.
I was using a producer bean pattern to produce my beam as an #ApplicationScoped bean inside the extension.
#ApplicationScoped
public class ProxyProducer {
#Produces
#ApplicationScoped
public BeanA setUpBean(ExtensionConfig config)
{
return new BeamsClientProxy(new InternalBean(config.prop1, config.prop2));
}
}
with the following BeanA class (just an example)
public class BeanA {
private final InternalBean innerBean;
public BeanA(final InternalBean innerBean) {
this.innerBean = innerBean;
}
#MyInterceptedAnnotation
public void doSomething() {
}
}
Due to this setup, the bean is not considered by the interceptor (i guess because it's produced only the first time it's used / injected somewhere else)
Removing the producer pattern and annotating directly the BeanA fixed the issue.
Example:
#ApplicationScoped
public class BeanA {
private final InternalBean innerBean;
public BeanA(final ExtensionConfig config) {
this.innerBean = new InternalBean(config.prop1, config.prop2);
}
#MyInterceptedAnnotation
public void doSomething() {
}
}
with of course adding the following lines to register the bean directly on the extension processor:
#BuildStep
AdditionalBeanBuildItem proxyProducer() {
return AdditionalBeanBuildItem.unremovableOf(BeanA.class);
}
As a conclusion:
Changing the bean implementation to avoid the producer-based bean use case solved my issue (please refers to Ladicek comment below)
Edit:
As Ladicek explained, Quarkus doesn't support interception on producer-based beans.

Spring rest application and exception localization

I am developing a spring boot app with restful services and angularjs as front end. The application must support multiple languages. One of the things I am having problem is the business exceptions thrown from my services. E.g. I have a book service which may throw an exception like this
if (book == null) {
throw new ServiceException("The book you are looking for no longer exist");
}
What is the best approach to localize them?
You have to use #RestControllerAdvice to seprate your exception handling logic from your business code. As per #ControllerAdvice doc, the methods defined in the class annotated as #ControllerAdvice apply globally to all Controllers. #RestControllerAdvice is just a convenience class equal to (#RestControllerAdvice = #ControllerAdvice + #ResponseBody). Please check below class:
#RestControllerAdvice
public class GenericExceptionHandler {
#Autowired
private MessageSource messageSource;
#ExceptionHandler(ServiceException.class)
public ResponseEntity<ErrorResponse> handle(ServiceException.class e, Locale locale) {
String errorMessage = messageSource.getMessage(
"error.message", new Object[]{},locale);
ErrorResponse error = new ErrorResponse();
error.setErrorCode(HttpStatus.BAD_REQUEST.value());
error.setMessage(errorMessage);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
}
// other Custom Exception handlers
Your ErrorResponse is normal javabean as below:
public class ErrorResponse{
private int errorCode;
private String message;
//getter and setter
}
And you should have MessageSource configured in your configuration to read the locale specific error messages as below:
#Configuration
public class MessageConfig {
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasename("i18n/messages");
source.setUseCodeAsDefaultMessage(true);
return source;
}
}
I would like to suggest use of #ControllerAdvice and #ExceptionHandler.
Also you can use #RestControllerAdvice, find example here

java ee 7 : using interceptors for centralized logging

I'd like to have one class responsible for my logging. The way I understand it, that's what interceptors can be used for. Here's my interceptor:
package logging;
import java.io.Serializable;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
public class LoggingInterceptor implements Serializable {
/**
*
*/
private static final long serialVersionUID = -2095670799863528243L;
#AroundInvoke
public Object intercept(InvocationContext context) throws Exception {
System.out.println("before calling method :"
+ context.getMethod().getName());
Object[] params = context.getParameters();
for (Object param : params) {
System.out.println("PARAM " + param.toString());
}
Object result = context.proceed();
System.out.println("after calling method :"
+ context.getMethod().getName());
return result;
}
}
I annotated various methods with the #Interceptor annotation.
My question is: How do I differentiate between the methods that are called? Depending on the method, I want to log a different message and maybe some parameters like folder names.
Right now, the only thing I can think of is a big if-elseif-else or switch statement to check the name of the method.
But this seems to be poor design. Am I using the interceptor for the right purpose? And if so, how would I go about implementing logging in a clean way?
I'd like to have one class responsible for logging because I want to display methods on the user interface as well as log to a file. Also, I'd like to use some java ee 7 built-in java ee 7 mechanism for such a cross-cutting concern.

Objectify with Cloud Endpoints

I am using appengine cloud endpoints and objectify. I have previously deployed these endpoints before and now I am updating them and it is not working with Objectify. I have moved to a new machine and running latest appengine 1.8.6. Have tried putting objectify in the classpath and that did not work. I know this can work, what am I missing??
When running endpoints.sh:
Error: Parameterized type
com.googlecode.objectify.Key<MyClass> not supported.
UPDATE:
I went back to my old computer and ran endpoints.sh on same endpoint and it worked fine. Old machine has 1.8.3. I am using objectify 3.1.
UPDATE 2:
Updated my old machine to 1.8.6 and get same error as other machine. Leaves 2 possibilities:
1) Endpoints no longer support objectify 3.1
or
2) Endpoints have a bug in most recent version
Most likely #1...I've been meaning to update to 4.0 anyways...
Because of the popularity of Objectify, a workaround was added in prior releases to support the Key type, until a more general solution was available. Because the new solution is available, the workaround has been removed. There are two ways you can now approach the issue with the property.
Add an #ApiResourceProperty annotation that causes the key to be omitted from your object during serialization. Use this approach if you want a simple solution and don't need access to the key in your clients.
Add an #ApiTransformer annotation that provides a compatible mechanism to serialize/deserialize the field. Use this approach if need access to the key (or a representation of it) in your clients. As this requires writing a transformer class, it is more work than the first option.
I came up with the following solution for my project:
#Entity
public class Car {
#Id Long id;
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
Key<Driver> driver;
public Key<Driver> getDriver() {
return driver;
}
public void setDriver(Key<Driver> driver) {
this.driver = driver;
}
public Long getDriverId() {
return driver == null ? null : driver.getId();
}
public void setDriverId(Long driverId) {
driver = Key.create(Driver.class, driverId);
}
}
#Entity
public class Driver {
#Id Long id;
}
I know, it's a little bit boilerplate, but hey - it works and adds some handy shortcut methods.
At first, I did not understand the answer given by Flori, and how useful it really is. Because others may benefit, I will give a short explanation.
As explained earlier, you can use #ApiTransformer to define a transformer for your class. This would transform an unserializable field, like those of type Key<myClass> into something else, like a Long.
It turns out that when a class is processed by GCE, methods called get{fieldName} and set{FieldName} are automatically used to transform the field {fieldName}. I have not been able to find this anywhere in Google's documentation.
Here is how I use it for the Key{Machine} property in my Exercise class:
public class Exercise {
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public Key<Machine> machine;
// ... more properties
public Long getMachineId() {
return this.machine.getId();
}
public void setMachineId(Long machineId) {
this.machine = new Key<Machine>(Machine.class, machineId);
}
// ...
}
Others already mentioned how to approach this with #ApiResourceProperty and #ApiTransformer. But I do need the key available in client-side, and I don't wanna transform the whole entity for every one. I tried replacing the Objectify Key with com.google.appengine.api.datastore.Key, and it looks like it worked just fine as well in my case, since the problem here is mainly due to that endpoint does not support parameterized types.

Unable to return collections or arrays from JAX-WS Web Service

I found that I was unable to return collections from my JAX-WS Web Service.
I appreciate that the Java Collections API may not be supported by all clients, so I switched to return an array, but I can't seem to do this either.
I've set up my web service as follows:
#WebService
public class MyClass {
public ReturnClass[] getArrayOfStuff() {
// extremely complex business logic... or not
return new ReturnClass[] {new ReturnClass(), new ReturnClass()};
}
}
And the ReturnClass is just a POJO. I created another method that returns a single instance, and that works. It just seems to be a problem when I use collections/arrays.
When I deploy the service, I get the following exception when I use it:
javax.xml.bind.MarshalException - with linked exception:
[javax.xml.bind.JAXBException: [LReturnClass; is not known to this context]
Do I need to annotate the ReturnClass class somehow to make JAX-WS aware of it?
Or have I done something else wrong?
I am unsure of wheter this is the correct way to do it, but in one case where I wanted to return a collection I wrapped the collection inside another class:
#WebService
public class MyClass {
public CollectionOfStuff getArrayOfStuff() {
return new CollectionOfStuff(new ReturnClass(), new ReturnClass());
}
}
And then:
public class CollectionOfStuff {
// Stuff here
private List<ReturnClass> = new ArrayList<ReturnClass>();
public CollectionOfStuff(ReturnClass... args) {
// ...
}
}
Disclaimer: I don't have the actual code in front of me, so I guess my example lacks some annotations or the like, but that's the gist of it.

Resources