diff --git a/README.md b/README.md new file mode 100644 index 0000000..60d2c8b --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# WallPost + +A Java Swing application which allows to post articles on a remote "wall" + +Includes: + +* SignUp/SignIn +* Posting stories +* Reading stories +* Multi-threaded server +* State persistence + +Employs a custom protocol in communication ("FOLLOWS protocol" to exchange variables) + + + +> **Notice** +> This is just a demonstrative application +> It lacks basic functionalities as editing and upvoting, and relies on terrible storage and authentication, in fact it does not even use SSL or TLS +> Be aware of this + + + +* WallPost: The client +* WallPostServer: The server \ No newline at end of file diff --git a/WallPost/WallPost.iml b/WallPost/WallPost.iml new file mode 100644 index 0000000..c90834f --- /dev/null +++ b/WallPost/WallPost.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/WallPost/src/HeaderNotFoundException.java b/WallPost/src/HeaderNotFoundException.java new file mode 100644 index 0000000..07c7fa6 --- /dev/null +++ b/WallPost/src/HeaderNotFoundException.java @@ -0,0 +1,5 @@ +public class HeaderNotFoundException extends Exception{ + public HeaderNotFoundException(){ + super("Header not found"); + } +} diff --git a/WallPost/src/Post.java b/WallPost/src/Post.java new file mode 100644 index 0000000..e499584 --- /dev/null +++ b/WallPost/src/Post.java @@ -0,0 +1,35 @@ +public class Post { + public String getTitle() { + return title; + } + + public String getBody() { + return body; + } + + public String getAuthor() { + return author; + } + + public String getDate() { + return date; + } + + public int getId() { + return id; + } + + private String title, body,author, date; + private int id; + public void setBody(String body) { + this.body = body; + } + + public Post(String title, String body, String author, String date, int id){ + this.title=title; + this.body=body; + this.author=author; + this.date=date; + this.id=id; + } +} diff --git a/WallPost/src/PostList.java b/WallPost/src/PostList.java new file mode 100644 index 0000000..24bed3b --- /dev/null +++ b/WallPost/src/PostList.java @@ -0,0 +1,5 @@ +import java.util.ArrayList; + +public class PostList extends ArrayList{ + +} diff --git a/WallPost/src/ServerConnection.java b/WallPost/src/ServerConnection.java new file mode 100644 index 0000000..6a63b45 --- /dev/null +++ b/WallPost/src/ServerConnection.java @@ -0,0 +1,230 @@ +import javax.swing.*; +import java.io.*; +import java.lang.reflect.Array; +import java.net.Socket; +import java.rmi.ServerError; +import java.util.ArrayList; + +public class ServerConnection { + private Socket socket; + private InputStreamReader i; + private BufferedReader in; + private OutputStreamWriter osw; + private BufferedWriter bw; + private PrintWriter out; + private ArrayList headers=new ArrayList(); + private ArrayList values=new ArrayList(); + private JFrame window; + public ServerConnection(JFrame window) throws IOException { + this.window=window; + socket= new Socket("mascmt.ddns.net",70); + i= new InputStreamReader(socket.getInputStream()); + in = new BufferedReader(i); + osw= new OutputStreamWriter(socket.getOutputStream()); + bw =new BufferedWriter(osw); + out= new PrintWriter(bw, true); + out.println("HELLO"); + } + public ArrayList> sendData(String action) throws IOException, ServerErrorException {//lenght of Arralist[] is 2 as in headers, values + ArrayList> hv=new ArrayList>(); + hv.add(new ArrayList()); + hv.add(new ArrayList()); + out.println("HELLO AGAIN"); + out.println("ACTION " + action); + for (int c = 0; c < headers.size(); c++) { + out.println("FOLLOWS " + headers.get(c).replaceAll("FOLLOWS","follows").replaceAll("END","end")); + out.println(values.get(c).replaceAll("FOLLOWS","follows").replaceAll("END","end")); + } + out.println("END"); + String line="",temp=""; + if(in.readLine().equals("ERROR")){ + while(true){ + line=in.readLine(); + if(line.equals("END")){ + String title=in.readLine(); + String type=in.readLine(); + + if(in.readLine().equals("QUIT")){ + throw new ServerErrorException(title,temp,in.readLine(),true); + } + else{ + throw new ServerErrorException(title,temp,in.readLine(),false); + } + } + else{ + + temp+=line; + } + } + } + int flNum=0; + do{ + line=in.readLine(); + if(line.startsWith("FOLLOWS")){ + hv.get(0).add(line.split(" ",2)[1]); + if(flNum!=0){ + hv.get(1).add(temp); + } + temp=""; + flNum++; + } + else if(line.equals("END")){ + hv.get(1).add(temp); + } + else{ + temp+=line; + } + + }while (!line.equals("END")); + return hv; + } + public void init(){ + headers.clear(); + values.clear(); + } + public void bindParam(String h,String v){ + headers.add(h); + values.add(v); + } + + public ArrayList> handledGetData(String action){ + ArrayList> s=new ArrayList>(2); + try { + s=this.sendData(action); + } catch (IOException e) { + JOptionPane.showMessageDialog(window, + "Connection error", + "Could not connect to the server", + JOptionPane.ERROR_MESSAGE); + } catch (ServerErrorException e) { + handleServerErrorInGUI(e.title,e.message,e.errorType,e.mustQuit); + } + System.out.println(s.toString()); + return s; + } + + public void handleServerErrorInGUI(String title, String text, String type, boolean mustQuit){ + if(type.equals("INFO")){ + JOptionPane.showMessageDialog(this.window, title, text,JOptionPane.INFORMATION_MESSAGE); + + } + else if(type.equals("WARNING")){ + JOptionPane.showMessageDialog(this.window, + title, + text, + JOptionPane.WARNING_MESSAGE); + + + } + else if(type.equals("PLAIN")){ + JOptionPane.showMessageDialog(this.window, + title, + text, + JOptionPane.PLAIN_MESSAGE); + + + } + else{//ERROR + JOptionPane.showMessageDialog(this.window, + title, + text, + JOptionPane.ERROR_MESSAGE); + + } + if(mustQuit) { + System.exit(1); + } + } + public String valueByHeader(ArrayList> resp,String header) throws HeaderNotFoundException { + for(int i=0;i> li= this.handledGetData("signIn"); + try { + return this.valueByHeader(li,"status").trim().equals("OK"); + } catch (HeaderNotFoundException e) { + return false; + } + } + public boolean signUp(String username, String password){ + this.init(); + this.bindParam("username",username); + this.bindParam("password",password); + ArrayList> li= this.handledGetData("signUp"); + + try { + return this.valueByHeader(li,"status").trim().equals("OK"); + } catch (HeaderNotFoundException e) { + return false; + } + } + public PostList fetchPostsFromServer(){ + this.init(); + ArrayList> li =this.handledGetData("listPosts"); + PostList p= new PostList(); + int count=0; + for(int i=0; i> li=this.handledGetData("getPostBody"); + String body=""; + try { + body=this.valueByHeader(li,"body"); + } catch (HeaderNotFoundException e) { + body="An error occurred while loading the post"; + } + a.setBody(body); + list.set(index,a); + } + return a; + } + public void publishPost(String title,String body){ + this.init(); + this.bindParam("POST-Title",title); + this.bindParam("POST-Body",body); + this.handledGetData("sendPost"); + } + /* + NOT SUPPORTED BY SERVER + @Deprecated + public Post getPost(int Id) throws HeaderNotFoundException { + this.init(); + this.bindParam("POST-Id",String.valueOf(Id)); + ArrayList> li=this.handledGetData("getPost"); + Post p= new Post(this.valueByHeader(li,"POST-Title"),this.valueByHeader(li,"POST-Body"),this.valueByHeader(li,"POST-Author"),this.valueByHeader(li,"POST-Date"),Integer.parseInt(this.valueByHeader(li,"POST-Id"))); + return p; + } + + */ + +} diff --git a/WallPost/src/ServerErrorException.java b/WallPost/src/ServerErrorException.java new file mode 100644 index 0000000..596078a --- /dev/null +++ b/WallPost/src/ServerErrorException.java @@ -0,0 +1,13 @@ +import javax.swing.*; + +public class ServerErrorException extends Exception{ + public String message,errorType, title; + public boolean mustQuit; + public ServerErrorException(String title, String message, String errorType, boolean mustQuit){ + super("The server issued an error"); + this.message=message; + this.title=title; + this.errorType = errorType; + this.mustQuit=mustQuit; + } +} diff --git a/WallPost/src/Window.java b/WallPost/src/Window.java new file mode 100644 index 0000000..2a877d9 --- /dev/null +++ b/WallPost/src/Window.java @@ -0,0 +1,231 @@ +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.DefaultTableModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.io.IOException; + +public class Window extends JFrame implements ActionListener { + private JPanel auth, list, viewOne, mine; + private JLabel labelUsername, labelPassword, postHeader; + private JTextField username, titleField; + private JPasswordField password; + private JButton signIn, signUp, make, send, backList,backList2; + private JScrollPane TablePane, textViewPane, textEditPane,postHeaderPane; + private JTable table; + private JTextArea view, write; + private DefaultTableModel tb; + private ServerConnection server; + private PostList PL; + private Post CP; + public Window() throws IOException { + initComponents(); + server= new ServerConnection(this); + PL=null; + } + public void initComponents(){ + + auth=new JPanel(); + labelUsername= new JLabel("Username"); + labelPassword=new JLabel("Password"); + username = new JTextField(15); + password = new JPasswordField(15); + signIn=new JButton("Sign In"); + signUp=new JButton("Sign Up"); + auth.setLayout(new GridLayout(3,2)); + + + auth.add(labelUsername); + auth.add(username); + + auth.add(labelPassword); + auth.add(password); + + auth.add(signIn); + + auth.add(signUp); + + + list=new JPanel(); + Object[][] data = {}; + String[] columnNames = {"User","Title","Date"}; + tb=new DefaultTableModel(data,columnNames); + table = new JTable(tb); + TablePane= new JScrollPane(table); + make=new JButton("Write"); + + list.setLayout(new GridLayout(2,1)); + + list.add(TablePane); + list.add(make); + + send= new JButton("Send"); + + this.setLayout(new FlowLayout()); + //this.add(list,new GridBagConstraints()); + viewOne= new JPanel(); + view= new JTextArea(15,50); + backList2= new JButton("Back To list"); + textViewPane= new JScrollPane(view); + postHeader=new JLabel("Title, details"); + postHeaderPane=new JScrollPane(postHeader, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + viewOne.setLayout(new GridLayout(3,1)); + + viewOne.add(postHeaderPane); + viewOne.add(textViewPane); + viewOne.add(backList2); + + mine=new JPanel(); + titleField=new JTextField(15); + titleField.setForeground(Color.GRAY); + titleField.setText("Title"); + titleField.setForeground(Color.GRAY); + titleField.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + if (titleField.getText().equals("Title")) { + titleField.setText(""); + titleField.setForeground(Color.BLACK); + } + } + @Override + public void focusLost(FocusEvent e) { + if (titleField.getText().isEmpty()) { + titleField.setForeground(Color.GRAY); + titleField.setText("Title"); + } + } + }); + write=new JTextArea(15,50); + write.setForeground(Color.GRAY); + write.setText("Body"); + write.setForeground(Color.GRAY); + write.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + if (write.getText().equals("Body")) { + write.setText(""); + write.setForeground(Color.BLACK); + } + } + @Override + public void focusLost(FocusEvent e) { + if (write.getText().isEmpty()) { + write.setForeground(Color.GRAY); + write.setText("Body"); + } + } + }); + + textEditPane= new JScrollPane(write); + + backList= new JButton("Back To list"); + mine.setLayout(new GridLayout(4,1)); + mine.add(titleField); + mine.add(textEditPane); + mine.add(send); + mine.add(backList); + + + signIn.addActionListener(this); + signUp.addActionListener(this); + make.addActionListener(this); + backList.addActionListener(this); + backList2.addActionListener(this); + send.addActionListener(this); + + this.add(auth); + this.add(list); + this.add(viewOne); + this.add(mine); + list.setVisible(false); + viewOne.setVisible(false); + mine.setVisible(false); + //this.add(auth,new GridBagConstraints()); + table.getSelectionModel().addListSelectionListener(new ListSelectionListener(){ + public void valueChanged(ListSelectionEvent event) { + // do some actions here, for example + // print first column value from selected row + CP=server.fetchPostBodyIfEmpty(PL,table.getSelectedRow()); + if (CP!= null) { + + + postHeader.setText(CP.getTitle() + ", written by " + CP.getAuthor() + " at " + CP.getDate()); + view.setText(CP.getBody()); + list.setVisible(false); + viewOne.setVisible(true); + } + } + }); + } + public static void main(String args[]) throws IOException { + Window window= new Window(); + window.setSize(600,1000); + window.setTitle("WallPost"); + window.setVisible(true); + } + + public void updateTable(PostList a) { + tb.setRowCount(0);//Imposta il numero di righe a 0 eliminando tutte le righe presenti + for (int i = 0; i < a.size(); i++) { + Object[] row = {a.get(i).getAuthor(),a.get(i).getTitle(), a.get(i).getDate()};//Crea riga temporanea + tb.addRow(row);//aggiungi riga + } + } + @Override + public void actionPerformed(ActionEvent e) { + if(e.getSource().equals(signIn)){ + if(server.signIn(username.getText(),new String(password.getPassword()))) { + PL=server.fetchPostsFromServer(); + this.updateTable(PL); + auth.setVisible(false); + list.setVisible(true); + } + else{ + JOptionPane.showMessageDialog(this, + "Wrong credentials", + "Cannot sign in", + JOptionPane.WARNING_MESSAGE); + } + } + else if(e.getSource().equals(signUp)){ + if(server.signUp(username.getText(),new String(password.getPassword()))) { + updt(); + auth.setVisible(false); + list.setVisible(true); + } + else{ + JOptionPane.showMessageDialog(this, + "User Taken", + "Cannot sign up", + JOptionPane.WARNING_MESSAGE); + } + } + else if(e.getSource().equals(make)){ + list.setVisible(false); + mine.setVisible(true); + } + else if(e.getSource().equals(backList)||e.getSource().equals(backList2)){ + updt(); + viewOne.setVisible(false); + mine.setVisible(false); + list.setVisible(true); + } + else if(e.getSource().equals(send)){ + server.publishPost(titleField.getText(),write.getText()); + updt(); + mine.setVisible(false); + list.setVisible(true); + } + } + + public void updt(){ + PL=server.fetchPostsFromServer(); + this.updateTable(PL); + } +} + diff --git a/WallPostServer/WallPostServer.iml b/WallPostServer/WallPostServer.iml new file mode 100644 index 0000000..c90834f --- /dev/null +++ b/WallPostServer/WallPostServer.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/WallPostServer/out/artifacts/WallPostServer_jar/WallPostServer.jar b/WallPostServer/out/artifacts/WallPostServer_jar/WallPostServer.jar new file mode 100644 index 0000000..c251101 Binary files /dev/null and b/WallPostServer/out/artifacts/WallPostServer_jar/WallPostServer.jar differ diff --git a/WallPostServer/out/production/WallPostServer/META-INF/MANIFEST.MF b/WallPostServer/out/production/WallPostServer/META-INF/MANIFEST.MF new file mode 100644 index 0000000..836f72b --- /dev/null +++ b/WallPostServer/out/production/WallPostServer/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: WallPostServer + diff --git a/WallPostServer/src/HeaderNotFoundException.java b/WallPostServer/src/HeaderNotFoundException.java new file mode 100644 index 0000000..07c7fa6 --- /dev/null +++ b/WallPostServer/src/HeaderNotFoundException.java @@ -0,0 +1,5 @@ +public class HeaderNotFoundException extends Exception{ + public HeaderNotFoundException(){ + super("Header not found"); + } +} diff --git a/WallPostServer/src/META-INF/MANIFEST.MF b/WallPostServer/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..836f72b --- /dev/null +++ b/WallPostServer/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: WallPostServer + diff --git a/WallPostServer/src/PermanentSorage.java b/WallPostServer/src/PermanentSorage.java new file mode 100644 index 0000000..0a20b1e --- /dev/null +++ b/WallPostServer/src/PermanentSorage.java @@ -0,0 +1,73 @@ +import java.io.*; +import java.net.Socket; +import java.util.ArrayList; + +public class PermanentSorage extends Thread{ + public UserList getUList() { + return UList; + } + + public PostList getPList() { + return PList; + } + + private UserList UList; + private PostList PList; + private FileInputStream FIn=null; + private ObjectInputStream ObjIn=null; + private boolean firstTime; + public PermanentSorage(UserList UList, PostList PList) { + this.UList=UList; + this.PList=PList; + try { + File f = new File("SAVE.DAT"); + if(!f.exists()) { + this.firstTime= f.createNewFile(); + } + else { + FIn = new FileInputStream(f); + ObjIn = new ObjectInputStream(FIn); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + public void loadFromDisk(){ + try { + if(!firstTime) { + SaveContainer c = (SaveContainer) ObjIn.readObject(); + this.UList = c.getUl(); + this.PList = c.getPl(); + ObjIn.close(); + FIn.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + public void run(){ + while(true) { + try { + System.out.println("STARTING SAVE"); + FileOutputStream FOut= new FileOutputStream(new File("SAVE.DAT")); + ObjectOutputStream ObjOut= new ObjectOutputStream(FOut); + ObjOut.writeObject(new SaveContainer(UList,PList)); + FOut.close(); + Thread.sleep(60000);//10 sec + System.out.println("END SAVE"); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + } + } +} diff --git a/WallPostServer/src/Post.java b/WallPostServer/src/Post.java new file mode 100644 index 0000000..8b6e65f --- /dev/null +++ b/WallPostServer/src/Post.java @@ -0,0 +1,37 @@ +import java.io.Serializable; + +public class Post implements Serializable { + public String getTitle() { + return title; + } + + public String getBody() { + return body; + } + + public String getAuthor() { + return author; + } + + public String getDate() { + return date; + } + + public int getId() { + return id; + } + + private String title, body,author, date; + private int id; + public void setBody(String body) { + this.body = body; + } + + public Post(String title, String body, String author, String date, int id){ + this.title=title; + this.body=body; + this.author=author; + this.date=date; + this.id=id; + } +} diff --git a/WallPostServer/src/PostList.java b/WallPostServer/src/PostList.java new file mode 100644 index 0000000..34b1453 --- /dev/null +++ b/WallPostServer/src/PostList.java @@ -0,0 +1,18 @@ +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Random; + +public class PostList extends ArrayList implements Serializable { + public int getNewId(){ + int u; + Random r = new Random(); + u=r.nextInt(); + for(int i=0;i headers, values; + public ServerThread(Socket clientSocket,UserList UList,PostList PList) { + this.socket = clientSocket; + this.UList=UList; + this.PList=PList; + } + /* + + ERROR + TITLE + END + TEXT + END + END + END + + + ERROR + TITLE + END + TEXT + END + QUIT + QUIT + + */ + private String sanitize(String a){ + return a.replaceAll("FOLLOWS","follows").replaceAll("END","END"); + } + private synchronized UserList accessUserList(){ + return this.UList; + } + private synchronized PostList accessPostList(){ + return this.PList; + } + public String valueByHeader(String header) throws HeaderNotFoundException { + for(int i=0;i(); + values= new ArrayList(); + while (true) { + try { + line = in.readLine(); + if ((line == null) || line.equalsIgnoreCase("QUIT")) { + socket.close(); + return; + } else { + if(line.startsWith("ACTION")) { + headers.clear(); + values.clear(); + action=line.split(" ", 2)[1]; + while(!line.equals("END")){ + if(line.startsWith("FOLLOWS")){ + headers.add(line.split(" ", 2)[1]); + } + else{ + if(!line.startsWith("ACTION")) { + values.add(line); + } + } + line=in.readLine(); + } + } + if(line.equals("END")){ + if(action.equals("signIn")){ + UserList u=accessUserList(); + boolean access= u.signIn(new User(valueByHeader("username"),valueByHeader("password"))); + out.writeBytes("OK\n"); + out.writeBytes("FOLLOWS status\n"); + if(access){ + out.writeBytes("OK\n"); + out.writeBytes("END\n"); + name=valueByHeader("username"); + } + else{ + out.writeBytes("NO\n"); + out.writeBytes("END\n"); + + } + } + else if(action.equals("signUp")){ + UserList u=accessUserList(); + boolean access= u.signUp(new User(valueByHeader("username"),valueByHeader("password"))); + out.writeBytes("OK\n"); + out.writeBytes("FOLLOWS status\n"); + if(access){ + out.writeBytes("OK\n"); + out.writeBytes("END\n"); + name=valueByHeader("username"); + } + else{ + out.writeBytes("NO\n"); + out.writeBytes("END\n"); + + } + } + else if(name.equals("DEFAULT")){ + out.writeBytes("ERROR\n" + + "Authentication Error\n" + + "END\n" + + "You must log in" + + "END\n" + + "QUIT\n" + + "QUIT\n"); + } + else if(action.equals("listPosts")){ + + PostList p= accessPostList(); + out.writeBytes("OK\n"); + for(int i=0;i implements Serializable{ + public boolean signUp(User a){ + for(int i=0;i