Northwind Tutorial
From RifidiWiki
This document provides step-by-step instructions on how to get started on developing your first application that runs on the Rifidi Edge Server. The application we will develop will use Esper to collect tag reads from a reader and put them on a JMS queue to be consumed by a client application. Many of the steps are applicable to many kinds of plugins for the edge server, including creating a sensor plugin.
Contents
- 1 The Scenario: Northwind Shipping
- 2 Prerequisites
- 3 Source
- 4 Outline
- 5 Writing A Web Application
The Scenario: Northwind Shipping
Congratulations! You are the proud new founder of Northwind Shipping Inc. -- "delivering packages faster than a caffeinated lightning bug"™. One of your core business strategies is to out perform your competitor -- Pony Express Shipping Inc. -- by capitalizing on increased efficiencies gained by your innovative use of technology. You have heard all the hype about RFID and want to employ in it your new, state-of-the-art distribution center. You have decided to use the Rifidi Edge Server to run the RFID applications you will need in your distribution center. This tutorial is a step-by-step guide on how to develop an RFID application and web UI using the Rifidi Edge Server API and deploy the application on the Rifidi Edge Server.
The Northwind distribution center has many complex processes, which should be automated as much as possible. Because you are new to RFID (and because too many processes would overwhelm this tutorial ;-) ) you have decided to start small and only implement a basic process with a few business rules. The first process you will automate using RFID is the receiving process (DC-Inbound). You have a dock door which will be receiving incoming shipments from trucks. The packages must be checked in at the dock door so that your ERP system knows the packages have arrived. Once this happens, the packages must be moved to a separate staging area where it needs to be weighed and prepared for further routing.
There are several goals for the application
- Track the packages from the dock door (stage 1) to the weigh station (stage 2).
- Send an alert if a package moves backwards (from stage 2 to stage 1).
- Send an alert if a package arrives at the weigh station but was not seen at the dock door
- Send an alert if a package departs the dock door and is not seen at the weigh station within five minutes
The Architecture
The solution that we will build will consist of two parts: the application bundle which will implement all the business rules, and the web application which will display the items. The two pieces will communicate using JMS.
Prerequisites
For this tutorial, we will use Eclipse to develop the application. While it would be possible to develop the application in any IDE that you are familiar with, Eclipse provides great tooling around OSGi application development and deployment, and thus makes this process much easier. If this is your first time developing with eclipse, there will be a learning curve. However, the payoff is worth it.
To get started see Setting up a Development Environment.
Source
You can download the source for this project here: File:Northwind 1.0.0.zip. Just unzip the project, open up eclipse and select import->existing projects into workspace.
Outline
- Step 1: Create the Application
- Step 2: Using Emulator
- Step 3: Esper: Track Packages
- Step 4: Esper: Package Moved Backwards
- Step 5: Esper: Package Skipped Dock Door
Alert: Package is Lost!
The Code
Add the following to the setUpAlerts method
// 1 Create a new alert whenever a package departs from the dock door and is not seen at the weighstation with a given time period statements.add(esperService.getProvider().getEPAdministrator().createEPL( "on pattern[every-distinct(tag.tag_ID) tag=dockdoor_recent -> " + "(timer:interval(5 min) and not weighstation(tag_ID = tag.tag_ID))] " + "insert into alerts " + "select 3 as alert_type, tag_ID as tag_ID " + "from dockdoor_recent where (tag_ID = tag.tag_ID)"));
In addition, you need to modify the switch statement of the alerts handler:
switch (alertType) { case 1: System.out.println("Package moved backwards: " + id ); break; case 2: System.out.println("Package skipped the dock door: " + id); break; case 3: System.out.println("Package is lost: " + id); break; }
Explanation of the Code
- The only statement we have to add to implement the required functionality is, like the 'Package cannot move backwards' alert, implemented in two parts. The first part (on pattern) looks for a sequence to happen. The second part (insert into) does something whenever the first part happens.
- We can use the followed-by operator to specify that that we should look for an event leaving a dockdoor followed by a specific time period (15 seconds in our case) and no package with a corresponding id at the weigh station.
- When this pattern triggers, we need to insert a new event into the alerts window.
- For this kind of alert, we will assume the type will be 3.
Sending out events with JMS
What you will learn:
- Importing the JMS services provided by Rifidi Edge Server
- Exporting packages from a bundle
- Sending out events over JMS
Once you have the esper queries defined, debugged, and working, it's time to figure out exactly how you want to react to the various events. You might want to log the events to a database. You may want to expose the events to a .Net application via web services. Or you might want to integrate with an ERP system such as SAP. For the Northwind application, we want to send out notifications over Java Messaging Service (JMS) for a web application (which we will build soon) to display.
Create Notification Classes
Creating an API Bundle |
For this example, we are creating the notification classes in the same bundle that our application is in in order to simplify the tutorial. This means that our web application will have to have a direct dependency on the application bundle, so that the web bundle has access to the notification classes. A cleaner solution would be to extract out the notification classes into a separate bundle so that both the application bundle and the web bundle would depend on the API bundle. |
The first in using JMS to alert another component about the RFID events that we are capturing in our Esper listeners is to create a few Java classes that define the kind of events we want to send out. In particular we will need:
- ItemArrivalNotification that is sent out when an item has arrived at a zone (either dock door or weigh station).
- ItemDepartureNotification that is sent out when an item has departed from a zone.
- AlertNotification that is sent when one of the three exception cases happens.
In addition, we will define two enums:
- EZone that defines the two zones we have in our system.
- EAlertType that defines the three alert types that we have.
There are three steps:
- Create a new package. Right-click on the com.northwind.rfid.shipping package and select new->package. Name the package com.northwind.rfid.shipping.notifications.
- Create the classes (see below)
- Export the com.northwind.rfid.shipping.notifications package in the Manifest. This allows other OSGi bundles to see this package. To do this, open up the Manifest.MF file. Click on the Runtime tab. In the 'Exported Packages' section, click Add. Choose the com.northwind.rfid.shipping.notifications package. Save.
Notification Classes
ItemArrivalNotification
package com.northwind.rfid.shipping.notifications; import java.io.Serializable; /** * This class defines an Item Arrival Event * @author Kyle Neumeier - kyle@pramari.com * */ public class ItemArrivalNotification implements Serializable{ /** Default Serial ID */ private static final long serialVersionUID = 1L; /** The zone that this tag arrived at*/ private final EZone zone; /** The tag that arrived */ private final String tag_Id; /** * @param zone * The zone that this tag arrived at * @param tag_id * The ID of the tag that arrived */ public ItemArrivalNotification(EZone zone, String tag_id) { this.zone = zone; this.tag_Id = tag_id; } /** * @return the zone */ public EZone getZone() { return zone; } /** * @return the tag_id */ public String getTag_Id() { return tag_Id; } }
ItemDepartureNotification
package com.northwind.rfid.shipping.notifications; import java.io.Serializable; /** * This class defines an Item Departure Event * * @author Kyle Neuemeier - kyle@pramari.com * */ public class ItemDepartureNotification implements Serializable { /** Default Serial ID */ private static final long serialVersionUID = 1L; /** The zone that this tag departed from */ private final EZone zone; /** The tag that departed */ private final String tag_Id; /** * @param zone * The zone that this tag departed from * @param tag_Id * The ID of the tag that departed */ public ItemDepartureNotification(EZone zone, String tag_Id) { this.zone = zone; this.tag_Id = tag_Id; } /** * @return the zone */ public EZone getZone() { return zone; } /** * @return the tag_id */ public String getTag_Id() { return tag_Id; } }
AlertNotification
package com.northwind.rfid.shipping.notifications; import java.io.Serializable; /** * This class defines an Alert Event. * * @author Kyle Neumeier - kyle@pramari.com * */ public class AlertNotification implements Serializable { /** Default Serial ID */ private static final long serialVersionUID = 1L; /** The alert message */ private final EAlertType alert; /** The tag that the alert applies to */ private final String tagID; /** * @param alert * @param tagID */ public AlertNotification(EAlertType alert, String tagID) { super(); this.alert = alert; this.tagID = tagID; } /** * @return the alert */ public EAlertType getAlert() { return alert; } /** * @return the tagID */ public String getTag_Id() { return tagID; } /** * @return A message that describes this alert type */ public String getMessage() { switch (alert) { case Package_is_Lost: return "Package is lost"; case Package_Moved_Backwards: return "Package moved backwards"; case Package_Skipped_Dock_Door: return "Package skipped dock door"; default: return "Unknown alert"; } } }
EZone
package com.northwind.rfid.shipping.notifications; /** * This enum lists the read zones for our system. * @author Kyle Neumeier - kyle@pramari.com * */ public enum EZone { DOCK_DOOR, WEIGH_STATION; }
EAlertType
package com.northwind.rfid.shipping.notifications; /** * This enum lists the kinds of alerts that are defined. * @author Kyle Neumeier - kyle@pramari.com * */ public enum EAlertType { Package_Moved_Backwards, Package_Skipped_Dock_Door, Package_is_Lost; }
Export Notification Package
In order for another bundle to be able to use the Notification classes we created, we need to export the notification package.
- Open the Manifest.MF.
- Select the Runtime Tab.
- In the Exported Packages section, click add.
- Choose the com.northwind.rfid.shipping.notifications package.
Use JMS to Send out Notifications in Application
In this step we will modify our code to use the Notification objects to send out notifications over JMS.
Add JMS Dependencies
The first step you need to do is to add the the necessary package dependencies to your bundle. Open up the Manifest.MF, select the 'Dependencies' tab. On the 'Imported Packages' section click add choose the following packages.
- javax.jms
- org.apache.activemq.command
- org.springframework.beans.factory
- org.springframework.core
- org.springframework.jms
- org.springframework.jms.core
Add JMS Setters
The next step is to modify ShippingApp to have a setter for the JMSTemplate and Destination objects that spring can use to inject them.
/** Esper service */ private volatile EsperManagementService esperService; /**All statements that have been defined so far*/ private final Set<EPStatement> statements = new CopyOnWriteArraySet<EPStatement>(); /**JMS Destination to send messages to*/ private volatile Destination destination; /**JMS Template to use to send messages*/ private volatile JmsTemplate template; /** * Constructor */ public ShippingApp() { Activator.myApp=this; } /** * Called by spring * * @param esperService */ public void setEsperService(EsperManagementService esperService) { this.esperService = esperService; start(); setUpAlerts(); setupListeners(); } /** * Called by spring * @param destination the destination to set */ public void setDestination(Destination destination) { this.destination = destination; } /** * Called by spring * @param template the template to set */ public void setTemplate(JmsTemplate template) { this.template = template; }
Modify spring.xml to inject the JMS objects
Open up the spring.xml file. There are a few things to take care of here.
- Lookup the JMS template from the OSGi service registry. You will use this object to send out notifications
- Create a new JMS Destination (specifically a Topic desitination). This is where the notifications will be sent.
- Inject the template and destination into the application.
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi" xmlns:amq="http://activemq.apache.org/schema/core" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd"> <!-- Create the Application bean and inject dependencies--> <bean id="rfidapp" class="com.northwind.rfid.shipping.ShippingApp"> <property name="esperService" ref="esperManagementService" /> <property name="destination" ref="NorthwindTopic" /> <property name="template" ref="jmsTemplate" /> </bean> <!--Get a reference to the Esper Management Service from the OSGi Service Registry--> <osgi:reference id="esperManagementService" interface="org.rifidi.edge.core.services.esper.EsperManagementService" /> <!-- Create a new Topic to publish Notifications to --> <bean id="NorthwindTopic" class="org.apache.activemq.command.ActiveMQTopic"> <property name="physicalName" value="com.northwind.rfid.shipping.topic" /> </bean> <!-- Lookup the JMS Template from OSGi Service Registry --> <osgi:reference id="jmsTemplate" interface="org.springframework.jms.core.JmsTemplate" bean-name="externalJMSTemplate" /> </beans>
Add a send method
We can encapsulate the logic required to send out a JMS message in one method. Add this method to the application class
/** * Method to use to send out notifications over JMS. It seriallizes the * message into an array of bytes, then sends those bytes out over JMS * * @param notification */ private void send(final Serializable notification) { if (template == null || destination == null) { // TODO: Log error message; return; } try { template.send(destination, new MessageCreator() { @Override public Message createMessage(Session arg0) throws JMSException { BytesMessage message = arg0.createBytesMessage(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { ObjectOutput out = new ObjectOutputStream(bos); out.writeObject(notification); out.close(); message.writeBytes(bos.toByteArray()); return message; } catch (IOException e) { throw new JMSException(e.getMessage()); } } }); } catch (JmsException exception) { // TODO: log error message return; } }
Finally, let's send some messages!
Edit the dockDoorListener as follows:
//create a listener to handle Dock Door events StatementAwareUpdateListener dockDoorListener = new StatementAwareUpdateListener() { @Override public void update(EventBean[] arrivals, EventBean[] departures, EPStatement arg2, EPServiceProvider arg3) { if(arrivals!=null){ for(EventBean bean : arrivals){ String id = (String)bean.get("tag_ID"); send(new ItemArrivalNotification(EZone.DOCK_DOOR, id)); } } if(departures!=null){ for(EventBean bean : departures){ String id = (String)bean.get("tag_ID"); send(new ItemDepartureNotification(EZone.DOCK_DOOR, id)); } } } };
Similarly edit the weightStationListener:
StatementAwareUpdateListener weighStationListener = new StatementAwareUpdateListener() { @Override public void update(EventBean[] arrivals, EventBean[] departures, EPStatement arg2, EPServiceProvider arg3) { if(arrivals!=null){ for(EventBean bean : arrivals){ String id = (String)bean.get("tag_ID"); send(new ItemArrivalNotification(EZone.WEIGH_STATION, id)); } } if(departures!=null){ for(EventBean bean : departures){ String id = (String)bean.get("tag_ID"); send(new ItemDepartureNotification(EZone.WEIGH_STATION, id)); } } } };
Finally edit the Alert Listener:
StatementAwareUpdateListener alertListener = new StatementAwareUpdateListener() { @Override public void update(EventBean[] arrivals, EventBean[] departures, EPStatement arg2, EPServiceProvider arg3) { if (arrivals != null) { for (EventBean bean : arrivals) { int alertType = (Integer) bean.get("alert_type"); String id = (String) bean.get("tag_ID"); AlertNotification alert = null; switch (alertType) { case 1: alert = new AlertNotification(EAlertType.Package_Moved_Backwards, id); break; case 2: alert = new AlertNotification(EAlertType.Package_Skipped_Dock_Door, id); break; case 3: alert = new AlertNotification(EAlertType.Package_is_Lost, id); break; } if (alert != null) { send(alert); } } } } };
Writing A Web Application
Set up Project
What you will learn
- How to set up a web application project
Create the project
Just like when we created a new project for the Northwind application, we need to create a new project for the web application.
- Go to File-> New -> Project. Select "Plug-in Project" from the file chooser. Click "Next."
- Assign the project a name. For web applications, the application needs to end in 'war'. I chose com.northwind.rfid.shipping.war. Click next.
- Assign the plugin an ID, which is used to identify the bundle in the OSGi environment. Give it a descriptive name as well as the the name of the entity (company, person, project) that will maintain the bundle. For this bundle, we don't need the Activator, so uncheck that box.
Add the necessary files and folders
- Download and unzip this file: File:Tutorial-war-files.zip. It's a zip file that contains two files you will need.
- In the main project, make a new folder called 'WEB-INF'
- In the WEB-INF folder, make a new folder called 'jsp'
- In the WEB-INF folder, make a new file called 'web.xml'
- Put the file 'spring.tld' in the WEB-INF folder.
- In the META-INF folder, make a new folder called 'xsd'
- Put the file 'spring-beans-2.5.xsd' in the xsd folder.
- In the root folder, create a file called 'index.jsp'
Your project should now look like this:
Configure the Manifest
Open up the Manifest.MF file. Click on the Dependencies tab.
- On the Required Plug-ins Section, add the following bundles
- com.northwind.rfid.shipping
- com.springsource.org.apache.taglibs.standard
- On the Imported Packages section, add the following packages
- javax.jms
- javax.servlet
- javax.servlet.http
- javax.servlet.jsp
- javax.servlet.jsp.jstl.core
- org.apache.activemq.command
- org.apache.activemq.spring
- org.springframework.jms.core
- org.springframework.jms.listener
- org.springframework.web.servlet
- org.springframework.web.servlet.mvc
- org.springframework.web.servlet.view
- Click the Manifest.MF tab. Add the following line to the end. Make sure there is an blank line as the last line of the Manifest.MF (and make sure there is no leading whitespace for this line).
Web-ContextPath: NORTHWIND-DEMO
Modify the web.xml
Open up the Web.xml file. Modify it to look like this:
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <jsp-config> <taglib> <taglib-uri>/spring</taglib-uri> <taglib-location>/WEB-INF/spring.tld</taglib-location> </taglib> </jsp-config> </web-app>
When building web applications in java, the web.xml file provides configuration and deployment information for the Web components. Initially, we supply two pieces of information:
- The welcome file is page that is loaded by default when you go to 127.0.0.1:8080/NORTHWIND-DEMO
- The jsp-config provides the location of some jsp tags provided by spring.
Add a include.jsp
Make a new file called include.jsp inside of the jsp folder. Modify it to look like this:
<%@ page session="false"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
This file simply includes some headers that we want on top of every JSP we will make. Therefore, in each JSP, we only have to have one include instead of several.
Modify index.jsp
Open up index.jsp. Modify it to look like this:
<%@ include file="/WEB-INF/jsp/include.jsp" %> <jsp:useBean id="now" class="java.util.Date" /> Hello World at <fmt:formatDate value="${now}" pattern="MM.dd.yyyy" />
Here, we use some JSP tags to print out the date.
Run the web application
- Open up the run configuration and select the com.nortwind.rfid.shipping.war plugin. Run
- Point your browser to http://127.0.0.1:8080/NORTHWIND-DEMO/
If everything worked, you should see 'hello world' and the date.
Write a Hello World Servlet
What you will learn
- How to use Spring's MVC Controller
- How to write a servlet
- How to write a JSP
Write a JSP
Create a new file in the jsp directory called taglocation.jsp. Edit it as follows:
<%@ include file="/WEB-INF/jsp/include.jsp" %> Hello World at <fmt:formatDate value="${model.date}" pattern="MM.dd.yyyy" />
As you can see, this does the same thing as the index page, except it will get the date passed in from the Contoller in the "model" instead of getting it from a JSP tag.
Write a Controller
The next thing to do is to create the controller. The controller's purpose is to pass in a model to the view. In this case, our model is simply a date object. However, this will change when we hook up our web app to our RFID application. For now, create a new package called <tt?>com.northwind.rfid.shipping.war</tt>. Create a new Java class called TagLocationController. Edit it as follows:
package com.northwind.rfid.shipping.war; import java.util.Date; import java.util.HashMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; /** * @author Kyle Neumeier - kyle@pramari.com * */ public class TagLocationController implements Controller{ @Override public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception { HashMap<String, Object> model = new HashMap<String, Object>(); model.put("date", new Date(System.currentTimeMillis())); return new ModelAndView("/WEB-INF/jsp/taglocation.jsp", "model", model); } }
What happens now is that anytime a web page that is controlled by this controller is loaded, the handleRequest method will be invoked. This method simply passes in a model object to the proper jsp.
Modify the web.XML
Now we need to modify the web.xml so that the controller will be loaded at the proper time. The web.xml should now look like this:
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <jsp-config> <taglib> <taglib-uri>/spring</taglib-uri> <taglib-location>/WEB-INF/spring.tld</taglib-location> </taglib> </jsp-config> <servlet> <servlet-name>NorthwindDemo</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>NorthwindDemo</servlet-name> <url-pattern>/taglocation.htm</url-pattern> </servlet-mapping> </web-app>
The two things we did here:
- Told the web application about a servlet called NorthwindDemo.
- Told the web application to invoke the NorthwindDemo servlet whenever a page called 'taglocation.htm' is requested.
Create a servlet xml
Now we need to create a servlet XML that controls how the NorthwindDemo servlet behaves. Create a new file called NorthwindDemo-servlet.xml in the WEB-INF folder (the name of the file must be the name of the servlet with "-servlet.xml" appended to it). Make the servlet xml look like this:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi" xmlns:amq="http://activemq.apache.org/schema/core" xsi:schemaLocation="http://www.springframework.org/schema/beans META-INF/xsd/spring-beans-2.5.xsd http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd"> <bean name="/taglocation.htm" class="com.northwind.rfid.shipping.war.TagLocationController" /> </beans>
Modify index.jsp
The last thing to do is to modify our welcome file so that we are redirected to 'taglocation.htm' by default
<%@ include file="/WEB-INF/jsp/include.jsp" %> <%-- Redirected because we can't set the welcome page to a virtual URL. --%> <c:redirect url="/taglocation.htm"/>
Run It!
When you run the web application again (or use the 'update' trick) and point your browser to http://127.0.0.1:8080/NORTHWIND-DEMO, you should see the same Hello World message as last time. This time however, the back end is using Spring's Web MVC framework.
Write at TagLocation Service
Now that we have all the MVC plumbing working, we need to hook up the RFID application with the web application. We will do this by creating a service that the controller can use to find out the list of tags at each stage. Then we will write a JMS listener who will update the service with the latest information. Finally we will inject the service into the contoller.
Write a Service Interface
- Create a new package called com.northwind.rfid.shipping.war.service
- Create a new Interface called TagLocationService
package com.northwind.rfid.shipping.war.service; import java.util.List; import com.northwind.rfid.shipping.notifications.AlertNotification; /** * This service provides the current list of tags at the Dock Door and the Weigh * Station, as well as all the alerts that have happened so far * * @author Kyle Neumeier - kyle@pramari.com * */ public interface TagLocationService { /** * Get a list of all packages at the dock door * * @return */ List<String> getDockDoorItems(); /** * Get a list of all packages at the Weigh Station * * @return */ List<String> getWeighStationItems(); /** * Get a list of all the alerts * * @return */ List<AlertNotification> getAlerts(); }
Write a Manager Interface
The TagLocationService interface allows a component to consume information. We also need the ability to add data to the service as it arrives. As such, let's create a second interface with the required methods.
- Create a new package called com.northwind.rfid.shipping.war.service.impl
- Create a new Interface called TagLocationServiceManager
package com.northwind.rfid.shipping.war.service.impl; import com.northwind.rfid.shipping.notifications.AlertNotification; /** * The manager of the TagLocationService. It allows a component to add data. * * @author Kyle Neumeier - kyle@pramari.com * */ public interface TagLocationServiceManager { /** * Called when a new package has arrived at the dockdoor * * @param item */ void PackageArrivedAtDockDoor(String item); /** * Called when a package departs from the dock door * * @param item */ void PackageDepartedFromDockDoor(String item); /** * Called when an item arrives at the weigh station * * @param item */ void PackageArrivedAtWeighStation(String item); /** * Called when an item departs from the weigh station * * @param item */ void PackageDepartedFromWeighStation(String item); /** * Called when a new alert happens * * @param notification */ void Alert(AlertNotification notification); }
Implement the Service
Create a class called TagLocationServiceImpl in the com.northwind.rfid.shipping.war.service.impl package. There are a few considerations to take into account:
- The class needs to be thread-safe, since multiple threads might access the class. In our case, there are two: The JMS thread will call the manager methods, while the servlet thread will call the service methods. In order to prevent any race conditions, I wrapped the three data structures in synchronized collections. I also made sure to copy the collections before I returned them.
- We probably want to preserve the ordering that tags were inserted into the lists, however we don't want to store one tag more than once in each data structure. Therefore, I used a LinkedHashSet.
package com.northwind.rfid.shipping.war.service.impl; import java.util.Collections; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import com.northwind.rfid.shipping.notifications.AlertNotification; import com.northwind.rfid.shipping.war.service.TagLocationService; /** * Thread-safe implementation of TagLocationService and TagLocationServiceManager * interfaces. * * @author Kyle Neumeier - kyle@pramari.com * */ public class TagLocationServiceImpl implements TagLocationService, TagLocationServiceManager { /** All the items that can be seen at dock door */ private final Set<String> dockDoor = Collections .synchronizedSet(new LinkedHashSet<String>()); /** All the items that can be seen at the weigh station */ private final Set<String> weighstation = Collections .synchronizedSet(new LinkedHashSet<String>()); /** All the alerts that have been created */ private final List<AlertNotification> alerts = Collections .synchronizedList(new LinkedList<AlertNotification>()); /* * (non-Javadoc) * * @see * com.northwind.rfid.shipping.war.service.TagLocationService#getAlerts() */ @Override public List<AlertNotification> getAlerts() { return new LinkedList<AlertNotification>(alerts); } /* * (non-Javadoc) * * @see * com.northwind.rfid.shipping.war.service.TagLocationService#getDockDoorItems * () */ @Override public List<String> getDockDoorItems() { return new LinkedList<String>(dockDoor); } /* * (non-Javadoc) * * @seecom.northwind.rfid.shipping.war.service.TagLocationService# * getWeighStationItems() */ @Override public List<String> getWeighStationItems() { return new LinkedList<String>(weighstation); } /* * (non-Javadoc) * * @see * com.northwind.rfid.shipping.war.service.impl.TagLocationServiceManager * #Alert(com.northwind.rfid.shipping.notifications.AlertNotification) */ @Override public void Alert(AlertNotification notification) { this.alerts.add(notification); } /* * (non-Javadoc) * * @see * com.northwind.rfid.shipping.war.service.impl.TagLocationServiceManager * #PackageArrivedAtDockDoor(java.lang.String) */ @Override public void PackageArrivedAtDockDoor(String item) { this.dockDoor.add(item); } /* * (non-Javadoc) * * @see * com.northwind.rfid.shipping.war.service.impl.TagLocationServiceManager * #PackageArrivedAtWeighStation(java.lang.String) */ @Override public void PackageArrivedAtWeighStation(String item) { this.weighstation.add(item); } /* * (non-Javadoc) * * @see * com.northwind.rfid.shipping.war.service.impl.TagLocationServiceManager * #PackageDepartedFromDockDoor(java.lang.String) */ @Override public void PackageDepartedFromDockDoor(String item) { this.dockDoor.remove(item); } /* * (non-Javadoc) * * @see * com.northwind.rfid.shipping.war.service.impl.TagLocationServiceManager * #PackageDepartedFromWeighStation(java.lang.String) */ @Override public void PackageDepartedFromWeighStation(String item) { this.weighstation.remove(item); } }
Write a JMS Listener
Create a new class called MessageReceiever in the com.northwind.rfid.shipping.war pacakge.
package com.northwind.rfid.shipping.war; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.ObjectInputStream; import javax.jms.BytesMessage; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import com.northwind.rfid.shipping.notifications.AlertNotification; import com.northwind.rfid.shipping.notifications.EZone; import com.northwind.rfid.shipping.notifications.ItemArrivalNotification; import com.northwind.rfid.shipping.notifications.ItemDepartureNotification; import com.northwind.rfid.shipping.war.service.impl.TagLocationServiceManager; /** * The class that implements the logic of receiving a notification from JMS * using the TagLocationServiceManager to update the TagLocationService * * @author Kyle Neumeier - kyle@pramari.com * */ public class MessageReceiver implements MessageListener { /** The TagLocationServiceManager to use */ private volatile TagLocationServiceManager TLSManager; /** * Called by spring * * @param TLSManager * The TagLocationServiceManager */ public void setTLSManager(TagLocationServiceManager TLSManager) { this.TLSManager = TLSManager; } /* * (non-Javadoc) * * @see javax.jms.MessageListener#onMessage(javax.jms.Message) */ @Override public void onMessage(Message arg0) { if (arg0 instanceof BytesMessage) { try { // deserialize the message Object message = deserialize((BytesMessage) arg0); if (message instanceof ItemArrivalNotification) { // If the message is an ItemArrival ItemArrivalNotification notification = (ItemArrivalNotification) message; if (notification.getZone() == EZone.DOCK_DOOR) { // If it arrived at the dock door TLSManager.PackageArrivedAtDockDoor(notification .getTag_Id()); } else { // otherwise it arrived at the weigh station TLSManager.PackageArrivedAtWeighStation(notification .getTag_Id()); } } else if (message instanceof ItemDepartureNotification) { // if the message is an Item Departure ItemDepartureNotification notification = (ItemDepartureNotification) message; if (notification.getZone() == EZone.DOCK_DOOR) { // If it departed from the dock door TLSManager.PackageDepartedFromDockDoor(notification .getTag_Id()); } else { // otherwise it departed from the weigh station TLSManager.PackageDepartedFromWeighStation(notification .getTag_Id()); } } else if (message instanceof AlertNotification) { // if the message is an alert TLSManager.Alert((AlertNotification) message); } else { System.out.println("Notification type not recognized"); } } catch (JMSException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } /*** * A private helper method that deserializes the incoming JMS notifications * * @param message * The message to deserialize * @return * @throws JMSException * @throws IOException * @throws ClassNotFoundException */ private Object deserialize(BytesMessage message) throws JMSException, IOException, ClassNotFoundException { int length = (int) message.getBodyLength(); byte[] bytes = new byte[length]; message.readBytes(bytes); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream( bytes)); return in.readObject(); } }
Create the Northwind webpage
What you will learn
- How to use the JSP standard tag library to create a dynamic web page
Create a JSP
Modify the taglocation.jsp file to the following:
<%@ include file="/WEB-INF/jsp/include.jsp" %> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" /> <title>Northwind Shipping</title> <meta name="generator" content="Amaya, see http://www.w3.org/Amaya/" /> </head> <body> <table border="0" style="width: 80%;border-collapse: collapse;table-layout: fixed"> <tr> <td valign="top"> <table border="1" style="width:90%;background-color:#93D2FF;border-collapse: collapse"> <tr> <td style="text-align:center"> <h3>Dock Door</h3> </td> </tr> <tr> <td> <strong>EPC</strong> </td> </tr> <c:forEach items="${model.dockdoor}" var="tag"> <tr> <td style="font-family:monospace;">${tag}</td> </tr> </c:forEach> </table> </td> <td valign="top"> <table border="1" style="width:90%;background-color:#93D2FF;border-collapse: collapse;float:right"> <tr> <td colspan="4" style="text-align:center"> <h3>Weigh Station</h3> </td> </tr> <tr> <td> <strong>EPC</strong> </td> </tr> <c:forEach items="${model.weighstation}" var="tag"> <tr> <td style="font-family:monospace">${tag}</td> </tr> </c:forEach> </table> </td> </tr> <tr> <td colspan="2"> <p /> </td> </tr> <tr> <td colspan="2"> <table border="1" style="width:100%;background-color:#FFA69C;border-collapse: collapse"> <tr> <td> <h3 style="text-align:center">Alerts</h3> </td> </tr> <c:forEach items="${model.alerts}" var="alert"> <tr> <td> Tag:<span style="font-family:monospace">${alert.tag_Id}</span>: ${alert.message} </td> </tr> </c:forEach> </table> </td> </tr> </table> </body> </html>
Putting it all together
Now that we have a TagLocationService which is receiving notifications from our RFID application, and we have a JSP that will display the information, we need to hook up the Controller with the TagLocationService.
Modify the Controller
We first need to modify the controller to do two things:
- Spring needs to inject the TagLocationService into it
- We need to modify the handleRequest method to use the TagLocationService.
package com.northwind.rfid.shipping.war; import java.util.HashMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; import com.northwind.rfid.shipping.war.service.TagLocationService; /** * @author Kyle Neumeier - kyle@pramari.com * */ public class TagLocationController implements Controller { /** The Tag Location Service */ private volatile TagLocationService tagLocationService; /** * Called by Spring * * @param tagLocationService * the tagLocationService to set */ public void setTagLocationService(TagLocationService tagLocationService) { this.tagLocationService = tagLocationService; } @Override public ModelAndView handleRequest(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception { HashMap<String, Object> model = new HashMap<String, Object>(); model.put("dockdoor", tagLocationService.getDockDoorItems()); model.put("weighstation", tagLocationService.getWeighStationItems()); model.put("alerts", tagLocationService.getAlerts()); return new ModelAndView("/WEB-INF/jsp/taglocation.jsp", "model", model); } }
Modify the servlet xml
The next thing is to modify the servlet xml so that it will
- Create the TagLocationService
- Create the JMS Listener and inject the TagLocationServiceManager into it
- Inject the TagLocationService into the Controller.
Modify the NorthwindDemo-servlet.xml to look as follows:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi" xmlns:amq="http://activemq.apache.org/schema/core" xsi:schemaLocation="http://www.springframework.org/schema/beans META-INF/xsd/spring-beans-2.5.xsd http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd"> <!-- Create the Controller --> <bean name="/taglocation.htm" class="com.northwind.rfid.shipping.war.TagLocationController" > <property name="tagLocationService" ref = "tagLocService"/> </bean> <!-- Create the TagLocationService --> <bean id="tagLocService" class="com.northwind.rfid.shipping.war.service.impl.TagLocationServiceImpl" /> <!-- Create the JMS Message Receiver--> <bean id="messageReceiver" class="com.northwind.rfid.shipping.war.MessageReceiver"> <property name="TLSManager" ref="tagLocService" /> </bean> <!-- Create the topic to connect to --> <bean id="NorthwindTopic" class="org.apache.activemq.command.ActiveMQTopic"> <property name="physicalName" value="com.northwind.rfid.shipping.topic" /> </bean> <!-- JMS Connection Factory --> <bean id="connectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory"> <property name="brokerURL" value="vm://externalBroker?create=false" /> </bean> <!-- Spring Helper to listen to a JMS Destination --> <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory" /> <property name="destination" ref="NorthwindTopic" /> <property name="messageListener" ref="messageReceiver" /> </bean> </beans>
Run The Application
Now you can run the whole application. You can point your browser to the web application, start emulator and place tags on the readers. As you move tags around, you can hit the refresh button on your browser to see the tags.