Best way to support "Extendable enums" in annotations? - java

In my library there is a concept of "user levels". I have provided several default levels but for various reasons want to give the user the option of using their own levels.
Currently this is implemented as
public interface AdminLevel {
public void name();
}
public enum StandardAdminLevels implements AdminLevel {
ADMIN,
ANONYMOUS
}
The problem is that the user is usually going to be passing their required user level in an annotation. Things I have tried and failed:
Using AdminLevel as the type - Fails with "invalid type for annotation member"
Using String as the type but setting the value with StandardAdminLevels.ADMIN.name() - Fails with "attribute value must be constant"
Making StandardAdminLevels a final class that doesn't implement anything with public static final field's for each of the levels (essentially an enum) - Fails with "invalid type for annotation member"
Is there any other way I can't think of to have extendable enums in annotations? I'm trying to stick with enums due to their clarity and basic protection against invalid values, but the only other way I can think of is String constants. The problem I have is that would require verification at every single point user levels are used, potentially even in client code
Any ideas?

One idea: every possible AdminLevel is its own class. Pass the Class of that class to the annotation.
public final class Admin implements AdminLevel { ... }
public final class Anonymous implements AdminLevel { ... }
#Requires(Admin.class) public void protectedMethod() { ... }
#interface Requires {
Class<? extends AdminLevel> value();
}

Related

Java Spring Webflux: issue types MyInterface and org.springframework.data.repository.reactive.ReactiveCrudRepository<MyPojo,String> are incompatible

Small question regarding Java Spring Webflux and type incompatibility issues please.
I have a very simple Spring Webflux application, where I declare a common interface for all my repositories to save a pojo:
public interface MyInterface {
Mono<MyPojo> save(MyPojo myPojo);
}
Here are example of a concrete implementation, for instance, Redis:
#Repository("redis")
public class MyRedisRepository implements MyInterface {
private final ReactiveRedisOperations<String, String> reactiveRedisOperations;
public MyRedisRepository(ReactiveRedisOperations<String, String> reactiveRedisOperations) {
this.reactiveRedisOperations = reactiveRedisOperations;
}
#Override
public Mono<MyPojo> save(MyPojo myPojo) {
return reactiveRedisOperations.opsForValue().set("someKey", "someValue").map(__ -> myPojo);
}
}
And now an example with Elastic.
#Repository("elastic")
public interface MyElasticRepository extends ReactiveElasticsearchRepository<MyPojo, String>, MyInterface {
}
Please note, the important point is that some are regular classes (like Redis) which needs to implement the save method.
On the other hand, some are interface which implements Reactive___Repository<MyPojo, String> (which already have a save method)
When trying to compile, I am faced with this issue:
types question.MyInterface and org.springframework.data.repository.reactive.ReactiveCrudRepository<question.MyPojo,java.lang.String> are incompatible;
This is a bit strange to me, as my intention is just to have all the repositories under a common MyInterface , with a save method.
May I ask why I am facing this issue, and most of all, how to resolve this (keeping the MyInterface ) please?
Thank you
The return type and parameter type of the save method defined on the two interfaces are different, which makes them incompatible.
The ReactiveCrudRepository that ReactiveElasticsearchRepository extends, specifies that types derived from MyPojo can be passed to and will be returned from the save method.
In your custom interface you limit the passed argument and return type strictly to MyPojo. So the compiler recognizes there is no way to determine which save method is called at runtime and complains.
Try adjusting the return type of your interface to the following and adjusting your implementations:
public interface MyInterface<T extends MyPojo> {
Mono<T> save(T myPojo);
}

Dynamic index naming with class name

I'm creating a framework for spring-data-elasticsearch as a practice project.
My question is about the #Document tag that will create the index based on the name provided in indexName parameter of annotation.
However, I'm thinking is it possible to make it dynamic! In most of my usecases, the index name will match the class name. All my index classes will extend a abstract class which has generic implementation for all the and specific implementations needs to be done in the entity class.
This means, I have to maintain the #Document annotation for every entity. But since all the entities will extend a particular abstract class, is it possible to annotate the abstract class and somehow tell spring to use the class name as index name.
import org.springframework.data.elasticsearch.annotations.Document;
#Document(indexName = "BaseClassName OR something like Animal.getName" /*And other index properties of-course*/)
abstract class Animal {
String name;
public String getName() {
return name;
}
public abstract String makeSomeNoise();
}
All the concrete class that extends the Animals will be indexed in Elasticserch.
abstract class TwoLeggedAnimals extends Animal {}
abstract class FourLeggedAnimals extends Animal {}
The above two are just the grouping classes. For the sake of the example
class Duck extends TwoLeggedAnimals {
public Duck() {
this.name = Duck.class.getSimpleName();
}
#Override
public String makeSomeNoise() {
return "quack";
}
}
Class Duck extends TwoLeggedAnimals which in turn extends the "Animals" class and thus, Duck qualifies for index creation.
The same explanation for Horse class
class Horse extends FourLeggedAnimals {
Horse() {
this.name = Horse.class.getSimpleName();
}
#Override
public String makeSomeNoise() {
return "neigh";
}
}
You did not write what your specific problem or error is and what ES version you are using.
You can put the #Document annotation with the index name on an abstract baseclass and then use a derived class to store your entites into the index without adding some annotation on your derived class; this works with no problems.
But you cannot store different types (like TwoLeggedAnimals and FourLeggedAnimals) in the same index since Elasticsearch 6.0 (see ES 6.0 breaking changes). Your program will work as long as you are using one type, as soon as you try to store the second type, you will get
Elasticsearch exception [type=illegal_argument_exception, reason=Rejecting mapping update to [animals] as the final mapping would have more than 1 type: [twoleggedanimal, fourleggedanimal]]
The last 5.x version 5.6 had support until 2019-03-11 (Elastic end of life dates), so that's not supported anymore.
So, as it is not possible to store more than one type in an index, you will have to rethink your classes and how you store them - please check ES removal of types as well, if the alternatives outlined there might help you.

