opendms - the first 2 weeks

17

Click here to load reader

Upload: jpc-hanson

Post on 15-Apr-2017

12 views

Category:

Software


0 download

TRANSCRIPT

Page 1: OpenDMS - the first 2 weeks

OpenDMSI couldnt think of anything to call it, but as Darren said it could be open sourced and it is a DMS this seemed logical.

While I am working (at the moment) on eBay functionality, I have been mindful to do things is such a way as to allow this to serve as a framework of sorts for the rest of the DMS proper. In this document I hope to take you through the salient points of the design and how they would be extensible to additional functionality.

IntroductionWhile I have split this project into a loose Model-View-Presenter architecture, it is not tied to any paricular MVP framework or library, more a guiding principal than anything else. Still it serves well enough to divide up the functionality

model

Database (appendix A)As it stands currently, the core of the Model is the JDBC database integration, this begins with the conection manager. I have used the C3PO connection pooling library to implement a pooled datasourceobject which allows client objects request connections from (current max of 20 connections but that can be changed easily).

Individual database queries are encapsulated as concrete commands in a command pattern with the invoker being a factory. As it stands the invoker/factory keeps copies of the commands in a map for easy access, but should there turn out to be a significant number of concrete database commands it would make sense to have a specific factory class for each concrete command to avoid having to store all of them in each instance of the invoker.

As a side point each concrete command has a 'List<String[]> execute(String[])' where the individual query functionality is implemented. In the return type ach element of the String[] represents a column, and each element of the list is a seperate row.

Obviously insert/update/delete queries only return a result code so there is a choice here. Seperate the whole thing into two command patterns or treat every query equally and just use the ouput appropiatelyand consistantly.

Services (appendix B)

This is the second part of the model and encompasses everything else that needs to go on in the background. I can see two types of services that might need to be implemented: Scheduled services andtriggered services. i.e. a service that runs at set intervals and services that are expressly triggered by theuser.

Page 2: OpenDMS - the first 2 weeks

I have implemented the Scheduled Services part but not the Triggered Services part as yet. The Scheduled services are implemented using a class called ServiceScheduler, which creates a ScheduledThreadPoolExecutor (from the java.util.concurrent lib) and stores AbstractService derived types (AbstractService is an empty interface that implements runnable) in a List. It has an add(AbstractService, delay, rate) method so that each service can run at its own pre-specified interval, and delay; as well as a start method which iterates through the list calling the .get() method on each services ScheduledFuture<?>, fairly self explanatory.

As I said I have not implemented the triggered services yet as I dont know what the best way of doing this would be.

View/presenter (appendix C)The View is Jetty based (embedded http server and servlet container). Currently the HTTP server has a fairly basic configuration (no SSL, no sessions, no auth) but I figured that could be configured at a laterstage (jetty has built in database auth functionality, as well as support for SSL and sessions, and a bunch of other fun stuff).

The HTTP Server is responsible for one servlet at the moment, cunningly named UIServlet. This UIServlet uses a View/Presenter Abstract factory to select an appropriate pair of View and Presenter based on the URL parameters passed to the servlets doGet method from individual HTTP requests.

The concrete View objects are responsible for reading .html files located in a resources directory and providing a 'make' method for the presenter to use when putting a particular view together i.e. the make method returns a string representing the particular part of a view that has been requested header/footer/navbar/etc.

As a result the presenters responsibility is two-fold, firstly to assemble the static part of the view (the static html from the concrete views) and to insert dynamic content from the database(using the JDBC implementation described earlier). They do all of this within their String present(AbstractView) methods.

The Abstract factory class brings this all together so that the UIServlet can do its job of selecting the appropriate view based on the URL query strings from an http request.

I hope that this has been at least relatively clear. I know its a lot trying to get an overview of the system as is in a couple of pages. I have included appendices after this if it helps.

[email protected]@tomoparts.co.uk07796311906

Page 3: OpenDMS - the first 2 weeks

