View Javadoc

1   /*
2    *  
3    *  Fosstrak LLRP Commander (www.fosstrak.org)
4    * 
5    *  Copyright (C) 2008 ETH Zurich
6    *
7    *  This program is free software: you can redistribute it and/or modify
8    *  it under the terms of the GNU General Public License as published by
9    *  the Free Software Foundation, either version 3 of the License, or
10   *  (at your option) any later version.
11   *
12   *  This program is distributed in the hope that it will be useful,
13   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   *  GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License
18   *  along with this program.  If not, see <http://www.gnu.org/licenses/> 
19   *
20   */
21  
22  package org.fosstrak.llrp.adaptor;
23  
24  import java.rmi.RemoteException;
25  import java.rmi.server.UnicastRemoteObject;
26  import java.util.LinkedList;
27  
28  import org.apache.log4j.Logger;
29  import org.fosstrak.llrp.adaptor.exception.LLRPRuntimeException;
30  import org.fosstrak.llrp.adaptor.util.AsynchronousNotifiableList;
31  import org.fosstrak.llrp.client.LLRPExceptionHandlerTypeMap;
32  import org.llrp.ltk.exceptions.InvalidLLRPMessageException;
33  import org.llrp.ltk.generated.LLRPMessageFactory;
34  import org.llrp.ltk.generated.enumerations.KeepaliveTriggerType;
35  import org.llrp.ltk.generated.messages.KEEPALIVE;
36  import org.llrp.ltk.generated.messages.SET_READER_CONFIG;
37  import org.llrp.ltk.generated.parameters.KeepaliveSpec;
38  import org.llrp.ltk.net.LLRPAcceptor;
39  import org.llrp.ltk.net.LLRPConnection;
40  import org.llrp.ltk.net.LLRPConnectionAttemptFailedException;
41  import org.llrp.ltk.net.LLRPConnector;
42  import org.llrp.ltk.net.LLRPEndpoint;
43  import org.llrp.ltk.net.LLRPIoHandlerAdapter;
44  import org.llrp.ltk.net.LLRPIoHandlerAdapterImpl;
45  import org.llrp.ltk.types.Bit;
46  import org.llrp.ltk.types.LLRPMessage;
47  import org.llrp.ltk.types.UnsignedInteger;
48  
49  /**
50   * This class implements the ReaderInterface. The Reader implementation 
51   * maintains two queues to decouple the user interface from the actual message 
52   * delivery over the network.<br/>
53   * 1. from the user to the LLRP reader: the message to be sent is put into a 
54   * queue. a queue watch-dog awakes as soon as there are messages in the queue 
55   * and delivers them via LTK.<br/>
56   * 2. from the LLRP reader to user: the incoming message from the reader is 
57   * stored into a queue. a queue watch-dog awakes as soon as there are messages 
58   * in the queue and delivers them to the user.
59   * @author sawielan
60   *
61   */
62  public class ReaderImpl extends UnicastRemoteObject implements LLRPEndpoint, Reader {
63  	
64  	/**
65  	 * serial version.
66  	 */
67  	private static final long serialVersionUID = 1L;
68  
69  	/** the logger. */
70  	private static Logger log = Logger.getLogger(ReaderImpl.class);
71  	
72  	/** the llrp connector to the physical reader. */
73  	private LLRPConnection connector = null;
74  	
75  	/** the adaptor where the reader belongs to. */
76  	private Adaptor adaptor = null;
77  
78  	/** a list with all the receivers of asynchronous messages. */
79  	private AsynchronousNotifiableList toNotify = new AsynchronousNotifiableList();
80  	
81  	/** the default keep-alive interval for the reader. */
82  	public static final int DEFAULT_KEEPALIVE_PERIOD = 10000; 
83  	
84  	/** default how many times a keep-alive can be missed. */
85  	public static final int DEFAULT_MISS_KEEPALIVE = 3;
86  	
87  	/** flag whether to throw an exception when a timeout occurred. */
88  	private boolean throwExceptionKeepAlive = true;
89  	
90  	/** meta-data about the reader, if connection is up, number of packages, etc... */
91  	private ReaderMetaData metaData = new ReaderMetaData();
92  	
93  	/** IO handler. */
94  	private LLRPIoHandlerAdapter handler = null;
95  	
96  	/** handle to the connection watch-dog. */
97  	private Thread wd = null;
98  	
99  	/** handle to the out queue worker. */
100 	private Thread outQueueWorker = null;
101 	
102 	/** handle to the in queue worker. */
103 	private Thread inQueueWorker = null;
104 	
105 	/** queue to hold the incoming messages.*/
106 	private LinkedList<byte[]> inqueue = new LinkedList<byte[]> ();
107 	
108 	/** queue to hold the outgoing messages. */
109 	private LinkedList<LLRPMessage> outqueue = new LinkedList<LLRPMessage> ();
110 	
111 	/** queue policies. */
112 	public enum QueuePolicy {
113 		DROP_QUEUE_ON_ERROR,
114 		KEEP_QUEUE_ON_ERROR
115 	};
116 	
117 	/**
118 	 * constructor for a local reader stub. the stub maintains connection
119 	 * to the llrp reader.
120 	 * @param adaptor the adaptor responsible for this reader.
121 	 * @param readerName the name of this reader.
122 	 * @param readerAddress the address where to connect.
123 	 * @throws RemoteException whenever there is an RMI exception
124 	 */
125 	public ReaderImpl(Adaptor adaptor, String readerName, String readerAddress) throws RemoteException {
126 		this.adaptor = adaptor;
127 		metaData._setAllowNKeepAliveMisses(DEFAULT_MISS_KEEPALIVE);
128 		metaData._setKeepAlivePeriod(DEFAULT_KEEPALIVE_PERIOD);
129 		metaData._setReaderName(readerName);
130 		metaData._setReaderAddress(readerAddress);
131 	}
132 	
133 	/**
134 	 * constructor for a local reader stub. the stub maintains connection
135 	 * to the llrp reader.
136 	 * @param adaptor the adaptor responsible for this reader.
137 	 * @param readerName the name of this reader.
138 	 * @param readerAddress the address where to connect.
139 	 * @param port the port where to connect.
140 	 * @throws RemoteException whenever there is an RMI exception
141 	 */
142 	public ReaderImpl(Adaptor adaptor, String readerName, String readerAddress, int port) throws RemoteException {
143 		this.adaptor = adaptor;
144 		metaData._setAllowNKeepAliveMisses(DEFAULT_MISS_KEEPALIVE);
145 		metaData._setKeepAlivePeriod(DEFAULT_KEEPALIVE_PERIOD);
146 		metaData._setReaderName(readerName);
147 		metaData._setReaderAddress(readerAddress);
148 		metaData._setPort(port);
149 	}
150 	
151 	/* (non-Javadoc)
152 	 * @see org.fosstrak.llrp.adaptor.ReaderIface#connect(boolean)
153 	 */
154 	public void connect(boolean clientInitiatedConnection) throws LLRPRuntimeException, RemoteException {
155 		
156 		try {
157 			String address = metaData.getReaderAddress();
158 			metaData._setClientInitiated(clientInitiatedConnection);
159 			
160 			// start a new counter session
161 			metaData._newSession();
162 			
163 			if (metaData.getPort() == -1) {
164 				metaData._setPort(Constants.DEFAULT_LLRP_PORT);
165 				log.warn("port for reader '" + metaData.getReaderName() + "' not specified. using default port " + metaData.getPort());
166 			}
167 			
168 			if (clientInitiatedConnection) {
169 				if (address == null) {
170 					log.error("address for reader '" + metaData.getReaderName() + "' is empty!");
171 					reportException(new LLRPRuntimeException("address for reader '" + metaData.getReaderName() + "' is empty!"));
172 					return;
173 				}
174 					
175 				// run ltk connector.
176 				LLRPConnector connector = new LLRPConnector(this, address, metaData.getPort());
177 				connector.getHandler().setKeepAliveAck(true);
178 				connector.getHandler().setKeepAliveForward(true);
179 				try {
180 					connector.connect();
181 				} catch (LLRPConnectionAttemptFailedException e) {
182 					log.error("connection attempt to reader " + metaData.getReaderName() + " failed");
183 					reportException(new LLRPRuntimeException("connection attempt to reader " + metaData.getReaderName() + " failed"));
184 				}
185 				
186 				this.connector = connector;
187 			} else {
188 				// run the ltk acceptor
189 				LLRPAcceptor acceptor = new LLRPAcceptor(this, metaData.getPort());
190 				handler = new LLRPIoHandlerAdapterImpl(acceptor);
191 				handler.setKeepAliveAck(true);
192 				acceptor.getHandler().setKeepAliveForward(true);
193 				acceptor.setHandler(handler);
194 				try {
195 					acceptor.bind();
196 				} catch (LLRPConnectionAttemptFailedException e) {
197 					log.error("could not bind acceptor for reader " + metaData.getReaderName());
198 					reportException(new LLRPRuntimeException("could not bind acceptor for reader " + metaData.getReaderName()	));
199 				}
200 				
201 				this.connector = acceptor;
202 			}
203 			metaData._setConnected(true);
204 			
205 			outQueueWorker = new Thread(getOutQueueWorker());
206 			outQueueWorker.start();
207 			
208 			inQueueWorker = new Thread(getInQueueWorker());
209 			inQueueWorker.start();
210 	
211 			// only do heart beat in client initiated mode.
212 			if (clientInitiatedConnection) {
213 				enableHeartBeat();
214 			}
215 			log.info(String.format("reader %s connected.", metaData.getReaderName()));
216 			
217 		} catch (Exception e) {
218 			// catch all unexpected errors...
219 			LLRPRuntimeException ex = new LLRPRuntimeException(
220 					String.format("Could not connect to reader %s on adapter %s:\nException: %s",
221 							getReaderName(), adaptor.getAdaptorName(), e.getMessage()));
222 			reportException(ex);
223 			throw ex;
224 		}
225 	}
226 	
227 	/* (non-Javadoc)
228 	 * @see org.fosstrak.llrp.adaptor.ReaderIface#disconnect()
229 	 */
230 	public void disconnect() throws RemoteException {
231 		log.debug("disconnecting the reader.");
232 		setReportKeepAlive(false);
233 		
234 		if (connector != null) {
235 			try {
236 				if (connector instanceof LLRPConnector) {
237 					// disconnect from the reader.
238 					((LLRPConnector)connector).disconnect();
239 				} else if (connector instanceof LLRPAcceptor) {
240 					// close the acceptor.
241 					((LLRPAcceptor)connector).close();
242 				}
243 			} catch (Exception e) {
244 				connector = null;
245 			}
246 		}
247 		
248 		metaData._setConnected(false);
249 		
250 		// stop the outqueue worker
251 		if (null != outQueueWorker) {
252 			outQueueWorker.interrupt();
253 		}
254 		
255 		// stop the inqueue worker
256 		if (null != inQueueWorker) {
257 			inQueueWorker.interrupt();
258 		}
259 		
260 		// stop the connection watch-dog.
261 		if (null != wd) {
262 			wd.interrupt();
263 		}
264 	}
265 	
266 	/* (non-Javadoc)
267 	 * @see org.fosstrak.llrp.adaptor.ReaderIface#reconnect()
268 	 */
269 	public void reconnect() throws LLRPRuntimeException, RemoteException  {
270 		// first try to disconnect
271 		disconnect();
272 		connect(isClientInitiated());
273 	}
274 	
275 	/* (non-Javadoc)
276 	 * @see org.fosstrak.llrp.adaptor.ReaderIface#send(byte[])
277 	 */
278 	public void send(byte[] message) throws RemoteException {
279 		if (!metaData.isConnected() || (connector == null)) {
280 			reportException(new LLRPRuntimeException(String.format("reader %s is not connected", metaData.getReaderName())));
281 			return;
282 		}
283 		
284 		// try to create the llrp message from the byte array.
285 		LLRPMessage llrpMessage = null;
286 		try {
287 			llrpMessage = LLRPMessageFactory.createLLRPMessage(message);
288 		} catch (InvalidLLRPMessageException e) {
289 			reportException(new LLRPRuntimeException(e.getMessage()));
290 		}
291 		
292 		if (llrpMessage == null) {
293 			log.warn(String.format("do not send empty llrp message on reader %s", metaData.getReaderName()));
294 			return;
295 		}
296 		
297 		// put the message into the outqueue
298 		synchronized (outqueue) {
299 			outqueue.add(llrpMessage);
300 			outqueue.notifyAll();
301 		}
302 	}
303 	
304 	/**
305 	 * performs the actual sending of the LLRP message to the reader.
306 	 * @param llrpMessage the LLRP message to be sent.
307 	 * @throws RemoteException at RMI exception.
308 	 */
309 	private void sendLLRPMessage(LLRPMessage llrpMessage) throws RemoteException {
310 		try {
311 			// send the message asynchronous.
312 			connector.send(llrpMessage);
313 			metaData._packageSent();
314 		} catch (NullPointerException npe) {
315 			// a null-pointer exception occurs when the reader is no more connected.
316 			// we therefore report the exception to the GUI.
317 			disconnect();
318 			reportException(new LLRPRuntimeException(String.format("reader %s is not connected", metaData.getReaderName()),
319 					LLRPExceptionHandlerTypeMap.EXCEPTION_READER_LOST));
320 		} catch (Exception e) {
321 			// just to be sure...
322 			disconnect();			
323 		}		
324 	}
325 
326 	/* (non-Javadoc)
327 	 * @see org.fosstrak.llrp.adaptor.ReaderIface#isConnected()
328 	 */
329 	public boolean isConnected() throws RemoteException {
330 		return metaData.isConnected();
331 	}
332 
333 	/**
334 	 * when there is an error, the ltk will call this method.
335 	 * @param message the error message from ltk.
336 	 */
337 	public void errorOccured(String message) {
338 		reportException(new LLRPRuntimeException(message));
339 	}
340 
341 	/**
342 	 * when a message arrives through ltk, this method is called.
343 	 * @param message the llrp message delivered by ltk.
344 	 */
345 	public void messageReceived(LLRPMessage message) {
346 		if (message == null) {
347 			return;
348 		}
349 		byte[] binaryEncoded;
350 		try {
351 			binaryEncoded = message.encodeBinary();
352 		} catch (InvalidLLRPMessageException e1) {
353 			reportException(new LLRPRuntimeException(e1.getMessage()));
354 			return;
355 		}
356 		metaData._packageReceived();
357 		if (message instanceof KEEPALIVE) {
358 			
359 			metaData._setAlive(true);
360 			log.debug("received keepalive message from the reader:" + metaData.getReaderName());
361 			if (!metaData.isReportKeepAlive()) {
362 				return;
363 			}
364 		}
365 		
366 		// put the message into the inqueue.
367 		synchronized (inqueue) {
368 			inqueue.add(binaryEncoded);
369 			inqueue.notifyAll();
370 		}
371 	}
372 
373 	/**
374 	 * deliver a received message to the handlers.
375 	 * @param binaryEncoded the binary encoded LLRP message.
376 	 */
377 	private void deliverMessage(byte[] binaryEncoded) {
378 		try {
379 			adaptor.messageReceivedCallback(binaryEncoded, metaData.getReaderName());
380 		} catch (RemoteException e) {
381 			reportException(new LLRPRuntimeException(e.getMessage()));
382 		}
383 		
384 		// also notify all the registered notifyables.
385 		try {
386 			toNotify.notify(binaryEncoded, metaData.getReaderName());
387 		} catch (RemoteException e) {
388 			reportException(new LLRPRuntimeException(e.getMessage()));
389 		}
390 	}
391 
392 	/* (non-Javadoc)
393 	 * @see org.fosstrak.llrp.adaptor.ReaderIface#getReaderAddress()
394 	 */
395 	public String getReaderAddress() throws RemoteException {
396 		return metaData.getReaderAddress();
397 	}
398 
399 	/* (non-Javadoc)
400 	 * @see org.fosstrak.llrp.adaptor.ReaderIface#getPort()
401 	 */
402 	public int getPort() throws RemoteException {
403 		return metaData.getPort();
404 	}
405 	
406 	/* (non-Javadoc)
407 	 * @see org.fosstrak.llrp.adaptor.ReaderIface#isClientInitiated()
408 	 */
409 	public boolean isClientInitiated() throws RemoteException {
410 		return metaData.isClientInitiated();
411 	}
412 	
413 	public void setClientInitiated(boolean clientInitiated)
414 			throws RemoteException {
415 		metaData._setClientInitiated(clientInitiated);
416 	}
417 
418 	/* (non-Javadoc)
419 	 * @see org.fosstrak.llrp.adaptor.ReaderIface#registerForAsynchronous(org.fosstrak.llrp.adaptor.AsynchronousNotifiable)
420 	 */
421 	public void registerForAsynchronous(AsynchronousNotifiable receiver) throws RemoteException {
422 		toNotify.add(receiver);
423 	}
424 	
425 	/* (non-Javadoc)
426 	 * @see org.fosstrak.llrp.adaptor.ReaderIface#deregisterFromAsynchronous(org.fosstrak.llrp.adaptor.AsynchronousNotifiable)
427 	 */
428 	public void deregisterFromAsynchronous(AsynchronousNotifiable receiver) throws RemoteException {
429 		toNotify.remove(receiver);
430 	}
431 	public String getReaderName() throws RemoteException {
432 		return metaData.getReaderName();
433 	}
434 
435 	public boolean isConnectImmediate() throws RemoteException {
436 		return metaData.isConnectImmediately();
437 	}
438 
439 	public void setConnectImmediate(boolean value) throws RemoteException {
440 		metaData._setConnectImmediately(value);
441 	}
442 
443 	/**
444 	 * reports an exception the the adaptor. if the reporting of the 
445 	 * exception also fails, the stack-trace gets logged but the 
446 	 * reader continues to work.
447 	 * @param e the exception to report.
448 	 */
449 	private void reportException(LLRPRuntimeException e) {
450 		if (adaptor == null) {
451 			log.error("no adaptor to report exception to on reader: " + metaData.getReaderName());
452 			return;
453 		}
454 		try {
455 			adaptor.errorCallback(e, metaData.getReaderName());
456 		} catch (RemoteException e1) {
457 			// print the stacktrace to the console
458 			log.debug(e1.getStackTrace().toString());
459 		}
460 	}
461 	
462 	/**
463 	 * sends a SET_READER_CONFIG message that sets the keepalive value. 
464 	 */
465 	private void enableHeartBeat() {		
466 		// build the keepalive settings
467 		SET_READER_CONFIG sr = new SET_READER_CONFIG();
468 		KeepaliveSpec ks = new KeepaliveSpec();
469 		ks.setKeepaliveTriggerType(new KeepaliveTriggerType(KeepaliveTriggerType.Periodic));
470 		ks.setPeriodicTriggerValue(new UnsignedInteger(metaData.getKeepAlivePeriod()));
471 		
472 		sr.setKeepaliveSpec(ks);
473 		sr.setResetToFactoryDefault(new Bit(0));
474 		
475 		log.debug(String.format("using keepalive periode: %d", metaData.getKeepAlivePeriod()));		
476 		try {
477 			send(sr.encodeBinary());
478 		} catch (RemoteException e) {
479 			if (throwExceptionKeepAlive) {
480 				reportException(new LLRPRuntimeException("Could not install keepalive message: " + e.getMessage()));
481 			} else {
482 				e.printStackTrace();
483 			}
484 		} catch (InvalidLLRPMessageException e) {
485 			e.printStackTrace();
486 		}
487 		
488 		// run the watch-dog
489 		wd = new Thread(new Runnable() {
490 			public void run() {
491 				log.debug("starting connection watchdog.");
492 				try {
493 					while (isConnected()) {
494 						try {
495 							Thread.sleep(metaData.getAllowNKeepAliveMisses() * metaData.getKeepAlivePeriod());
496 							if (!metaData.isAlive()) {
497 								log.debug("connection timed out...");
498 								disconnect();
499 								if (throwExceptionKeepAlive) {
500 									reportException(new LLRPRuntimeException("Connection timed out",
501 											LLRPExceptionHandlerTypeMap.EXCEPTION_READER_LOST));
502 								}
503 							}
504 							metaData._setAlive(false);
505 						} catch (InterruptedException e) {
506 							log.debug("received interrupt - stopping watchdog.");
507 						}
508 						
509 					}
510 				} catch (RemoteException e) {
511 					e.printStackTrace();
512 				}
513 				log.debug("connection watchdog stopped.");
514 			}
515 		});
516 		wd.start();
517 	}
518 
519 	/* (non-Javadoc)
520 	 * @see org.fosstrak.llrp.adaptor.Reader#getKeepAlivePeriod()
521 	 */
522 	public int getKeepAlivePeriod() throws RemoteException {
523 		return metaData.getKeepAlivePeriod();
524 	}
525 
526 	/* (non-Javadoc)
527 	 * @see org.fosstrak.llrp.adaptor.Reader#setKeepAlivePeriod(int, int, boolean, boolean)
528 	 */
529 	public void setKeepAlivePeriod(int keepAlivePeriod, int times,
530 			boolean report, boolean throwException) throws RemoteException {
531 		
532 		metaData._setKeepAlivePeriod(keepAlivePeriod);
533 		metaData._setAllowNKeepAliveMisses(times);
534 		metaData._setReportKeepAlive(report);
535 		this.throwExceptionKeepAlive = throwException;
536 		
537 	}
538 
539 	public void setReportKeepAlive(boolean report) throws RemoteException {
540 		metaData._setReportKeepAlive(report);
541 	}
542 
543 	public boolean isReportKeepAlive() throws RemoteException {
544 		return metaData.isReportKeepAlive();
545 	}
546 
547 	public final ReaderMetaData getMetaData() throws RemoteException {
548 		return new ReaderMetaData(metaData);
549 	}
550 	
551 	/**
552 	 * creates a runnable that watches the out queue for new messages to be 
553 	 * sent. at arrival of a new message, the message is sent via LTK.
554 	 * @return a runnable.
555 	 */
556 	private Runnable getOutQueueWorker() {
557 		final LinkedList<LLRPMessage> queue = outqueue;
558 		return new Runnable() {
559 			public void run() {
560 				try {
561 					while (true) {
562 						synchronized (queue) {
563 							while (queue.isEmpty()) queue.wait();
564 							
565 							LLRPMessage msg = queue.removeFirst();
566 							try {
567 								sendLLRPMessage(msg);
568 							} catch (RemoteException e) {
569 								log.debug(String.format(
570 										"Could not send message: %s", 
571 										e.getMessage()));
572 							}
573 						}
574 					}
575 				} catch (InterruptedException e) {
576 					log.debug("stopping out queue worker.");
577 				}
578 			}
579 			
580 		};
581 	}
582 	
583 	/**
584 	 * creates a runnable that watches the in queue for new messages and at 
585 	 * arrival, delivers them to the management.
586 	 * @return a runnable.
587 	 */
588 	private Runnable getInQueueWorker() {
589 		final LinkedList<byte[]> queue = inqueue;
590 		return new Runnable() {
591 			public void run() {
592 				try {
593 					while (true) {
594 						synchronized (queue) {
595 							while (queue.isEmpty()) queue.wait();
596 							
597 							byte[] msg = queue.removeFirst();
598 							deliverMessage(msg);
599 						}
600 					}
601 				} catch (InterruptedException e) {
602 					log.debug("stopping in queue worker.");
603 				}
604 			}			
605 		};
606 	}
607 
608 }