How should I design this program?
How to pass objects and notify the threads? We are keeping the cache objects in HashMap and the CacheService thread needs to pass the key to the MDLService. So which pattern should I use?
Seems to me that you have 1 thread too many. The XML reading thread and the MDL writing make sense, but a thread to just put things into an in-memory cache seems to be too complicated. If the MDL generator needs to use the Guava cache then it should "own" the cache and stick things into it.
That leaves you with 1 input SAX processor thread and one output MDL generator thread. Good. To connect the two I would use a BlockingQueue
like the LinkedBlockingQueue
. You may or may not want to set a size limit on the queue depending on whether or not the reading is faster than the writing and how many records in your job.
So you main thread will create the BlockingQueue
and then pass it to both the input and output threads. The SAX input thread calls put()
on the queue and the MDL output thread calls take()
puts the object into the Guava cache and then does the MDL generation.
Hope this helps.
Since you are using Guava Cache, you can use Guava AsyncEventBus for disopatching messages between the tasks and do away with the three separate dedicated ExecutorServices.
Here is an example implementation for the above described case. Please note that the implementation could have been possible even without Guava cache, as mentioned by some of the others who have replied; nevertheless I presume there may have been a valid reason for Nirmalaya to ask for it. One such reason that I could think about is spilling over of the cache to storage devices or databases, to save upon the runtime memory.
employee-records.xml
<?xml version="1.0" encoding="UTF-8"?>
<Employees>
<Employee id="1">
<name>Thomas</name>
</Employee>
<Employee id="2">
<name>Lisa</name>
</Employee>
<Employee id="3">
<name>Ronald</name>
</Employee>
<Employee id="4">
<name>Erica</name>
</Employee>
</Employees>
Employee.java
package com.technoroy.examples.guava;
/**
* A value holder POJO implementation for Employee records
* @author Rahul R
*
*/
class Employee {
private Integer id = null;
private String name = null;
public Employee() {
super();
}
public Employee(Integer id, String name) {
super();
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + "]";
}
}
GuavaCacheProcessor.java
package com.technoroy.examples.guava;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
/**
* The primary executable class
*
* @author Rahul R
*
*/
public class GuavaCacheProcessor {
private final static BlockingQueue<Integer> notificationQueue = new LinkedBlockingQueue<>();
public static void main(String... arguments) {
Runnable xmlProcessor = new Runnable() {
public void run() {
parseDataFile();
}
};
Runnable mdlGenerator = new Runnable() {
public void run() {
try {
while (true) {
Integer id = notificationQueue.take();
Employee record = ApplicationCacheUtil.getRecord(id);
generateContent(record);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
};
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(xmlProcessor);
executorService.submit(mdlGenerator);
}
public static void generateContent(Employee employee) {
System.out.println(employee);
}
public static void parseDataFile() {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
InputStream dataInputStream = GuavaCacheProcessor.class.getResourceAsStream("employee-records.xml");
try {
SAXParser saxParser = saxParserFactory.newSAXParser();
saxParser.parse(dataInputStream, new DefaultHandler() {
private Employee employee = null;
private StringBuilder elementValue = null;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if (qName.equalsIgnoreCase("Employee")) {
employee = new Employee();
String id = attributes.getValue("id");
if (id.matches("-?\\d+(\\.\\d+)?")) {
employee.setId(Integer.valueOf(id));
}
}
elementValue = new StringBuilder();
}
@Override
public void characters(char ch[], int start, int length) throws SAXException {
if (elementValue != null) {
elementValue.append(new String(ch, start, length));
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equalsIgnoreCase("name")) {
if (employee != null && elementValue != null) {
employee.setName(elementValue.toString());
}
} else if (qName.equalsIgnoreCase("Employee")) {
ApplicationCacheUtil.putRecord(employee.getId(), employee);
try {
notificationQueue.put(employee.getId());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
elementValue = null;
}
});
} catch (ParserConfigurationException | SAXException | IOException e) {
e.printStackTrace();
}
}
}
/**
* The Cache utilities class, that initializes and returns a handle to the
* cache.
*
* @author Rahul R
*
*/
class ApplicationCacheUtil {
private static Cache<Integer, Employee> cache = CacheBuilder.newBuilder().build();
public static Cache<Integer, Employee> getCache() {
return cache;
}
public static void putRecord(Integer key, Employee value) {
cache.put(key, value);
}
public static Employee getRecord(Integer key) {
return cache.getIfPresent(key);
}
}