here

advertisement
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!
Download