Appendix AConnection Manager/** * This class provides access to a pool of database connections. It is a threadsafe singleton * so always use the instance method to get an instance of this class, NEVER STORETHE INSTANCE. * @author Jan P.C. Hanson * */public class ConnectionManager{

/**Singleton instance with eager instanciation**/private static ConnectionManager test = new ConnectionManager();/**C3P0 Data pool**/private ComboPooledDataSource cpds;

/** * singleton contstructor, initialises the connection pool */private ConnectionManager(){

cpds = new ComboPooledDataSource();try{cpds.setDriverClass("com.mysql.jdbc.Driver");}catch (PropertyVetoException e){e.printStackTrace();}

cpds.setJdbcUrl("jdbc:mysql://192.168.0.202:3306/ebay");cpds.setUser("openDMS");cpds.setPassword("0p3nDMS/*-");cpds.setMinPoolSize(3);cpds.setAcquireIncrement(5);cpds.setMaxPoolSize(20);cpds.setMaxStatements(180);

}

/** * instance method returns an instance of the ConnectionManager class * @return ConnectionManager instance */public static ConnectionManager instance(){return test;}

/** * retrieves a database connection from the pool of available connections.

When * Connection.close is called the connection is returned to the pool. * @return * @throws SQLException */public Connection getConnection() throws SQLException{return this.cpds.getConnection();}

}

Page 4: OpenDMS - the first 2 weeks

Database Query (invoker)

