Getting annotations

We will extend the ProductInformation class with the following field:

private List<Class<? extends Annotation>> check;

Since this is a DTO, and Spring needs the setters and getters, we will also add a new getter and setter to it. This field will contain the list of classes that each class implement one of our annotations and also the built-in JDK interface, Annotation, because that is the way the Java compiler generates them. At this point, this may be a bit murky but I promise that the dawn will break and there will be light as we go on.

To get the product information, we have to look it up by ID. This is the interface and service that we developed in the last chapter, except, this time, we have another new field. This is, in fact, a significant difference although the ProductLookup interface did not change at all. In the last chapter, we developed two versions. One of the versions was reading the data from a properties file, the other one was connecting to a REST service.

Properties files are ugly and old technology but a must if ever you intend to pass a Java interview or work on enterprise applications developed at the start of the 21st century. I had to include it in the last chapter. It was my own urge to include it in the book. At the same time, while coding for this chapter, I did not have the stomach to keep using it. I also wanted to show you that the same content could be managed in a JSON format.

Now, we will extend the implementation of ResourceBasedProductLookup to read the product information from JSON formatted resource files. Most of the code remains the same in the class; therefore, we only list the difference here:

package packt.java9.by.example.mybusiness.bulkorder.services; 

import ...

@Service
public class ResourceBasedProductLookup implements ProductLookup {
private static final Logger log = LoggerFactory.getLogger(ResourceBasedProductLookup.class);

private ProductInformation fromJSON(InputStream jsonStream)
throws IOException {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(jsonStream,
ProductInformation.class);
}

...
private void loadProducts() {
if (productsAreNotLoaded) {
try {
Resource[] resources =
new PathMatchingResourcePatternResolver().
getResources("classpath:products/*.json");
for (Resource resource : resources) {
loadResource(resource);
}
productsAreNotLoaded = false;
} catch (IOException ex) {
log.error("Test resources can not be read", ex);
}
}
}

private void loadResource(Resource resource)
throws IOException {
final int dotPos =
resource.getFilename().lastIndexOf('.'),
final String id =
resource.getFilename().substring(0, dotPos);
final ProductInformation pi =
fromJSON(resource.getInputStream());
pi.setId(id);
products.put(id, pi);
}
...

In the project resources/products directory we have a few JSON files. One of them contains the desk lamp product information:

{ 
"id" : "124",
"title": "Desk Lamp",
"check": [
"packt.java9.by.example.mybusiness.bulkorder.checkers.PoweredDevice"
],
"description": "this is a lamp that stands on my desk",
"weight": "600",
"size": [ "300", "20", "2" ]
}

The type of product is specified in a JSON array. In this example, this array has only one element and that element is the fully qualified name of the annotation interface that represents the type of product. When the JSON marshaller converts the JSON to a Java object, it recognizes that the field that needs this information is a List, so it converts the array to a list and, also, the elements from String to Class objects representing the annotation interface.

Now that we have the resources loaded from JSON formatted resources and we saw how easy it is to read JSON data when using Spring, we can get back to the order consistency check. The Checker class implements the logic to collect the pluggable checkers and to invoke them. It also implements the annotation-based screening so as not to invoke the checkers we don't really need for the actual products in the actual order:

package packt.java9.by.example.mybusiness.bulkorder.services; 

import ...

@Component()
@RequestScope
public class Checker {
private static final Logger log =
LoggerFactory.getLogger(Checker.class);

private final Collection<ConsistencyChecker> checkers;
private final ProductInformationCollector piCollector;
private final ProductsCheckerCollector pcCollector;

public Checker(
@Autowired Collection<ConsistencyChecker> checkers,
@Autowired ProductInformationCollector piCollector,
@Autowired ProductsCheckerCollector pcCollector) {
this.checkers = checkers;
this.piCollector = piCollector;
this.pcCollector = pcCollector;
}

public boolean isConsistent(Order order) {
Map<OrderItem, ProductInformation> map =
piCollector.collectProductInformation(order);
if (map == null) {
return false;
}
Set<Class<? extends Annotation>> annotations =
pcCollector.getProductAnnotations(order);
for (ConsistencyChecker checker :
checkers) {
for (Annotation annotation :
checker.getClass().getAnnotations()) {
if (annotations.contains(
annotation.annotationType())) {
if (checker.isInconsistent(order)) {
return false;
}
break;
}
}
}
return true;
}
}

One of the interesting things to mention is that the Spring auto-wiring is very clever. We have a field with the Collection<ConsistencyChecker> type. Usually, auto-wiring works if there is exactly one class that has the same type as the resources to wire. In our case, we do not have any such candidate since this is a collection, but we have many ConsistencyChecker classes. All our checkers implement this interface and Spring recognizes it, instantiates them all, magically creates a collection of them, and injects the collection into this field.

Usually a good framework works logically. I was not aware of this feature of Spring, but I thought that this would be logical and, magically, it worked. If things are logical and just work, you do not need to read and remember the documentation. A bit of caution does not harm however. After I experienced that this functionality works this way, I looked it up in the documentation to see that this is really a guaranteed feature of Spring and not something that just happens to work but may change in future versions without notice. Using only guaranteed features is extremely important but is neglected many times in our industry.

When the isConsistent method is invoked, it first collects the product information into HashMap, assigning a ProductInformation instance to each OrderItem. This is done in a separate class. After this, ProductsCheckerCollector collects the ConsistencyChecker instances needed by one or more product items. When we have this set, we need to invoke only those checkers that are annotated with one of the annotations that are in this set. We do that in a loop.

In this code, we use reflection. We loop over the annotations that each checker has. To get the collection of annotations, we invoke checker.getClass().getAnnotations(). This invocation returns a collection of objects. Each object is an instance of some JDK runtime generated class that implements the interface we declared as an annotation in its own source file. There is no guarantee, though, that the dynamically created class implements only our @interface and not some other interfaces. Therefore, to get the actual annotation class, we have to invoke the annotationType method.

The ProductCheckerCollector and ProductInformationCollector classes are very simple, and we will discuss them later when we learn about streams. They will serve as a good example at that place, when we implement them using loops and, right after that, using streams.

Having them all, we can finally create our actual checker classes. The one that helps us see that there is a power cord ordered for our lamp is the following:

package packt.java9.by.example.mybusiness.bulkorder.checkers; 

import ...
@Component
@PoweredDevice
public class NeedPowercord implements ConsistencyChecker {
private static final Logger log =
LoggerFactory.getLogger(NeedPowercord.class);

@Override
public boolean isInconsistent(Order order) {
log.info("checking order {}", order);
CheckHelper helper = new CheckHelper(order);
return !helper.containsOneOf("126", "127", "128");
}
}

The helper class contains simple methods that will be needed by many of the checkers, for example:

public boolean containsOneOf(String... ids) { 
for (final OrderItem item : order.getItems()) {
for (final String id : ids) {
if (item.getProductId().equals(id)) {
return true;
}
}
}
return false;
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.70.247