Service classes

We have two service classes. These classes serve the controllers with data and implement the business logic, no matter how simple they are. One of the service class implementations calls REST-based services, while the other one reads data from properties files. The latter can be used to test the application offline. The one that calls REST services is used in the production environment. Both of them implement the ProductLookup interface:

package packt.java9.by.example.mybusiness.productinformation; 
import java.util.List;
public interface ProductLookup {
ProductInformation byId(String id);
List<String> byQuery(String query);
}

ResourceBasedProductLookup stores the whole database in a map called products. It is filled from the properties files when one of the service methods is invoked. The private method loadProducts is invoked from each of the service methods when they start, but it loads the data only if it is not loaded yet:

package packt.java9.by.example.mybusiness.productinformation.lookup; 
import...

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

The class is annotated using @Service. This annotation is practically equivalent to the @Component annotation. This is only an alternative name to the same annotation. Spring also handles the @Component annotation such that if an annotation interface is annotated using the @Component annotation, the annotation can also be used to signal that a class is a Spring component. You can write your own annotation interfaces if you want to signal for better readability that a class is not a simple component but some other special type.

For example, start up your IDE and navigate to the source code of the org.springframework.stereotype.Service interface:

    private ProductInformation 
fromProperties(Properties properties) {
final ProductInformation pi = new ProductInformation();
pi.setTitle(properties.getProperty("title"));
pi.setDescription(properties.getProperty("description"));
pi.setWeight(
Double.parseDouble(properties.getProperty("weight")));
pi.getSize()[0] =
Double.parseDouble(properties.getProperty("width"));
pi.getSize()[1] =
Double.parseDouble(properties.getProperty("height"));
pi.getSize()[2] =
Double.parseDouble(properties.getProperty("depth"));
return pi;
}

The fromProperties method creates an instance of ProductInformation and fills it from the parameters given in the Properties object. The Properties class is an old and widely used type. Although there are more modern formats and classes, this is still widely used and it is likely that you will meet this class. This is the very reason we use it here.

ProductInformation is a simple Data Transfer Object (DTO) that contains no logic, only fields, setters, and getters. It also contains a constant, emptyProductInformation, holding a reference to an instance of the class with empty values.

A Properties object is similar to a Map object. It contains String values assigned to String keys. There are methods, as we will see in our examples, that help the programmer to load a Properties object from a so-called properties file. Such a file usually has the .properties extension, and it contains key value pairs in the following format:

key=value

For example, the 123.properties file contains the following:

id=123 
title=Book Java 9 by Example
description=a new book to learn Java 9
weight=300
width=20
height=2
depth=18

The properties files are used to store simple configuration values and are almost exclusively used to contain language-specific constants. This is a very contorted use because properties files are ISO Latin-1 encoded files, and in case you need to use some special UTF-8 characters, you have to type them using the  uXXXX format or using the native2ascii converter program. You cannot save them simply as UTF-8. Nevertheless, this is the file format used for language-specific strings used for program internationalization (also abbreviated as i18n because there are 18 characters between the starting i and the last n).
To get the Properties object, we have to read the files in the project and get them packaged into a JAR file. The Spring class, PathMatchingResourcePatternResolver, helps us in doing so.

Gosh, yes, I know! We have to get used to these long names when we use Spring. Anyway, such long and descriptive names are widely used in an enterprise environment and they are needed to explain the functionality of the classes.

We declare a map that will contain all the products during the testing:

    final private Map<String, ProductInformation> 
products = new HashMap<>();

The key is the product ID, which is a string in our example. The values are the ProductInformation objects that we fill up using the fromProperties method:

    private boolean productsAreNotLoaded = true;

The next field signals that the products are not loaded:

Novice programmers usually use the opposite value with the name productsAreLoaded and set to false by default. In that case, the only place where we will read a value will negate the value, or the main branch of the if command becomes the do nothing part. Neither is a best practice.
    private void loadProducts() { 
if (productsAreNotLoaded) {
try {
Resource[] resources =
new PathMatchingResourcePatternResolver()
.getResources(
"classpath:products/*.properties");
for (Resource resource : resources) {
loadResource(resource);
}
}
productsAreNotLoaded = false;
} catch (IOException ex) {
log.error("Test resources can not be read",ex);
}
}
}

The getResources method returns all the resources (files) that are on the classpath under the products directory and that have a.properties extension:

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

The product ID is given by the name of the file. This is calculated using simple string manipulation, cutting off the extension. The Resource can also provide an input stream that the Properties class's load method can use to load all the properties at once from the file. Finally, we save the new ProductInformation object in the map.