public class DatabaseQuery{

/**map of commands**/private Map<QueryType, AbstractDBQuery> commandMap_M;/**defensive enum for map**/public enum QueryType {

SELECT_EBAY_ORDERS, INSERT_EBAY_ORDERS}

/** * constructor, initialises the internal map. */public DatabaseQuery(){

super();this.commandMap_M = new HashMap<QueryType, AbstractDBQuery>();this.populateMap();

}

/** * * @param command enum value taken from the internal enum 'QueryType'

represents the type * of query that you wish to execute. * @param parameters a string array containing the parameters for the query

in question. * Not all queries require parameters whereas others do. Check the concrete

query type * documentation for more information * @return List<String[]> the position within the list defines the row, and

the position * withing the string array defines the column so list index 0, array index

2 would be the * 3rd column of the 1st row. * @throws SQLException */public List<String[]> execute(QueryType command, String[] parameters) throws

SQLException{return this.commandMap_M.get(command).execute(parameters);}

/** * populates the map with the concrete AbstractDBQuery Types. */private void populateMap(){

this.commandMap_M.put(DatabaseQuery.QueryType.SELECT_EBAY_ORDERS, new SelectEbayOrders());

this.commandMap_M.put(DatabaseQuery.QueryType.INSERT_EBAY_ORDERS, new InsertEbayOrders());

}

Abstract Command interface

/**

Page 5: OpenDMS - the first 2 weeks

* Interface that all database queries must adhere to. this is the Abstract command class for * this command pattern. * @author Jan P.C. Hanson * */public interface AbstractDBQuery{

/** * executes the query. * @param Parameter(s) to use in the query, see concrete type's class

documentation for more * information * @return String representing the output of a particular query. */public List<String[]> execute(String[] Parameter) throws SQLException;

}

Concrete Ebay Command Select Examplepublic class SelectEbayOrders implements AbstractDBQuery{

/**reference to the JDBC Statement**/private PreparedStatement statement_M = null;/**reference to the JDBC Database connection**/private Connection connection_M = null;/**SQL query string**/private static final String query = "SELECT * FROM ebay.ebay_buyers;";

/** * default constructor */public SelectEbayOrders(){super();}

/** * execute the query * @param parameter NOT USED for this query. * @return String representing the results of the query. * @throws SQLException */public List<String[]> execute(String[] parameter) throws SQLException{

this.initQuery();

ResultSet resultset = statement_M.executeQuery(query);List<String[]> result = this.formatResults(resultset);this.connection_M.commit();

this.cleanup();

return result;}

/** * formats the ResultSet (returned from the executed query) as a string * @param results the ResultSet (post query execution)

Page 6: OpenDMS - the first 2 weeks

* @return String containing the formatted results. * @throws SQLException */private List<String[]> formatResults(ResultSet results) throws SQLException{

List<String[]> rows = new ArrayList<String[]>();while (results.next()){

String[] cols = new String[4];cols[0] = results.getString("buyerID");cols[1] = results.getString("name");cols[2] = results.getString("shippingAddress");cols[3] = results.getString("invoiceAddress");rows.add(cols);

}return rows;

}

/** * initialise the connection and statement and set transaction variables. * @throws SQLException */private void initQuery() throws SQLException{

this.connection_M = ConnectionManager.instance().getConnection();this.connection_M.setAutoCommit(false);statement_M = connection_M.prepareStatement(query);

}

/** * do cleanup after the query has been executed * @throws SQLException */private void cleanup() throws SQLException{

if (statement_M != null) {statement_M.close();System.out.println("closing statement");}

if (connection_M != null) {connection_M.close();System.out.println("closing connection");}

}}

Concrete Command Insert Example

public class InsertEbayOrders implements AbstractDBQuery{

/**reference to the JDBC Statement**/private PreparedStatement statement_M = null;/**reference to the JDBC Database connection**/private Connection connection_M = null;/**SQL query string**/private String query ="INSERT IGNORE INTO ebay.ebay_orders (orderID,

transactionID, buyerID, salesRecNo, shippingType, createdTime)"+ "VALUES (?,?,?,?,?,?);";

/** * default constructor */

Page 7: OpenDMS - the first 2 weeks

public InsertEbayOrders(){super();}

/** * execute the query * @param parameter an array of strings where the 0th element is the

parameter for the * first column, the 1st element is the parameter for the 2nd column and so

on. The Ebay * Orders Table only has 6 columns so any element above the 3rd element will

be ignored. * col1 =orderID:int(30), col2=transactionID:int(20),

col3=buyerID:varchar(40), * col4=salesRecNo:int(10), col5=shippingType:varchar(200),

col6=createdTine:datetime * @return List<String> representing the results of the query. * @throws SQLException */public List<String[]> execute(String[] parameter) throws SQLException{

this.initQuery();this.connection_M.prepareStatement(query);this.statement_M.setInt(1, 0000);this.statement_M.setInt(2, 1111);this.statement_M.setString(3, "kirk");this.statement_M.setInt(4, 42);this.statement_M.setString(5, "carrier pidgeon");java.util.Date date = new Date();java.sql.Date sqlDate = new java.sql.Date(date.getTime());this.statement_M.setTimestamp(6, new

java.sql.Timestamp(date.getTime()));// this.statement_M.setInt(6, 20151013);

int resultCode = statement_M.executeUpdate();this.connection_M.commit();

this.cleanup();

List<String[]> res = new ArrayList<String[]>();res.add(new String[] {resultCode + ""});

return res;}

/** * initialise the connection and statement and set transaction variables. * @throws SQLException */private void initQuery() throws SQLException{

this.connection_M = ConnectionManager.instance().getConnection();this.connection_M.setAutoCommit(false);statement_M = connection_M.prepareStatement(query);

}

/** * do cleanup after the query has been executed * @throws SQLException */

Page 8: OpenDMS - the first 2 weeks

private void cleanup() throws SQLException{

if (statement_M != null) {statement_M.close();System.out.println("closing statement");}

if (connection_M != null) {connection_M.close();System.out.println("closing connection");}

Appendix BService Schedulerpublic class ServicesScheduler{

/****/ScheduledThreadPoolExecutor serviceScheduler_M;/****/List<ScheduledFuture<?>> services_M;

/** * * @param noOfThreads */public ServicesScheduler(int noOfThreads){

this.serviceScheduler_M = new ScheduledThreadPoolExecutor(noOfThreads);

this.serviceScheduler_M.setMaximumPoolSize(noOfThreads+2);this.services_M = new ArrayList<ScheduledFuture<?>>();

}

/** * * @param service */public void add(AbstractService service, long delay, long rate){

this.services_M.add(this.serviceScheduler_M.scheduleWithFixedDelay(service, delay, rate, TimeUnit.MINUTES));

}

/** * */public void start(){

try {

for(int i = 0 ; i < this.services_M.size() ; ++i){

this.services_M.get(i).get();}

}catch(InterruptedException | ExecutionException e){e.printStackTrace();}

serviceScheduler_M.shutdown();

Page 9: OpenDMS - the first 2 weeks

}}

Abstract Service Interface

/** * Convienience interface, allows the system to only accept runnable's that implement this * interface rather than all runnables. * @author Jan P.C. Hanson * */public interface AbstractService extends Runnable{

public void run();}

eBay Service example

/** * This class represents the start of the execution flow for the eBay Service, Theservice * quereies the eBay API for Order and Item information and * @author Jan P.C. Hanson * */public class EbayService implements AbstractService{

/* (non-Javadoc) * @see java.lang.Runnable#run() */@Overridepublic void run(){

//index 0 = blank, index 1 = sandbox server string, index 2 = sandbox user token

//index 3 = production server string, index 4 = production user token.String[] credentials = ConfigReader.read("./config/", "ebay.cfg");

OrdersCall oCall = new OrdersCall(credentials[4], credentials[3]);try{

// OrderType[] orders = oCall.call(1);this.insertOrders();this.insertTransactions();this.insertBuyers();this.insertItems();

} // catch (ApiException e)// {e.printStackTrace();}// catch (SdkException e)// {e.printStackTrace();}

catch (Exception e){e.printStackTrace();}

}

private void insertOrders() throws SQLException{

Page 10: OpenDMS - the first 2 weeks

InsertEbayOrders iOrd = new InsertEbayOrders();iOrd.execute(new String[] {});

}

private void insertTransactions(){

}

private void insertBuyers(){

}

private void insertItems(){

}}

Appendix C

Servletpublic class UIServlet extends HttpServlet{

/**needed to avoid warnings**/private static final long serialVersionUID = -417534770555839323L;

/** * instantiates a servlet using the views provided. * @param views */public UIServlet(){super();}

/** * service method, this controls how the servlet responds to particular URL

query strings * @param request the http request to the servlet * @param response the http response to the servlet */public void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException{

PrintWriter out = response.getWriter();

// ViewPresenterFactory viewPresenterFactory = ViewPresenterFactory.instance();

AbstractViewPresenterFactory factory;

String viewParam = request.getParameter("view");

if (ViewPresenterFactory.instance().hasFactory(viewParam)==true){

factory = ViewPresenterFactory.instance().getFactory(viewParam);

Page 11: OpenDMS - the first 2 weeks

out.print(factory.makePresenter().present(factory.makeView()));}else{

factory = ViewPresenterFactory.instance().getFactory("ROOT");out.print(factory.makePresenter().present(factory.makeView()));

}

out.close();}

}

Abstract Factory interface/** * the interface that all concrete factories should subscribe to. * @author Jan P.C. Hanson * */public interface AbstractViewPresenterFactory{

/** * factory method that creates a view * @return AbstractView the view that has been requested */public AbstractView makeView();

/** * factory method that creates a presenter * @return AbstractPresenter the presenter than has been requested. */public AbstractPresenter makePresenter();

}

concrete Factory for eBay View/Presenter/** * EbayAbstractFactory creates the presenter and view for the eBay view. * @author Jan P.C. Hanson * */public class EbayFactory implements AbstractViewPresenterFactory{

/* (non-Javadoc) * @see

openDMS.helpers.viewPresenterFactory.AbstractViewPresenterFactory#makeView() */@Overridepublic AbstractView makeView(){return new EbayView();}

/* (non-Javadoc) * @see

openDMS.helpers.viewPresenterFactory.AbstractViewPresenterFactory#makePresenter() */@Override

Page 12: OpenDMS - the first 2 weeks

public AbstractPresenter makePresenter(){return new EbayPresenter();}

}

concrete Factory for root view/presenter/** * RootAbstractFactory creates the presenter and view for the root view. * @author Jan P.C. Hanson * */public class RootFactory implements AbstractViewPresenterFactory{

/* (non-Javadoc) * @see

openDMS.helpers.viewPresenterFactory.AbstractViewPresenterFactory#makeView() */@Overridepublic AbstractView makeView(){return new RootView();}

/* (non-Javadoc) * @see

openDMS.helpers.viewPresenterFactory.AbstractViewPresenterFactory#makePresenter() */@Overridepublic AbstractPresenter makePresenter(){return new RootPresenter();}

}

Abstract Factory implementationpublic class ViewPresenterFactory{

/**Singleton instance variable**/private static final ViewPresenterFactory instance_M = new

ViewPresenterFactory();/**enum to use for factory creation options**/public enum Select {ROOT, EBAY}/**map of enum constants to concrete factories**/Map<Select, AbstractViewPresenterFactory> factory_M;

/** * default constructor */private ViewPresenterFactory(){

super();factory_M = new HashMap<Select, AbstractViewPresenterFactory>();this.populateMap();

}

/** * singleton instance method, returns an instance of this object.

Page 13: OpenDMS - the first 2 weeks

* @return ViewPresenterFactory the singleton instance. */public static ViewPresenterFactory instance(){return ViewPresenterFactory.instance_M;}

/** * creates a specific factory based on the string provided. * @param factory string that is compared to enum values. * @return AbstractViewPresenterFactory a concrete viewPresenterFactory */public AbstractViewPresenterFactory getFactory(String factory){return this.factory_M.get(ViewPresenterFactory.Select.valueOf(factory));}

/** * check to see if a factory type exists by comparing the string provided to

the * internal enum 'Select' * @param factory the string to compare to the internal enum * @return boolean true if the factory is a valid type, false otherwise. */public boolean hasFactory(String factory){

if (factory == null) {return false;}else if (Select.valueOf(factory) != null) {return true;}else {return false;}

}

/** * populate the internal map with viewPresenterFactories. */private void populateMap(){

this.factory_M.put(ViewPresenterFactory.Select.ROOT, new RootFactory());

this.factory_M.put(ViewPresenterFactory.Select.EBAY, new EbayFactory());

}}

public interface AbstractView{

/** * This method constructs the part of the view that is requested in the

parameters. * @param part should be taken from the internal Enum defined within this

class. Each enum * constant refers to a specific part of the view. * @return String containing the HTML for the part of the view that has been

requested. */public String make(AbstractViewPart part);

}

Views interface/**

Page 14: OpenDMS - the first 2 weeks

* This interface provides a root for all enums needed for the AbstractView derived objects * @author Jan P.C. Hanson * */public interface AbstractViewPart{

/**root enum, to be implemented by the enums inside the Abstract view derived classes**/

public enum Part {};}

concrete view 1public class EbayView implements AbstractView{

/**mapping of enum constant keys to html strings**/private Map<Part, String> viewPart_M;/**enum containing constants relating to the view parts**/public enum Part implements AbstractViewPart{HEADER, FOOTER};

/** * default constructor */public EbayView(){

super();viewPart_M = new HashMap<Part, String>();this.populateMap();

}

/* (non-Javadoc) * @see openDMS.view.AbstractView#make(openDMS.view.AbstractView.PART) */@Overridepublic String make(AbstractViewPart part){return this.viewPart_M.get(part);}

/** * populates the map with the */protected void populateMap(){

this.viewPart_M.put(EbayView.Part.HEADER, FileToString.convert("./res/ebay/", "eBayHeader.html", "utf-8"));

this.viewPart_M.put(EbayView.Part.FOOTER, FileToString.convert("./res/ebay/", "eBayFooter.html", "utf-8"));

}

}

concrete view 2public class RootView implements AbstractView{

/**mapping of enum constant keys to html strings**/private Map<Part, String> viewPart_M;

Page 15: OpenDMS - the first 2 weeks

/**enum containing constants relating to the view parts**/public enum Part implements AbstractViewPart{INDEX}

public RootView(){

super();viewPart_M = new HashMap<Part, String>();this.populateMap();

}

/* (non-Javadoc) * @see openDMS.view.AbstractView#make(openDMS.view.AbstractViewPart) */@Overridepublic String make(AbstractViewPart part){return this.viewPart_M.get(part);}

private void populateMap(){

this.viewPart_M.put(RootView.Part.INDEX, FileToString.convert("./res/", "index.html", "utf-8"));

}

}

presenter interfacepublic interface AbstractPresenter{

/** * this method is called when you actually want to present the information

contained in the * view/presenter to the servlet * @param view the view to be rpesented * @return String representing the html contained in the view/presenter */public String present(AbstractView view);

}

cocnrete presenter 2public class RootPresenter implements AbstractPresenter{

/** * default constructor */public RootPresenter(){super();}

/* (non-Javadoc) * @see

openDMS.presenters.AbstractPresenter#present(openDMS.view.views.AbstractView) */@Overridepublic String present(AbstractView view){

String output = "";

Page 16: OpenDMS - the first 2 weeks

output += view.make(RootView.Part.INDEX);

System.out.println("Root page loaded");

return output;}

}

concrete presenter 1public class EbayPresenter implements AbstractPresenter{

/** * default constructor */public EbayPresenter(){super();}

/* (non-Javadoc) * @see

openDMS.presenters.AbstractPresenter#present(openDMS.view.views.AbstractView) */@Overridepublic String present(AbstractView view){

String output = "";

output += view.make(EbayView.Part.HEADER);output += this.doStuff();output += view.make(EbayView.Part.FOOTER);

return output;}

private String doStuff() {

String result = "";try{

DatabaseQuery query = new DatabaseQuery();query.execute(DatabaseQuery.QueryType.INSERT_EBAY_ORDERS, new

String[] {});List<String[]> rows =

query.execute(DatabaseQuery.QueryType.SELECT_EBAY_ORDERS,new String[] {""});

result+= "<table class='table table-bordered'> \n";result+= "<thead>\n<tr>\n"

+ "<th>ID</th>\n"+ "<th>name</th>\n"+ "<th>Shipping Address</th>\n"+ "<th>Invoice Address</th>\n"+ "</tr>\n</thead>\n <tbody>";

for (String[] cols : rows){

result+="<tr>\n";

Page 17: OpenDMS - the first 2 weeks

result+="<td>"+ cols[0].trim() + "</td>\n";result+="<td>"+ cols[1].trim() + "</td>\n";result+="<td>"+ cols[2].trim() + "</td>\n";result+="<td>"+ cols[3].trim() + "</td>\n";result+="</tr>\n";

}

result+="</tbody>\n</table>";

}catch(SQLException e){e.printStackTrace();}

return "<h1> eBay </h1> \n <a class='btn btn-default' href='/'>Root</a><br/>\n" + result;

}}