How to force Spring Data to create query methods with entity runtime type?

I've got around 5 objects that I want to do similar things with.
I figured out that not to polute the code I will put a logic for those objects in one place.
public class MetaObjectController<T extends MetaObject> {
#Autowired
private final MetaObjectRepository<T> repository;
// generic logic
Here's how repository looks:
public interface MetaObjectRepository<T extends MetaObject> extends GraphRepository<T> {
T findByName(String name);
}
Now, I create concrete class which uses delegation:
public class ExperimentalController {
#Autowired
private final MetaObjectController<MetaCategory> metaController;
#RequestMapping(method = RequestMethod.POST)
public void add(#RequestBody MetaCategory toAdd) {
metaController.add(toAdd);
}
Now, when I look at the generated queries I see, that although instantiated correctly, repository puts MetaObject as an entity name instead of runtime type.
Is there a way to force the repository to use runtime type?
Please don't advise to put a #Query annnotation. That's not what I am looking for.
This is most probably due to type erasure: at runtime there is only the type constraint available which is MetaObject. If you want to use (via spring-data) the actually relevant subclass you will have to create explicit interfaces of the MetaObjectRepository like this:
public class Transmogrifier extends MetaObject
public interface MetaTransmogrifierRepository
extends MetaObjectRepository<Transmogrifier> {}

Bounded Type Parameters bounded by an annotation

In Java, it is possible to make a bounder type parameter have to extend from a particular class or interface e.g.
public class Box<T extends MyClass> {
T t
...
}
Is there anyway I can bound by an annotation so that values of T can only be classes that have a particular annotation?
Starting in Java 8, you can write
public class Box<T extends #MyAnno MyClass> {
...
}
As with any Java annotation, to enforce the semantics, you need to use an annotation processor. The Checker Framework is one annotation-processing tool that enforces the semantics for you: you can configure it to issue an error if you ever try to instantiate the Box type using a type argument that lacks the #MyAnno annotation.
Unfortunately there is no way to express it in java AFAIK. <T annotatedWith MyAnnotation> would be so convenient in some cases, but it would add a new keyword and honestly generics are difficult enough ;)
Otherwise for an annotation, as #duckstep said it is easy to check at runtime with
t.getClass().isAnnotationPresent(annotationClass)
For an annotation processor, though, the APIs are much more tough to deal with. Here is some code if it can help some people :
private boolean isAnnotationPresent(TypeElement annotationTypeElement, String annotationName) {
for (AnnotationMirror annotationOfAnnotationTypeMirror : annotationTypeElement.getAnnotationMirrors()) {
TypeElement annotationOfAnnotationTypeElement = (TypeElement) annotationOfAnnotationTypeMirror.getAnnotationType().asElement();
if (isSameType(annotationOfAnnotationTypeElement, annotationName)) {
return true;
}
}
return false;
}
private boolean isSameType(TypeElement annotationTypeElement, String annotationTypeName) {
return typeUtils.isSameType(annotationTypeElement.asType(), elementUtils.getTypeElement(annotationTypeName).asType());
}

Factory and generics

