Contents Overview Architecture: Client Side Architecture: Server Side Pains & Gains Future Work Demos Overview X-Traveler is a mobile application on the android platform Allows people to search and share information about different tourist sites like national parks ~100 classes and ~10000 lines of codes The codes are well refactored for further development Major use cases have been implemented: Login, Register, Search for Attraction Place, View Attraction Place Details, Open Personal Page, Add To Wishlist, Create Travel Plan Overview Partially Implemented Extension Features: Add Friends Extension Features To Be Implemented: Invite Friends to Comment on Plan, Recommend User with Similar Interests Overview Use Cases Review: Basic: Login, Signup, Search for Place, Open Personal Page, Add to Wish list, Add to Visited List, Create Travel Plan Extension: Recommend Friend, Recommend Place, Add friend, Comment on Travel Plan Overview The Architecture: Socket JSONObject Architecture: Client Side UI View Model Utility Class Command res Userinterface.package Util.package Command.package xml Activity SocketHelper Interface: HttpHelper Request Response Client Side: UI Views UI Pages: Personal Page Login wishlist Place visitedlist Main Page Signup Find Place City Client Side: Model Each Activity corresponds to a page in the app public class LogInActivity extends Activity { TextView test; Handler mHandler; …… signupButton = (Button)findViewById(R.id.signup); signupButton.setOnClickListener(new OnClickListener(){ public void onClick(View arg0) { Request req = new SignUpRequest(username.getText().toString() ,password.getText().toString() ); req.getJSON(); ConnectServerThread().start(); // Option: new Task().excute(); } }); jsonobj = new Client Side: Model Open a new thread to connect with Server-Side class ConnectServerThread extends Thread { public void run() { try { helper.sendJSONToServer(jsonobj); Message msg = mHandler.obtainMessage(); msg.obj = helper.getUTFFromServer(); JSONObject resJSON = helper.toJSON(msg.obj String response = (String) resJSON.get(keys.RES_LOGIN_ACT); if (response.equals(values.LOGIN_SUC) || response.equals(values.SIGN_UP_SUC)) { /* Switch to next page */ Response res = new LoginSucResponse(); res.run(LogInActivity.this, MainPage.class); } else { mHandler.sendMessage(msg); } Client Side: Model Handling different actions according to different response from Server class MessageHandler extends Handler { @Override public void handleMessage(Message msg) { try { String response = (helper.toJSON(msg.obj)).toString(); if (response.equals(values.SIGN_UP_INVALID_INPUT)){ test.setText("Invalid username or password to sign up."); } else if (response.equals(values.SIGN_UP_USERNAME_DUP)){ test.setText("Username exists. Please select another one."); } else if … } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); … Client Side: Model Each Activity corresponds to a page in the app public class LogInActivity extends Activity { TextView test; Handler mHandler; …… signupButton = (Button)findViewById(R.id.signup); signupButton.setOnClickListener(new OnClickListener(){ public void onClick(View arg0) { Request req = new SignUpRequest(username.getText().toString() ,password.getText().toString() ); req.getJSON(); ConnectServerThread().start(); // Option: new DownloadFileTask().excute(); } }); jsonobj = new Client Side: Model class DownloadFileTask extends AsyncTask<Void, Void, Void> { @Override public void onPostExecute(Void result) { wishlistImageViews[1].setImageBitmap(downloadImages[1]); …} @Override protected Void doInBackground(Void... params) { // Connect with Server and get feedback HttpUtil httpUtil = new HttpUtil …… InputStream inputStream = httpUtil.getInputStream(); downloadImages[2] = BitmapFactory.decodeStream(inputStream); …… } Client Side: Utility Classes Two ways to connect with outside resources Server: Socket // 10.0.2.2 for the emulator // IPv4 connect in same wifi network socket = new Socket("10.164.238.14", 4415); dos = new DataOutputStream( socket.getOutputStream()); dos.writeUTF(req.toString()); … dis = new DataInputStream( socket.getInputStream()); return dis.readUTF() Images: HTTP httpURLConnection = (HttpURLConnection) url.openConnection(); // Set time out httpURLConnection.setConnectTimeout(3000); httpURLConnection.setRequestMethod("GET"); …… Client Side: Utility Classes Storage Options Shared Preferences Internal Storage SQLite Databases Client Side: Utility Classes Store user information locally: /*Store session key/ username into USER_INFOS.XML*/ SharedPreferences infos = getSharedPreferences(USER_INFOS, 0); infos.edit() .putString(NAME, username.getText().toString()) .commit(); /*Get session key from USER_INFOS.XML*/ Sessionkey = getSharedPreferences(JsonKeys.USER_INFOS, 0).getString(JsonKeys.SESSION_KEY,””) Architecture: Server Side Overview: Session Comman d PersonalPag e Command Factory Open Comman d Login Command Factory Interface: Command Interface: CommandF actory General Command Factory DAO Layer Socket Manager Hibernate MySQL Client Handler Server Architecture: Server Side SessionKey Communication: 1 Signup/Login (id + pw) 2 SessionKey (or fail) Client 1 3 SessionKey + new request 4 Respond (or sessionout) SessionKey is a randomly generated character string of length 32 (62^32) Client k Architecture: Server Side Socket Manager: public class SocketManager { public SocketManager(SessionFactory sessionFactory) { /** constructor code **/ } public void createSocketManager(Scanner inputPortNumber) throws IOException, JSONException { int cur_port = inputPortNumber.nextInt(); serverSocket = new ServerSocket(cur_port); while (true) { try { System.out.println("S: Receiving..."); socket = serverSocket.accept(); ClientHandler clientHandler = new ClientHandler(socket, sessionFactory); // This thread will do the talking Thread t = new Thread(clientHandler); t.start(); } catch (IOException ioe) { System.out.println("IOException on socket listen: " + ioe); ioe.printStackTrace(); } } } ClientHandler Connects Network (socket) with database (sF) Architecture: Server Side Client Handler: public class ClientHandler implements Runnable { private Socket socket; private SessionFactory sessionFactory; public ClientHandler(Socket s, SessionFactory sessionFactory) { this.socket = s; this.sessionFactory = sessionFactory; } Context: Necessary Execution Environment public void run() { GeneralCommandFactory generalCommandFactory = new GeneralCommandFactory(); ExecutionContext context = new ExecutionContext(sessionFactory, new UserDAO(sessionFactory), new PlaceDAO(sessionFactory), new PlanDAO(sessionFactory)); try { OCP / DRY DataInputStream dis = new DataInputStream(socket.getInputStream()); JSONObject jsonObject = new JSONObject(dis.readUTF()); Principles // call exception to deal with illegal purpose command Command command = generalCommandFactory.create(jsonObject); DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); JSONObject response = command.execute(context); dos.writeUTF(response.toString()); } catch (IOException | JSONException | CommandException| NullPointerException e) { e.printStackTrace(); }} } Architecture: Server Side ExecutionContext public class ExecutionContext { private SessionFactory sessionFactory; private UserDAO userDAO; private PlaceDAO placeDAO; private PlanDAO planDAO; ….. /** Contains all execution requirement **/ } OCP Principle Architecture: Server Side Command Factory: (Factory Pattern & Singleton Pattern) public class GeneralCommandFactory { JSONObject jsonObject; /** * FACTORISE contains all concrete command factories */ private static final CommandFactory[] FACTORIES = { new LoginCommandFactory(), new SignUpCommandFactory(), new LogOutCommandFactory(), new AddVisitedPlaceCommandFactory(), new AddWishListPlaceCommandFactory(), newSearchPlaceCommandFactory(), new GetVisitedListCommandFactory(), new GetWishListCommandFactory(), new PersonalPageCommandFactory(), new ListPlaceDetailCommandFactory(), new ListFriendFactory(), new AddFriendFactory(), new DeleteFriendFactory(), new ShowCommonInterestUserFactory(), new UpdateUserInfoFactory(), new WritePlanFactory(), new PlanCommentFactory() }; Singleton private Map<String, CommandFactory> factoryMap; public GeneralCommandFactory() { this.factoryMap = new HashMap<>(); for (CommandFactory factory : FACTORIES) { Pattern this.factoryMap.put(factory.getCommandName(), factory); }} Architecture: Server Side Command Factory: (Factory Pattern & Singleto Pattern) /** * pass the json object to each concrete command factory * @param jsonObject * @return Command * @throws JSONException * @throws CommandException */ public Command create(JSONObject jsonObject) throws JSONException, CommandException { Command command = null; String commandName = jsonObject.getString(JsonKeys.PURPOSE); System.out.println(commandName); CommandFactory factory = this.factoryMap.get(commandName); if (factory == null) { // TODO: replace with an exception type that the caller can handle // (probably ProtocolException) System.out.println("void"); throw new CommandException("no such command exist"); } else { command = factory.makeCommand(jsonObject); return command; } }} Architecture: Server Side Session Command & Open Command: (Command Pattern) public abstract class SessionCommand implements Command { JSONObject j; public SessionCommand(JSONObject j) { this.j = j; } public boolean executeAuthenticated() throws CommandException, JSONException { /* Code to Anthenticate With SessionKey */ } } public interface OpenCommand extends Command { } Architecture: Server Side Hibernate: DAO Layer public class PlaceDAO { public PlaceDAO(SessionFactory sessionFactory) {} public void insertPlace(/* args */) {} public Place getPlace(/* args */) {} public String getPlaceImage(/* args */) {} public int getPlaceRate(/* args */) {} public void addToWishList(/* args */){} … } Architecture: Server Side Hibernate DAO Layer: Template Method public abstract class TransactionRoutine<P, R, X extends Exception> { ….. public abstract R executeWithinTransaction(Session session, Transaction transaction, P param) throws X; public R execute(P param) throws X { Session session = sessionFactory.getCurrentSession(); boolean startedTransaction = false; Transaction tx = THREAD_TX.get(); if (tx == null) { tx = session.beginTransaction(); THREAD_TX.set(tx); startedTransaction = true;} try { R r = executeWithinTransaction(session, tx, param); if (startedTransaction) { tx.commit(); THREAD_TX.set(null);} return r; } catch (Throwable t) { if (startedTransaction) { tx.rollback(); THREAD_TX.set(null); } throw t;} }}} Architecture: Server Side Hibernate DAO Layer: Template Method public class PlaceDAO { ….. public Place getPlace(String placeName) { TransactionRoutine<String, Place, RuntimeException> routine = new TransactionRoutine<String, Place, RuntimeException>(this.sessionFactory) { @Override public Place executeWithinTransaction(Session session, Transaction tx, String placeName) { Query query = session.createQuery("from Place where placename=:placename"); query.setParameter("placename", placeName); List<Place> list = query.list(); for (Place u : list) { return u;} the return null; Hollywood }}; Principle! return routine.execute(placeName); }} Architecture: Server Side Hibernate: ThreadLocal public class HibernateUtil { public static final ThreadLocal local = new ThreadLocal (); public static Session currentSession() throws HibernateException { Session session = (Session) local.get(); //open a new session if this thread has no session if(session == null) { ThreadLocal(): session = sessionFactory.openSession(); create a thread local local.set(session); variable } return session; } } get(): return the session / value of threadlocal variable Purpose: avoid multiple sessions in a single thread Architecture: Server Side Hibernate: In hibernate configuration file, <property name = “hbm2ddl.auto”> create </property> can clear the whole (previous) database But when there are changes in the foreign keys of one table (or related constraints), Hibernate may not delete them automatically. We then have to delete the database by hand and recreate it Pains & Gains A lot of time is spent struggling with Git But finally figured out how it works Writing our own sockets manager takes more time and codes We have more flexibility with our own socket manager Server side code is structurally refactored on Iteration 4 Finally got a clearly (hierarchically) structured server side and classes with efficient inheritance Pains & Gains Old Structure: PersonalPag e Command Command Factory DAO Layer Client Handler Login Command Socket Manager Hibernate Interface: Command MySQL Potential Conflict Among multiple Users Server Future Work Implement two extension features and use Facebook API Add more detailed controls on steps such as password strength and etc. Refine the java doc Further machine tests with tools such as Monkey Test Test app online and get User Experience feedbacks Future Work Extension Feature: Friend Recommendation Currently we are using a simple comparison method: for two arbitrary users, we use the number of shared visited places and wish-to-go places as a similarity score When the number of users get large, we could use some more efficient clustering algorithms such as k-means For place recommendation, we will use a similar logic with social network data: compared a person’s friends with the group of people who have been to a place as a recommendation score Thanks! Thanks to Professor Smith and all TAs for this excellent class! Thanks to Jed and Zach for your patience and help in solving the different problems we ran into! Thanks to Our team members, Group 14 is an awesome team!