We also have a special noProduct list that is empty. This is returned if there is no product for the query when we want to search for products:

    private static final List<String> noProducts = 
new LinkedList<>();

The product lookup service just takes a product from the Map and returns it, or if it does not exist, it returns an empty product:

@Override 
public ProductInformation byId(String id) {
loadProducts();
if (products.containsKey(id)) {
return products.get(id);
} else {
return ProductInformation.emptyProductInformation;
}
}

The query is a bit more complex. It implements searching for a product by title. Real-life implementations may implement a more complex logic, but this version is for local testing only; thus, the search by title is enough, perhaps even more complex than would be really necessary:

@Override 
public List<String> byQuery(String query) {
loadProducts();
List<String> pis = new LinkedList<>();
StringTokenizer st = new StringTokenizer(query, "&=");
while (st.hasMoreTokens()) {
final String key = st.nextToken();
if (st.hasMoreTokens()) {
final String value = st.nextToken();
log.debug("processing {}={} query", key, value);
if (!"title".equals(key)) {
return noProducts;
}
for (String id : products.keySet()) {
ProductInformation pi = products.get(id);
if (pi.getTitle().startsWith(value)) {
pis.add(id);
}
}
}
}
return pis;
}

The service class that implements the production functionality is much simpler. Strange, but many times the test code is more complex than the production code:

package packt.java9.by.example.mybusiness.productinformation.lookup; 

import ...

@Component
public class RestClientProductLookup implements ProductLookup {
private static Logger log = LoggerFactory.getLogger(RestClientProductLookup.class);

final private ProductInformationServiceUrlBuilder piSUBuilder;

public RestClientProductLookup(
ProductInformationServiceUrlBuilder piSUBuilder) {
this.piSUBuilder = piSUBuilder;
}

The constructor is used to inject the URL builder bean and this is all the auxiliary code the class has. The rest are the two service methods:

    @Override 
public ProductInformation byId(String id) {
Map<String, String> uriParameters = new HashMap<>();
uriParameters.put("id", id);
RestTemplate rest = new RestTemplate();
InventoryItemAmount amount = rest.getForObject(
piSUBuilder.url("inventory"),
InventoryItemAmount.class,
uriParameters);
if ( amount.getAmount() > 0) {
return rest.getForObject(piSUBuilder.url("pi"),
ProductInformation.class,
uriParameters);
} else {
return ProductInformation.emptyProductInformation;
}
}

The byId method first calls the inventory service to see if there are any products on the inventory. This REST service returns a JSON that has the format, { amount : nnn }; thus, we need a class (so simple that we do not list here) that has the int amount field, the setter, and the getter.

The Spring RestTemplate provides an easy way to access a REST service. All it needs is the URL template, a type that is used to convert the result, and a Map object with the parameters. The URL template string may contain parameters in the same way as the request mapping in the Spring controllers, the name of the parameter being between the { and } characters. The template class provides simple methods to access REST services. It automatically does marshaling, sending parameters, and un-marshaling, receiving the response. In the case of a GET request, the marshaling is not needed. The data is in the request URL, and the {xxx} placeholders are replaced with the values from the map supplied as a third argument. The un-marshaling is readily available for most of the formats. In our application, the REST service sends JSON data, and it is indicated in the response Content-Type HTTP header. RestTemplate converts the JSON to the type provided as argument. If ever the server decides to send the response in XML, and it will also be indicated in the HTTP header, RestTemplate will handle the situation automatically. As a matter of fact, looking at the code, we cannot tell how the response is encoded. This is also nice because it makes the client flexible and at the same time, we do not need to deal with such technical details. We can focus on the business logic.

At the same time, the class also provides configuration parameters in the case of marshaling or some other functionality so that it automatically needs that. You can, for example, provide marshaling methods, though I recommend that you use whatever is available by default. In most cases, when a developer thinks that there is a need for a special version of any of these functions, the original design of their code is flawed.

The business logic is very simple. We first ask the inventory if there is any product in stock. If there is (more than zero), then we query the product information service and return the details. If there is none, then we return an empty record.

The other service is even simpler. It simply calls the underpinning service and returns the result:

    @Override 
public List<String> byQuery(String query) {
Map<String, String> uriParameters = new HashMap<>();
uriParameters.put("query", query);
RestTemplate rest = new RestTemplate();
return rest.getForObject(
piSUBuilder.url("query"),
List.class,
uriParameters);
}
}
..................Content has been hidden....................

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