I am trying to make my advice more dynamic based on the class/method it is providing advice for. Looking for something like this pseudoish code:
class Activity
private TheAdviceStrategyInterface activityAdviceStrategy = new ActivityAdviceStrategy();
#Entity(adviceStrategy = activityAdviceStrategy)
public void doSomething(ActivityInput ai) { ... }
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Entity {
public TheAdviceStrategyInterface adviceStrategy();
}
#Aspect
public class TheAdvice {
#Around("#annotation(entity)")
public Object doStuff (ProceedingJoinPoint pjp, Entity entity) {
...
TheAdviceStrategyInterface theStrat = entity.adviceStrategy();
....
}
}
But of course we can not have Objects or Interfaces as annotation parameters.
Any "advice" on how I can implement this? I basically want one Aspect annotation to handle very similar situations, with a slight difference depending on which class is using the annotation.
But of course we can not have Objects or Interfaces as Annotation
parameters. Any "advice" on how I can implement this?
1- Create a String parameter in the Entity interface to represent the possible strategies:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Entity {
public String adviceStrategy();
}
2- Create a class that implements the factory pattern, for instance:
public class TheAdviceStrategyFactory {
//use getShape method to get object of type shape
public TheAdviceStrategyInterface getStrategy(String strategy){
if(strategy == null){
return null;
}
if(strategy.equalsIgnoreCase("Strategy1")){
return new TheAdviceStrategy1();
} else if(strategy.equalsIgnoreCase("Strategy2")){
return new TheAdviceStrategy2();
return null;
}
}
with the Classes TheAdviceStrategy1 and TheAdviceStrategy2 implementing the interface TheAdviceStrategyInterface.
Take advantage of both in the advice:
#Aspect
public class TheAdvice {
#Around("#annotation(entity)")
public Object doStuff (ProceedingJoinPoint pjp, Entity entity) {
...
TheAdviceStrategyFactory factory = new TheAdviceStrategyFactory();
TheAdviceStrategyInterface theStrat = factory.getStrategy(entity.adviceStrategy());
....
}
}
Related
I have created REST controller with base request mapping on class.
#RestController
#RequestMapping(".../{type}/{typeId}/param..")
public class FooController{
#Autowired
BarServiceProxy proxy;
public List<Foo> getFoo(){
return proxy.get(getType());
}
/*
public Type getType(???){
return type;
}
*/
}
Next I have enum Type which determines what service will be used by proxy service (ie. proxy has injected list of serivces and gets one that supports type). I am wondering if there is any way how to make part of request mapping {type} and get it in getter method below so I don't have to repeat it in every request mapping in this class.
I only figured one alternative solution - make this class abstract and then extend it and return constant. This would however leave me with lot of classes without any added value. For example:
#RequestMapping(".../{typeId}/param..")
public abstract class FooController{
#Autowired
BarServiceProxy proxy;
public List<Foo> getFoo(){
return proxy.get(getType());
}
protected abstract Type getType();
}
#RestController
#RequestMapping("/typeAbc)
public class TypeAbcFooController extends FooController{
public Type getType{
return Type.Abc;
}
}
So is it possible to bind #PathVariable from URL specified on class #RequestMapping in some shared method? Thanks
I hope i've understood your problem, but one way of improving your design could be to implement a strategy per type, to inject them, and to use them corresponding to your type received in your controller.
Exemple:
public enum MyType {
TYPE1,
TYPE2
}
public interface IService {
MyType getHandledType();
List<Foo> getFoo();
}
#Service
public class Type1Service implements IService {
#Override
public MyType getHandledType() {
return MyType.TYPE1;
}
#Override
public List<Foo> getFoo() {
// IMPLEMENTATION FOR TYPE1;
}
}
public class FooController{
#Autowired
List<IService> services;
public List<Foo> getFoo(MyType requestType){
IService service = services.stream().filter(iService -> iService.getHandledType() == requestType).findFirst().get();
return service.getFoo();
}
}
This way your controller is agnostic of the underlying service implementation, which is a big responsability.
I am trying to add json serialization to my SpringBoot app using MapStruct. #Mapper class uses #Service to add some "aftermapping" logic. The problem is, that this #Service class is not autowired.
This is my Mapper class:
#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = "spring")
public abstract class InstrumentMapper {
protected MarketDataService marketDataService; // is #Service
#Mapping(target = "marketCode",
expression = "java(instrument.getMarket().getCode())")
public abstract InstrumentDto fromInstrument(Instrument instrument);
public abstract List<InstrumentDto> fromInstruments(List<Instrument> instruments);
#Mapping(target = "market",
expression = "java(marketDataService.findMarketByCode(instrumentDto.getMarketCode()))")
public abstract Instrument toInstrument(InstrumentDto instrumentDto);
public abstract List<Instrument> toInstruments(List<InstrumentDto> instrumentDtos);
#Autowired
public void setMarketDataService(MarketDataService marketDataService) {
this.marketDataService = marketDataService;
}
}
When toInstrument method is called, application fails with NPE, trying to marketDataService.findMarketByCode(instrumentDto.getMarketCode()).
Hopefully, this information will be enough. Let me know if anything else is needed.
Thanks in advance!
Update:
MarketDataService class. It is added to the context through #Service annotation.
#Service
public class MarketDataService {
#Autowired
private InstrumentRepository instrumentRepository;
public Instrument findInstrumentByCode(String code) {
return instrumentRepository.findFirstByCode(code);
}
public List<InstrumentDto> getAllInstrumentDtos() {
List<Instrument> instruments = getAllInstruments();
List<InstrumentDto> dtos = Mappers.getMapper(InstrumentMapper.class).fromInstruments(instruments);
return dtos;
}
public void updateInstrument(InstrumentDto instrumentDto) {
Instrument instrument = findInstrumentByCode(instrumentDto.getCode());
if (instrument == null) {
throw new IllegalArgumentException("Market with given code not found!");
}
instrumentRepository.delete(instrument);
instrument = Mappers.getMapper(InstrumentMapper.class).toInstrument(instrumentDto);
instrumentRepository.save(instrument);
}
}
The algorithm is the following: #Controller class gets PUT request and calls MarketDataService.updateInstrument method with the body of the request (instrumentDto parameter). The latter one calls toInstrument method with the same parameter.
The reason why you have an NPE is because you are using the MapStruct Mappers factory for a non default component model.
The Mappers factory does not perform any dependency injections.
You have to inject your mapper in your MarketDataService. Be careful when doing that because you have a cyclic dependency.
In addition to that the patterns you are using in your Mapper are not really the right ones. You are using an expression when a simple source will do.
e.g.
#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = "spring")
public abstract class InstrumentMapper {
protected InstrumentRepository instrumentRepository;
#Mapping(target = "marketCode", source = "market.code")
public abstract InstrumentDto fromInstrument(Instrument instrument);
public abstract List<InstrumentDto> fromInstruments(List<Instrument> instruments);
#Mapping(target = "market", source = "marketCode")
public abstract Instrument toInstrument(InstrumentDto instrumentDto);
public abstract List<Instrument> toInstruments(List<InstrumentDto> instrumentDtos);
protected Instrument findInstrumentByCode(String code) {
return instrumentRepository.findFirstByCode(code);
}
#Autowired
public void setMarketDataService(MarketDataService marketDataService) {
this.marketDataService = marketDataService;
}
}
I want to mark a field of a class with my custom annotation. And whenever any method is invoke I want to do some modification on that field.
public class Message{
public Integer id;
#FreeText // this is my custom annotation
public String htmlMsg;
public String textMsg ;
}
This annotation (#FreeText) can be used in any class.
In seasar framework, I can do this by create an interceptor and override invoke method. The I can get the object of this class and the find the field that marked with my annotation and modify it. However, i cannot find a way to do it in Spring.
In spring, I found some method like MethodInvocationInterceptor, but I don't know how to implement it. Can you suggest any way to do this in Spring?
Seasar2 and Spring are very close. I have not tested but you can do something like this.
First create FreeText custom annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#Documented
public #interface FreeText {}
Then create the following interceptor
public class EncryptSensitiveDataInterceptor extends MethodInterceptor {
#Override
public Object invoke(MethodInvocation in) throws Throwable {
Object[] params = in.getArguments();
Object param = params[0];
for (Field field : param.getClass().getDeclaredFields()) {
for (Annotation anno : field.getDeclaredAnnotations()) {
if (anno instanceof FreeText) {
field.set(param, [YOUR CUSTOM LOGIC METHOD]);
}
}
}
return in.proceed();
}
Hope this help.
In Java is it possible to use a class annotation as a typed method parameter.
For example - this is your annotation
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface Entity {
}
then
#Entity
public class Car {
...
}
and then do
interface Persister {
void persist(Entity entity);
}
You can do
public #interface Entity {
String name();
}
public class Car implements Entity{
public String name(){ return "car"; }
}
but that's just odd. Entity should be an ordinary interface instead.
---
It is possible though that through annotation processing, we can require that an argument to a method must have a static type that contains certain annotation. Not sure if someone has done that.
You can do this but it won't do what you expect. This persist(Entity) method can only take your Entity annotation, not an instance of a class you want to use.
Instead what you can do is
interface Entity { }
interface Car extends Entity {
interface Persister {
void persist(Entity entity);
}
This will work as expected and you can pass an instance of a Car to the persist method.
I have a working "request factory" example and i want to refactor it, so that i can move the generic methods like "persist()" and "remove()" out of the domain object into a generic locator. Currently i have the following (working) code:
A generic super class that holds the id and the version for all domain objects:
#MappedSuperclass
public class EntityBase {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Version
#Column(name = "version")
private Integer version;
// setter & getter
}
A domain object. It has the persist() and remove()-methods, which i want to refactore out of the class:
#Entity
#Table(name = "article")
public class Article extends EntityBase{
public static Article findArticle(Long id) {
//find article
}
public void persist() {
// persist
}
public void remove() {
// remove
}
}
A proxy object for the domain object:
#ProxyFor(value = Article.class)
public interface ArticleProxy extends EntityProxy {
// some getter and setters
}
The request object for my domain object:
#Service(value = Article.class)
public interface ArticleRequest extends RequestContext {
Request<ArticleProxy> findArticle(Long id);
InstanceRequest<ArticleProxy, Void> persist();
InstanceRequest<ArticleProxy, Void> remove();
}
My request factory:
public interface MyRequestFactory extends RequestFactory {
ArticleRequest articleRequest();
}
---------------------------------------
Now my refactored code that is not working anymore:
I removed the persist() and remove()-method out of my domain object:
#Entity
#Table(name = "article")
public class Article extends EntityBase{
public static Article findArticle(Long id) {
//find article
}
}
I created my locator like this and added the methods "remove()" and "persist()" here (alongside the other default methods):
public class EntityLocator extends Locator<EntityBase, Long> {
#Override
public EntityBase create(Class<? extends EntityBase> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
#Override
public EntityBase find(Class<? extends EntityBase> clazz, Long id) {
return null;
}
#Override
public Class<EntityBase> getDomainType() {
return null;
}
#Override
public Long getId(EntityBase domainObject) {
return null;
}
#Override
public Class<Long> getIdType() {
return null;
}
#Override
public Object getVersion(EntityBase domainObject) {
return null;
}
public void persist(EntityBase domainObject){
// persist something
}
public void remove(EntityBase domainObject){
// remove
}
}
My proxy object is linked to the locator (locator=EntityLocator.class):
#ProxyFor(value = Article.class, locator=EntityLocator.class)
public interface ArticleProxy extends EntityProxy {
// getter and setters here
}
My new Request object looks like this. I made the "InstanceRequests" to "Requests", changed return types and parameter according to my new methods in the locator:
#Service(value = Article.class)
public interface ArticleRequest extends RequestContext {
Request<ArticleProxy> findArticle(Long id);
Request<Void> persist(ArticleProxy article);
Request<Void> remove(ArticleProxy article);
}
But now i get the error "Could not find domain method similar to java.lang.Void persist()" for the persist() and remove()-method. Why doesn't the lookup in the EntityLocator work? Do i need a ServiceLocator? I did not fully understand the google tutorial and the linked example is not available anymore.
I had the same question as you. The guide on GWTProject.org (http://www.gwtproject.org/doc/latest/DevGuideRequestFactory.html) is not very clear on how to correctly implement this, although it is written between the lines.
The following tutorial made the solution clear to me: http://cleancodematters.com/2011/06/04/tutorial-gwt-request-factory-part-i/
For me the use of the term DAO obfuscated things. I'm not going to use the DAO pattern. That's what my transparent persistence layer is for. However, the use of the Locator requires an extra class to put the persist, remove and findX methods in. They call it a Data Access Object (which it is, actually), I'd rather call it the Manager.
tl;dr
The methods you're trying to put in the Locator don't go there. You need an extra class (call it a DAO or a Manager).
Use the DAO/Manager as service in your RequestContext
I don't think you can place the persist and remove methods in the locator. The documentation doesn't suggest you can add arbitrary methods to the locator interface and reference them from the client. If you just want to avoid duplicating the persist and remove methods in every entity class then you can put them in your EntityBase class. I've done this and it works nicely.
If you also want to avoid repeating the functions in each of your request interfaces, you can make a generic base class Request like so:
#SkipInterfaceValidation
public interface BaseEntityRequest<P extends EntityProxy> extends RequestContext {
InstanceRequest<P, Void> persist();
InstanceRequest<P, Void> remove();
}
and use it like so:
public interface ArticleRequest extends BaseEntityRequest<ArticleRequest> {
...
}
Although it makes sense that persist() and remove() were in the Locator, so as the entity was completely agnostic about the persistence layer, this is not supported by current RF api. As consequence you have to deal with that adding those methods to your BaseEntity and figuring out a way to call the persist method in your locator.
I think you could open a gwt issue requiring this feature though.
Another way to avoid having certain methods in your entities, is to use ValueProxy insteadof EntityProxy, but in this case you have to provide methods to save/delete those objects from the client.
Your interface ArticleRequest isn't configured properly. You need correct it like this.
#Service(value = SentenceServiceImpl.class, locator = SpringServiceLocator.class)
public interface SentenceServiceRequest extends RequestContext {
Request<List<SentenceProxy>> getSentences();
Request<Void> saveOrUpdate(SentenceProxy sentence);
}
Locator:
public class SpringServiceLocator implements ServiceLocator {
public Object getInstance(Class<?> clazz) {
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestFactoryServlet.getThreadLocalServletContext());
return context.getBean(clazz);
}
}