350 likes | 532 Views
X-Traveler. Sixiang Chen, Shuli Shi, Xiaoping Li, Yinan Zhao, Shaojie Chen Group 14. Contents. Overview Architecture : Client Side Architecture: Server Side Pains & Gains Future Work Demos. Overview. X-Traveler is a mobile application on the android platform
E N D
X-Traveler Sixiang Chen, Shuli Shi, Xiaoping Li, Yinan Zhao, Shaojie Chen Group 14
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 ApacheMysql Php Client Side Socket JVM JSONObject Hibernate ORM Database (MySQL) The Architecture:
Client Side: UI Views wishlist Personal Page Place Login visitedlist Main Page Signup Find Place City UI Pages:
Client Side: Model Each Activity corresponds to a page in the app public class LogInActivity extends Activity { TextViewtest; 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() ); jsonobj= req.getJSON(); new ConnectServerThread().start(); // Option: new Task().excute(); } });
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 { TextViewtest; 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() ); jsonobj= req.getJSON(); new ConnectServerThread().start(); // Option: new DownloadFileTask().excute(); } });
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 HttpUtilhttpUtil = new HttpUtil …… InputStreaminputStream = 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 networksocket = 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*/ SharedPreferencesinfos = 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 Session Command PersonalPage Command Factory General Command Factory Client Handler DAO Layer Login Command Factory Open Command Socket Manager Hibernate Interface: Command Interface: CommandFactory Server MySQL Overview:
Architecture: Server Side SessionKey is a randomly generated character string of length 32 (62^32) 1 Signup/Login (id + pw) Client 1 2 SessionKey (or fail) Server 3 SessionKey + new request 4 Respond (or sessionout) Database Client k SessionKey Communication:
Architecture: Server Side ClientHandler Connects Network (socket) with database (sF) Socket Manager: public class SocketManager { public SocketManager(SessionFactorysessionFactory) { /** constructor code **/ } public void createSocketManager(Scanner inputPortNumber) throws IOException, JSONException { intcur_port = inputPortNumber.nextInt(); serverSocket= new ServerSocket(cur_port); while (true) { try { System.out.println("S: Receiving..."); socket = serverSocket.accept(); ClientHandlerclientHandler = new ClientHandler(socket, sessionFactory); // This thread will do the talking Thread t = new Thread(clientHandler); t.start(); } catch (IOExceptionioe) { System.out.println("IOException on socket listen: " + ioe); ioe.printStackTrace(); } } }
Architecture: Server Side Context: Necessary Execution Environment OCP / DRY Principles Client Handler: public class ClientHandler implements Runnable { private Socket socket; private SessionFactorysessionFactory; public ClientHandler(Socket s, SessionFactorysessionFactory) { this.socket = s; this.sessionFactory = sessionFactory; } public void run() { GeneralCommandFactory generalCommandFactory = new GeneralCommandFactory(); ExecutionContext context = new ExecutionContext(sessionFactory, new UserDAO(sessionFactory), new PlaceDAO(sessionFactory), new PlanDAO(sessionFactory)); try { DataInputStream dis = new DataInputStream(socket.getInputStream()); JSONObject jsonObject = new JSONObject(dis.readUTF()); // 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 OCP Principle ExecutionContext public class ExecutionContext { private SessionFactorysessionFactory; private UserDAOuserDAO; private PlaceDAOplaceDAO; private PlanDAOplanDAO; ….. /** Contains all execution requirement **/ }
Architecture: Server Side Singleton Pattern 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() }; private Map<String, CommandFactory> factoryMap; public GeneralCommandFactory() { this.factoryMap = new HashMap<>(); for (CommandFactory factory : FACTORIES) { 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 Commandcreate(JSONObject jsonObject) throws JSONException, CommandException { Command command = null; String commandName = jsonObject.getString(JsonKeys.PURPOSE); System.out.println(commandName); CommandFactoryfactory = 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 booleanexecuteAuthenticated()throws CommandException, JSONException { /* Code to Anthenticate With SessionKey */ } } public interface OpenCommand extends Command { }
Architecture: Server Side Hibernate: DAO Layer public class PlaceDAO { public PlaceDAO(SessionFactorysessionFactory) {} public void insertPlace(/* args */) {} public Place getPlace(/* args */) {} public String getPlaceImage(/* args */) {} public intgetPlaceRate(/* args */) {} public voidaddToWishList(/* 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(); booleanstartedTransaction = 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 the Hollywood Principle! 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;} return null; }}; return routine.execute(placeName); }}
Architecture: Server Side ThreadLocal(): create a thread local variable get(): return the session / value of threadlocal variable Purpose: avoid multiple sessions in a single thread 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) { session = sessionFactory.openSession(); local.set(session); } return session; } }
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: PersonalPage Command Command Factory Client Handler DAO Layer Login Command Socket Manager Hibernate Interface: Command Potential Conflict Among multiple Users Server MySQL
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!