I have the following classes:
public interface IDataSource<T> {
public List<T> getData(int numberOfEntries);
}
public class MyDataSource implements IDataSource<MyData> {
public List<MyData> getData(int numberOfEntries) {
...
}
}
public class MyOtherDataSource implements IDataSource<MyOtherData> {
public List<MyOtherData> getData(int numberOfEntries) {
...
}
}
I would like to use a factory that return the correct implementation based on the data type. I wrote the following but I get "Unchecked cast" warnings:
public static <T> IDataSource<T> getDataSource(Class<T> dataType) {
if (dataType.equals(MyData.class)) {
return (IDataSource<T>) new MyDataSource();
} else if (dataType.equals(MyOtherData.class)) {
return (IDataSource<T>) new MyOtherDataSource();
}
return null;
}
Am I doing it wrong? What can I do to get rid of the warnings?
I am not aware of any way to get rid of those warnings without #SuppressWarnings("unchecked").
You are passing in a Class object so T can be captured. But you are forced to check the Class at runtime to determine which IDataSource<T> to return. At this time, type erasure has long since occurred.
At compile time, Java can't be sure of type safety. It can't guarantee that the T in the Class at runtime would be the same T in the IDataSource<T> returned, so it produces the warning.
This looks like one of those times when you're forced to annotate the method with #SuppressWarnings("unchecked") to remove the warning. That warning is there for a reason, so it is up to you to provide and ensure type safety. As written, it looks like you have provided type safety.
#SuppressWarnings("unchecked")
public static <T> IDataSource<T> getDataSource(Class<T> dataType) {
You're doing it right, and you should simply suppress the warnings. Factories are one of the tricky areas in generics where you really do need to manually cast to a generic type, and you have to ensure via whatever means that the returned value matches the Class<T> you pass in. For example, in this case you're hard-coding a couple of IDataSource implementations, so I would recommend writing unit tests that verify that the types are correct so that if the MyData implementation changes in an incompatible way, you'll get an error on build.
Just annotate the getDataSource method with #SuppressWarnings("unchecked"), and it's always a good idea to add an explanatory comment when suppressing warnings.
Generics are for compile-time type safety. They can't be used for runtime type determination like that. To get rid of the warning, you can do something like #SuppressWarnings("unchecked") or use the -Xlint:-unchecked compiler flag, as described in the "Raw Types" part of the Java tutorial.
The other answers have answered the problem as you posed it. But I'd like to take a step back to understand what you're trying to accomplish with this factory method. This factory basically provides a map of data types to IDataSource parameters. Dependency injection might be a more appropriate pattern since this is a small well-known set of data types and implementations (as indicated by your example).
Let's say you want to store all Widgets in Mongo but all Gadgets in Mysql, you might have two classes: a MongoWidgetDataSource that implements IDataSource<Widget> and a MysqlGadgetDataSource that implements IDataSource<Gadget>.
Instead of hardcoding a factory method call like MyFactory.getDataSource(Widget.class) inside a data consumer, I would inject the appropriate IDataSource dependency. We might have MyService that does something with widgets (stored in mongo). Using a factory as you proposed would look like this:
public class MyService {
public void doSomething() {
String value = MyFactory.getDataSource(Widget.class).getSomething();
// do something with data returned from the source
}
}
Instead, you should inject the appropriate data source as a constructor arg into the service:
public class MyService {
private final IDataSource<Widget> widgetDataSource;
public MyService(IDataSource<Widget> widgetDataSource) {
this.widgetDataSource = widgetDataSource;
}
public void doSomething() {
String value = widgetDataSource.getSomething();
// now do something with data returned from the source
}
}
This has the added benefit of making your code more reusable and easier to unit test (mock dependencies).
Then, where you instantiate MyService, you can also wire up your data sources. Many projects use a dependency injection framework (like Guice) to make this easier, but its not a strict requirement. Personally, though, I never work on a project of any real size or duration without one.
If you don't use an DI framework, you just instantiate the dependencies when you create the calling service:
public static void main(String[] args) {
IDataSource<Widget> widgetDataSource = new MongoWidgetDataSource();
IDataSource<Gadget> gadgetDataSource = new MysqlGadgetDataSource();
MyService service = new MyService(widgetDataSource, gadgetDataSource);
service.doSomething();
}
In Guice, you would wire up these data sources like this:
public class DataSourceModule extends AbstractModule {
#Override
protected void configure() {
bind(new TypeLiteral<IDataSource<Widget>>() {}).to(MongoWidgetDataSource.class);
bind(new TypeLiteral<IDataSource<Gadget>>() {}).to(MysqlGadgetDataSource.class);
}
}
Dependency inversion is a bit of a different way to think about the problem, but it can lead to a much more decoupled, reusable and testable code base.
This seems to work:
public static <T> IDataSource<T> getDataSource(MyData dataType) {
System.out.println("Make MyDataSource");
return (IDataSource<T>) new MyDataSource();
}
public static <T> IDataSource<T> getDataSource(MyOtherData dataType) {
System.out.println("Make MyOtherDataSource");
return (IDataSource<T>) new MyOtherDataSource();
}
public void test() {
IDataSource<MyData> myDataSource = getDataSource((MyData) null);
IDataSource<MyOtherData> myOtherDataSource = getDataSource((MyOtherData) null);
}
You may prefer to create empty archetypes rather than cast null like I have but I think this is a viable technique.

Categories

Resources