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.client.repository.sql;
23  
24  import java.sql.Connection;
25  import java.sql.DatabaseMetaData;
26  import java.sql.PreparedStatement;
27  import java.sql.ResultSet;
28  import java.sql.SQLException;
29  import java.sql.Statement;
30  import java.util.ArrayList;
31  import java.util.Map;
32  
33  import org.apache.log4j.Logger;
34  import org.fosstrak.llrp.adaptor.AdaptorManagement;
35  import org.fosstrak.llrp.adaptor.exception.LLRPRuntimeException;
36  import org.fosstrak.llrp.client.Constants;
37  import org.fosstrak.llrp.client.LLRPMessageItem;
38  import org.fosstrak.llrp.client.Repository;
39  import org.fosstrak.llrp.client.RepositoryFactory;
40  
41  /**
42   * The {@link AbstractSQLRepository} represents a common super class for all 
43   * SQL based Repositories. The class implements the different methods like 
44   * <code>put(LLRPMessageItem)</code> or <code>get(String)</code>  in an 
45   * abstract manner, using the strategy pattern. We explain the idea below:<br/>
46   * Two examples:<br/>
47   * <ol>
48   * <li>Create the table to store the LLRP messages:<br/>
49   * <code>Statement sCreateTable = conn.createStatement();</code><br/>
50   * <code>sCreateTable.execute(sqlCreateTable());</code><br/>
51   * <code>sCreateTable.close();</code><br/>
52   * As you can see, the {@link AbstractSQLRepository} runs on a prepared 
53   * statement (in this case a normal SQL statement would do as well), but this 
54   * statement is not hard-coded. The create SQL is obtained via the method 
55   * <code>sqlCreateTable()</code>. Depending on the implementing data base, 
56   * different SQL statements for the create instruction can be used. Example: a 
57   * derby implementation {@link DerbyRepository} uses a different create SQL 
58   * than a {@link MySQLRepository} (as the data types differ).
59   * </li>
60   * <li>Clear all the messages received by an adapter:<br/>
61   * <code>PreparedStatement psRemove = conn.prepareStatement(</code><br/>
62   * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<code>sqlRemoveAllAdapterMessages());</code><br/>
63   * <code>psRemove.setString(1, adapter);</code><br/>
64   * <code>psRemove.executeUpdate();</code><br/>
65   * <code>psRemove.close();</code><br/>
66   * Here the {@link AbstractSQLRepository} executes a prepared statement. The 
67   * name of the adapter to be cleared, is injected from the code in the 
68   * {@link AbstractSQLRepository}. Again, the actual query is delivered from the 
69   * implementing strategy (example: {@link MySQLRepository}).
70   * </li>
71   * </ol>
72   * <h3>NOTICE:</h3>
73   * By default, the {@link AbstractSQLRepository} uses the SQL commands tailored 
74   * to the Derby database (as this is the default internal database). So, if 
75   * you do not override certain SQL "queries-getter", please be aware that this 
76   * might cause trouble with a database differing from Derby.
77   * @author sawielan
78   *
79   */
80  public abstract class AbstractSQLRepository implements Repository {
81  	
82  	/** column index of the ID.*/
83  	public static final int SELECTOR_ID = 1;
84  	
85  	/** column index of the message type. */
86  	public static final int SELECTOR_MESSAGE_TYPE = 2;
87  	
88  	/** column index of the reader name. */
89  	public static final int SELECTOR_READER = 3;
90  	
91  	/** column index of the adapter name. */
92  	public static final int SELECTOR_ADAPTOR = 4;
93  	
94  	/** column index of the time-stamp column. */
95  	public static final int SELECTOR_TIMESTAMP = 5;
96  	
97  	/** column index of the status flag. */
98  	public static final int SELECTOR_STATUS = 6;
99  	
100 	/** column index of the comment field. */
101 	public static final int SELECTOR_COMMENT = 7;
102 	
103 	/** column index of the mark. */
104 	public static final int SELECTOR_MARK = 8;	
105 	
106 	/** column index of the comment column. */
107 	public static final int SELECTOR_CONTENT = 9;
108 	
109 	/** the name of the database in the database server. */
110 	public static final String DB_NAME = "llrpMsgDB";
111 	
112 	/** the name of the LLRP message repository table. */
113 	public static final String TABLE_LLRP_REPOSITORY = "llrp_msg";
114 	
115 	// the log4j logger.
116 	private static Logger log = Logger.getLogger(AbstractSQLRepository.class);
117 	
118 	/** whether the repository is healthy or not. */
119 	protected boolean isHealth;
120 	
121 	/** the number of table columns. */
122 	protected static final int NUM_TABLE_COLUMNS = 8;
123 	
124 	// ------------------------- JDBC Stuff ------------------------------
125 	/** the JDBC connection. */
126 	protected Connection conn = null;
127 	
128 	/** the database driver to use. NOTICE: the default is derby!. */
129 	public static final String DB_DRIVER = "org.apache.derby.jdbc.EmbeddedDriver";
130 	
131 	/** the database user name. */
132 	protected String username = "llrp";
133 	
134 	/** the database password. */
135 	protected String password = "llrp";
136 	
137 	/** the connection URL. */
138 	protected String connectURL;
139 	
140 	/** whether to wipe the database at startup or not. */
141 	protected boolean wipe = false;
142 	
143 	/** whether to wipe the RO_ACCESS_REPORTS database at startup or not. */
144 	protected boolean wipeROAccess = false;
145 	
146 	/** whether to log RO_ACCESS_REPORT. */
147 	protected boolean logROAccess = false;
148 	
149 	/** map with additional arguments to be passed to the initializer. */
150 	protected Map<String, String> args = null;
151 	
152 	// ------------------------- SQL STATEMENTS -------------------------------
153 	/**
154 	 * <strong>NOTICE:</strong> this SQL command corresponds to derby SQL!.
155 	 * @return a SQL command that creates the table.
156 	 */
157 	protected String sqlCreateTable(){
158 		return "CREATE TABLE " + TABLE_LLRP_REPOSITORY + " "
159     	+ "(MSG_ID CHAR(32),"
160     	+ "MSG_TYPE CHAR(32),"
161     	+ "READER CHAR(64),"
162     	+ "ADAPTER CHAR(64),"
163     	+ "MSG_TIME TIMESTAMP,"
164     	+ "STATUS CHAR(64),"
165     	+ "COMMENT VARCHAR(64),"
166     	+ "MARK CHAR(3),"
167 		+ "CONTENT CLOB)";
168 	}
169 	
170 	/**
171 	 * <strong>NOTICE:</strong> this SQL command corresponds to derby SQL!. So 
172 	 * override, if your database uses different SQL instructions.
173 	 * @return a SQL command that erases all the LLRP messages.
174 	 */
175 	protected String sqlRemoveAllMessages() {
176 		return "delete from " + TABLE_LLRP_REPOSITORY;
177 	}
178 	
179 	/**
180 	 * <strong>NOTICE:</strong> this SQL command corresponds to derby SQL!. So 
181 	 * override, if your database uses different SQL instructions.
182 	 * @return a SQL command that drops the LLRP message table.
183 	 */
184 	protected String sqlDropTable() {
185 		return "DROP TABLE " + TABLE_LLRP_REPOSITORY;
186 	}
187 	
188 	/**
189 	 * <strong>NOTICE:</strong> this SQL command corresponds to derby SQL!. So 
190 	 * override, if your database uses different SQL instructions.
191 	 * @return a SQL command that removes all the messages that belong to a given adapter.
192 	 */
193 	protected String sqlRemoveAllAdapterMessages() {
194 		return "delete from " + TABLE_LLRP_REPOSITORY + " where ADAPTER=?";
195 	}
196 	
197 	/**
198 	 * <strong>NOTICE:</strong> this SQL command corresponds to derby SQL!. So 
199 	 * override, if your database uses different SQL instructions.
200 	 * @return a SQL command that removes all the messages that belong to a given reader.
201 	 */
202 	protected String sqlRemoveAllReaderMessages() {
203 		return "delete from " + TABLE_LLRP_REPOSITORY + " where ADAPTER=? and READER=?";
204 	}
205 	
206 	/**
207 	 * <strong>NOTICE:</strong> this SQL command corresponds to derby SQL!. So 
208 	 * override, if your database uses different SQL instructions.
209 	 * @return a SQL command that inserts a new item into the database.
210 	 */
211 	protected String sqlInsertMessage() {
212 		return "insert into " + TABLE_LLRP_REPOSITORY + " values (?, ?, ?, ?, ?, ?, ?, ?, ?)";
213 	}
214 	
215 	/**
216 	 * <strong>NOTICE:</strong> this SQL command corresponds to derby SQL!. So 
217 	 * override, if your database uses different SQL instructions.
218 	 * @return a SQL command that selects an item by its ID.
219 	 */
220 	protected String sqlSelectMessageByID() {
221 		return "select * from " + TABLE_LLRP_REPOSITORY + " where MSG_ID=?";
222 	}
223 	
224 	/**
225 	 * <strong>NOTICE:</strong> this SQL command corresponds to derby SQL!. So 
226 	 * override, if your database uses different SQL instructions.
227 	 * @return a SQL command that selects all the messages with the content.
228 	 */
229 	protected String sqlSelectMessagesWithContent() { 
230 		return "select * from " + TABLE_LLRP_REPOSITORY + " " +
231 			"order by MSG_TIME DESC";
232 	}
233 
234 	/**
235 	 * <strong>NOTICE:</strong> this SQL command corresponds to derby SQL!. So 
236 	 * override, if your database uses different SQL instructions.
237 	 * @return a SQL command that selects all the messages without the content.
238 	 */
239 	protected String sqlSelectMessagesWithoutContent() {
240 		return "select MSG_ID,MSG_TYPE,READER,ADAPTER,MSG_TIME,STATUS,COMMENT,MARK " +
241 			"from " + TABLE_LLRP_REPOSITORY + " " +
242 			"order by MSG_TIME DESC";
243 	}
244 	
245 	// ------ adaptor given ------
246 	/**
247 	 * <strong>NOTICE:</strong> this SQL command corresponds to derby SQL!. So 
248 	 * override, if your database uses different SQL instructions.
249 	 * @return a SQL command that selects all the messages to a given adapter 
250 	 * with the content.
251 	 */
252 	protected String sqlSelectByAdapterWithContent() { 
253 		return "select * from " + TABLE_LLRP_REPOSITORY + " where ADAPTER=? " +
254 			"order by MSG_TIME DESC";
255 	}
256 	
257 	/**
258 	 * <strong>NOTICE:</strong> this SQL command corresponds to derby SQL!. So 
259 	 * override, if your database uses different SQL instructions.
260 	 * @return a SQL command that selects all the messages to a given adapter 
261 	 * without the content.
262 	 */
263 	protected String sqlSelectByAdapterWithoutContent() { 
264 		return "select MSG_ID,MSG_TYPE,READER,ADAPTER,MSG_TIME,STATUS,COMMENT,MARK " +
265 			"from " + TABLE_LLRP_REPOSITORY + " where ADAPTER=? " +
266 			"order by MSG_TIME DESC";
267 	}
268 	
269 	// ------ reader and adaptor given ------
270 	/**
271 	 * <strong>NOTICE:</strong> this SQL command corresponds to derby SQL!. So 
272 	 * override, if your database uses different SQL instructions.
273 	 * @return a SQL command that selects all the messages to a given adapter 
274 	 * and a given reader with the content.
275 	 */
276 	protected String sqlSelectByAdapterAndReaderWithContent() {
277 		return "select * from " + TABLE_LLRP_REPOSITORY + " where ADAPTER=? and READER=? " +
278 			"order by MSG_TIME DESC";
279 	}
280 	
281 	/**
282 	 * <strong>NOTICE:</strong> this SQL command corresponds to derby SQL!. So 
283 	 * override, if your database uses different SQL instructions.
284 	 * @return a SQL command that selects all the messages to a given adapter 
285 	 * and a given reader without the content.
286 	 */
287 	protected String sqlSelectByAdapterAndReaderWithoutContent() { 
288 		return "select MSG_ID,MSG_TYPE,READER,ADAPTER,MSG_TIME,STATUS,COMMENT,MARK " +
289 			"from " + TABLE_LLRP_REPOSITORY + " where ADAPTER=? and READER=? " +
290 			"order by MSG_TIME DESC";
291 	}
292 
293 	
294     /**
295      * Loads the appropriate JDBC driver for this environment/framework. 
296      * @return true if the loading went fine, false otherwise.
297      */
298 	protected boolean loadDriver() {
299     	boolean isHealth = false;
300     	final String driver = getDBDriver();
301     	try {
302             Class.forName(driver).newInstance();
303             log.info(String.format("Loaded the appropriate driver: %s",
304             		driver));
305             isHealth = true;
306         } catch (ClassNotFoundException cnfe) {
307             log.warn("Unable to load the JDBC driver " + driver);
308             log.warn("Please check your CLASSPATH.");
309             cnfe.printStackTrace(System.err);
310         } catch (InstantiationException ie) {
311         	log.warn("Unable to instantiate the JDBC driver " + driver);
312             ie.printStackTrace(System.err);
313         } catch (IllegalAccessException iae) {
314         	log.warn("Not allowed to access the JDBC driver " + driver);
315             iae.printStackTrace(System.err);
316         }
317         return isHealth;
318     }
319 	
320 	/**
321 	 * Returns the class name of the JDBC driver to be used. <strong>NOTICE
322 	 * </strong>: you should override this method if you use a database other 
323 	 * than derby.
324 	 * @return a class name of the JDBC driver to be used.
325 	 */
326 	protected String getDBDriver() {
327 		return DB_DRIVER;
328 	}
329 	
330 	/**
331 	 * Opens the JDBC connection to the database.
332 	 * @return a handle to the Connection item.
333 	 * @throws Exception whenever the connection could not be established.
334 	 */
335 	protected abstract Connection openConnection() throws Exception;
336 	
337 	public void initialize(Map<String, String> args) 
338 		throws LLRPRuntimeException {
339 
340 		this.args = args;
341 		
342 		username = args.get(RepositoryFactory.ARG_USERNAME);
343 		password = args.get(RepositoryFactory.ARG_PASSWRD);
344 		connectURL = args.get(RepositoryFactory.ARG_JDBC_STRING);
345 		try {
346 			wipe = Boolean.parseBoolean(args.get(RepositoryFactory.ARG_WIPE_DB));
347 			wipeROAccess = Boolean.parseBoolean(
348 				args.get(RepositoryFactory.ARG_WIPE_RO_ACCESS_REPORTS_DB));
349 			logROAccess = Boolean.parseBoolean(
350 					args.get(RepositoryFactory.ARG_LOG_RO_ACCESS_REPORT));
351 		} catch (NumberFormatException e) {
352 			wipe = false;
353 			wipeROAccess = false;
354 			logROAccess = false;
355 			log.error("wrong boolean value in args table for wipe-db|wipe-ro" + 
356 					" - using defaults (false).");
357 		}
358 		
359 		// check if values are set correctly.
360 		if (null == username) {
361 			throw new LLRPRuntimeException("username missing in args table.");
362 		}
363 		if (null == password) {
364 			throw new LLRPRuntimeException("password missing in args table.");
365 		}
366 		if (null == connectURL) {
367 			throw new LLRPRuntimeException("connectURL missing in args table.");
368 		}
369 
370 		//load the desired JDBC driver
371 		isHealth = loadDriver();
372 		log.debug("database driver loaded.");
373 		
374 		try {
375 			conn = openConnection();
376 		} catch (Exception e) {
377 			isHealth = false;
378 			throw new LLRPRuntimeException(e);
379 		}
380 		log.info("Connection Established");
381 	
382 		// wipe table if erroneous or if user requests it by preferences.
383 		if (!existsTable() || wipe) {
384 			dropTable();
385 			createTable();
386 		}
387 	}
388 	
389 	public Map<String, String> getArgs() {
390 		return args;
391 	}
392 	
393 	/**
394 	 * checks whether the required tables exist or not.
395 	 * @return true if everything is ok, false otherwise.
396 	 */
397 	protected boolean existsTable() {
398 		// we try to make a SQL query. if it fails, we assume the table to be dead...
399 		try {
400 			DatabaseMetaData dbMeta = conn.getMetaData();
401 			ResultSet resultSet = dbMeta.getColumns(
402 					null, null, TABLE_LLRP_REPOSITORY, null);
403 			int n = 0;
404 			while (resultSet.next()) {
405 				n++;
406 			}
407 			if (n<NUM_TABLE_COLUMNS) {
408 				throw new SQLException("missing fields");
409 			}
410 			 
411 		} catch (SQLException e) {
412 			log.error("table erroneous or missing. therefore recreate it.");
413 			return false;
414 		}
415 		return true;
416 	}
417 	
418 	/** 
419 	 * drops the table. 
420 	 */
421 	protected void dropTable() {
422 		try {
423 			
424 			Statement sDropTable = conn.createStatement();
425 			sDropTable.execute(sqlDropTable());
426 			
427 			log.info("Existing Table Removed.");
428 			
429 		} catch (Exception e) {
430 			log.info("Table doesn't exist. Remove failed." + e.getMessage());
431 		}
432 	}
433 	
434 	/**
435 	 * generates the necessary tables.
436 	 */
437 	protected void createTable() {
438 		try {
439 			
440 			Statement sCreateTable = conn.createStatement();
441 			
442 			// In first time, the message table will be created. If the table
443 			// exists. The Exception will be triggered.
444 			//
445 			sCreateTable.execute(sqlCreateTable());
446 			sCreateTable.close();
447 			
448 			log.info("New Table Created.");
449 		} catch (Exception e) {
450 			log.info("Table exists. " + e.getMessage());
451 		}
452 	}
453 	
454 	/**
455 	 * store an LLRP message into the repository.
456 	 * @param aMessage the message to be stored.
457 	 */
458 	public void put(LLRPMessageItem aMessage) {
459 		try {
460 			PreparedStatement psInsert = conn.prepareStatement(sqlInsertMessage());
461 
462 			psInsert.setString(SELECTOR_ID, aMessage.getId());
463 			psInsert.setString(SELECTOR_MESSAGE_TYPE, aMessage.getMessageType());
464 			psInsert.setString(SELECTOR_READER, aMessage.getReader());
465 			String adaptor = aMessage.getAdapter();
466 			if (adaptor == null) {
467 				adaptor = AdaptorManagement.DEFAULT_ADAPTOR_NAME;
468 			}
469 			psInsert.setString(SELECTOR_ADAPTOR, adaptor);
470 			psInsert.setTimestamp(
471 					SELECTOR_TIMESTAMP, 
472 					aMessage.getTime());
473 			psInsert.setString(SELECTOR_CONTENT, aMessage.getContent());
474 			psInsert.setString(SELECTOR_COMMENT, aMessage.getComment());
475 			psInsert.setString(SELECTOR_MARK, "" + aMessage.getMark());
476 			psInsert.setString(SELECTOR_STATUS, aMessage.getStatusCode());
477 			
478 			psInsert.executeUpdate();
479 			psInsert.close();
480 		
481 			log.debug("Put Message (ID=" + aMessage.getId() + ") into database.");
482 		} catch (SQLException sqle) {
483             sqle.printStackTrace();
484 		}
485 	}
486 	
487 	/**
488 	 * remove all the messages from the repository.
489 	 */
490 	public void clearAll() {
491 		try {
492 			PreparedStatement psRemoveAll = 
493 				conn.prepareStatement(sqlRemoveAllMessages());
494 			psRemoveAll.executeUpdate();
495 			psRemoveAll.close();
496 		} catch (SQLException sqle) {
497             sqle.printStackTrace();
498 		}
499 	}
500 	
501 	/**
502 	 * @return true if the repository is ok, false otherwise.
503 	 */
504 	public boolean isHealth() {
505 		return isHealth;
506 	}
507 
508 	/**
509 	 * the method computes the number of messages stored in the repository 
510 	 * depending on the input parameters:
511 	 * <ol>
512 	 * 	<li>(adaptor == null) then compute all messages in the repository.</li>
513 	 *  <li>(adaptor != null) && (reader == null) then compute all the messages 
514 	 *  for the adapter ignoring the name of the reader.</li>
515 	 *  <li>(adaptor != null) && (reader != null) then compute all the messages 
516 	 *  for the adapter where the reader name is equal to reader.</li> 
517 	 * </ol>
518 	 * @param adaptor the name of the adapter.
519 	 * @param reader the name of the reader.
520 	 * @return the number of messages stored in the repository.
521 	 */
522 	public int count(String adaptor, String reader) {
523 				
524 		int rowcount = 0;
525 		try {
526 			Statement stmt = conn.createStatement();
527 			String query = "";
528 			if (null == adaptor) {
529 				// all OK
530 				query = "SELECT COUNT(*) FROM LLRP_MSG";
531 			} else if (null == reader) {
532 				// restrict to adaptor
533 				query = String.format("%s WHERE ADAPTER='%s'", query, adaptor);
534 			} else {
535 				query = String.format("%s WHERE ADAPTER='%s' AND READER='%s'", 
536 						query, adaptor, reader);
537 			}
538 			ResultSet resultSet = stmt.executeQuery(query);
539 
540 			// Get the number of rows from the result set
541 			if (resultSet.next()) {
542 				rowcount = resultSet.getInt(1);
543 			}
544 
545 			stmt.close();
546 			resultSet.close();
547 		} catch (SQLException e) {
548 			log.error("Could not retrieve the number of messages: " + 
549 					e.getMessage());
550 		}
551 		
552 		return rowcount;
553 	}
554 
555 	/**
556 	 * clear the repository from entries to a given adapter.
557 	 * @param adapter the name of the adapter to clean out.
558 	 */
559 	public void clearAdapter(String adapter) {
560 		try {
561 			PreparedStatement psRemove = conn.prepareStatement(
562 					sqlRemoveAllAdapterMessages());
563 			psRemove.setString(1, adapter);
564 			psRemove.executeUpdate();
565 			psRemove.close();
566 		} catch (SQLException sqle) {
567             sqle.printStackTrace();
568 		}		
569 	}
570 
571 	/**
572 	 * clear the repository from entries to a given adapter and a given reader.
573 	 * @param adapter the name of the adapter.
574 	 * @param reader the name of the reader.
575 	 */
576 	public void clearReader(String adapter, String reader) {
577 		try {
578 			PreparedStatement psRemove = conn.prepareStatement(
579 					sqlRemoveAllReaderMessages());
580 			psRemove.setString(1, adapter);
581 			psRemove.setString(2, reader);
582 			psRemove.executeUpdate();
583 			psRemove.close();
584 		} catch (SQLException sqle) {
585             sqle.printStackTrace();
586 		}	
587 	}
588 	
589 	/**
590 	 * Close the database connection.
591 	 */
592 	public void close() {
593 		try {
594             if (conn != null) {
595                 conn.close();
596                 conn = null;
597             }
598         } catch (SQLException e) {
599             e.printStackTrace();
600         }
601 	}
602 	
603 	/**
604 	 * returns all the messages from the specified adaptor and the reader 
605 	 * limited by num. if you set num to RETRIEVE_ALL all messages get returned.
606 	 * if you set readerName to null, all the messages of all the readers with 
607 	 * adaptor adaptorName will be returned.
608 	 * @param adaptorName the name of the adaptor.
609 	 * @param readerName the name of the reader.
610 	 * @param num how many messages to retrieve.
611 	 * @param content if true retrieve the message content, false no content.
612 	 * @return a list of messages.
613 	 */
614 	public ArrayList<LLRPMessageItem> get(
615 			String adaptorName, String readerName, int num, boolean content) {
616 		
617 		ArrayList<LLRPMessageItem> msgs = new ArrayList<LLRPMessageItem> ();
618 		
619 		try {
620 			PreparedStatement st = null;
621 			ResultSet results = null;
622 			String sql = null;
623 			if ((null == adaptorName) || 
624 					(Constants.ROOT_NAME.equals(
625 							adaptorName))) {	
626 				sql = sqlSelectMessagesWithoutContent();
627 				if (content) {
628 					sql = sqlSelectMessagesWithContent();
629 				}
630 				st = conn.prepareStatement(sql);
631 			} else if (readerName != null) {
632 				sql = sqlSelectByAdapterAndReaderWithoutContent();
633 				if (content) {
634 					sql = sqlSelectByAdapterAndReaderWithContent();
635 				}
636 				st = conn.prepareStatement(sql);
637 				st.setString(1, adaptorName.trim());
638 				st.setString(2, readerName.trim());
639 			} else {
640 				sql = sqlSelectByAdapterWithoutContent();
641 				if (content) {
642 					sql = sqlSelectByAdapterWithContent();
643 				}
644 				
645 				st = conn.prepareStatement(sql);
646 				st.setString(1, adaptorName.trim());
647 			}
648 			// bound the number of items to retrieve
649 			if (num != Repository.RETRIEVE_ALL) {
650 				st.setMaxRows(num);
651 			}
652 			results = st.executeQuery();
653 			while (results.next()) {
654 				LLRPMessageItem item = new LLRPMessageItem();
655 				item.setAdapter(results.getString(SELECTOR_ADAPTOR));
656 				item.setComment(results.getString(SELECTOR_COMMENT));
657 				item.setId(results.getString(SELECTOR_ID));
658 				item.setMark(results.getInt(SELECTOR_MARK));
659 				item.setMessageType(results.getString(SELECTOR_MESSAGE_TYPE));
660 				item.setReader(results.getString(SELECTOR_READER));
661 				item.setStatusCode(results.getString(SELECTOR_STATUS));
662 				item.setTime(results.getTimestamp(SELECTOR_TIMESTAMP));
663 				if (content) {
664 					item.setContent(results.getString(SELECTOR_CONTENT));
665 				}
666 				msgs.add(item);
667 			}
668 			
669 		} catch (Exception e) {
670 			log.error(
671 					String.format(
672 							"could not retrieve from database: %s\n", 
673 							e.getMessage())
674 					);
675 		}
676 			
677 		return msgs;
678 	}
679 	
680 	/**
681 	 * @param aMsgSysId the message id of the item to be retrieved.
682 	 * @return the LLRP message to the given message id.
683 	 */
684 	public LLRPMessageItem get(String aMsgSysId) {
685 		
686 		LLRPMessageItem msg = new LLRPMessageItem();
687 		
688 		try {
689 			PreparedStatement psSelect = conn.prepareStatement(sqlSelectMessageByID());
690 			psSelect.setString(1, aMsgSysId);
691 			ResultSet results = psSelect.executeQuery();
692 			
693 			if (results.next()) {
694 				msg.setId(results.getString(SELECTOR_ID));
695 				msg.setMessageType(results.getString(SELECTOR_MESSAGE_TYPE));
696 				msg.setReader(results.getString(SELECTOR_READER));
697 				msg.setAdapter(results.getString(SELECTOR_ADAPTOR));
698 				msg.setTime(results.getTimestamp(SELECTOR_TIMESTAMP));
699 				msg.setContent(results.getString(SELECTOR_CONTENT));
700 				msg.setComment(results.getString(SELECTOR_COMMENT));
701 				msg.setMark(results.getInt(SELECTOR_MARK));
702 				msg.setStatusCode(results.getString(SELECTOR_STATUS));
703 				
704 				log.debug("Get Message (ID=" + results.getString(1) + ") from database.");
705 			}
706 			
707 			psSelect.close();
708 			results.close();
709 			
710 		} catch (SQLException sqle) {
711             sqle.printStackTrace();
712 		}
713 		
714 		return msg;
715 	}
716 	
717 	/**
718 	 * @return a handle to the database connection. users of the repository are 
719 	 * allowed to use the database for their own purposes.
720 	 */
721 	public Connection getDBConnection() {
722 		return conn;
723 	}
724 }