From a623c22f2e9f08464a8f85e98545476df3146f47 Mon Sep 17 00:00:00 2001 From: Harald Wolff Date: Sun, 24 Aug 2014 13:05:50 +0200 Subject: [PATCH 1/5] org.hwo.io.StreamReader(): Neue Eigenimplementation --- src/org/hwo/io/StreamReader.java | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/org/hwo/io/StreamReader.java diff --git a/src/org/hwo/io/StreamReader.java b/src/org/hwo/io/StreamReader.java new file mode 100644 index 0000000..e0f3395 --- /dev/null +++ b/src/org/hwo/io/StreamReader.java @@ -0,0 +1,41 @@ +package org.hwo.io; + +import java.io.IOException; +import java.io.InputStream; + +public class StreamReader { + + private InputStream stream; + + public StreamReader(InputStream stream) + { + this.stream = stream; + } + + public int read(byte[] buffer,int offset,int len) throws IOException + { + return this.stream.read(buffer, offset, len); + } + + public String readLine() throws IOException + { + StringBuilder builder = new StringBuilder(); + while (true) + { + int ch = this.stream.read(); + + if ((ch == -1)||(ch == '\n')) + return builder.toString(); + + if (ch >= 32) + { + builder.append((char)ch); + } + } + } + + + + + +} From 0fa6e912daa303cdd2db3aee9b649eec9a9dfc78 Mon Sep 17 00:00:00 2001 From: Harald Wolff Date: Sun, 24 Aug 2014 13:07:11 +0200 Subject: [PATCH 2/5] HTTP Server Framework Updates --- src/org/hwo/net/ServerObject.java | 5 +- src/org/hwo/net/http/HttpCookie.java | 116 ++++++++++ src/org/hwo/net/http/HttpException.java | 18 ++ src/org/hwo/net/http/HttpServer.java | 32 ++- .../hwo/net/http/HttpServerConnection.java | 10 +- src/org/hwo/net/http/HttpServerRequest.java | 213 ++++++++++++++---- src/org/hwo/net/http/MessageHeaders.java | 107 +++++++++ src/org/hwo/net/http/SessionTracker.java | 37 +++ .../requesthandler/ServerObjectHandler.java | 18 +- src/org/hwo/sessions/Session.java | 36 +++ src/org/hwo/sessions/SessionManager.java | 52 +++++ 11 files changed, 588 insertions(+), 56 deletions(-) create mode 100644 src/org/hwo/net/http/HttpCookie.java create mode 100644 src/org/hwo/net/http/HttpException.java create mode 100644 src/org/hwo/net/http/MessageHeaders.java create mode 100644 src/org/hwo/net/http/SessionTracker.java create mode 100644 src/org/hwo/sessions/Session.java create mode 100644 src/org/hwo/sessions/SessionManager.java diff --git a/src/org/hwo/net/ServerObject.java b/src/org/hwo/net/ServerObject.java index ea42708..ab8b1f1 100644 --- a/src/org/hwo/net/ServerObject.java +++ b/src/org/hwo/net/ServerObject.java @@ -2,6 +2,7 @@ package org.hwo.net; import java.io.IOException; +import org.hwo.net.http.HttpException; import org.hwo.net.serverobjects.ServerObjectRequest; public interface ServerObject { @@ -13,7 +14,7 @@ public interface ServerObject { public void addNamedChild(String childName,ServerObject serverObject); - public void climb(ServerObjectRequest request) throws IOException; - public void request(ServerObjectRequest request) throws IOException; + public void climb(ServerObjectRequest request) throws IOException, HttpException; + public void request(ServerObjectRequest request) throws IOException, HttpException; } diff --git a/src/org/hwo/net/http/HttpCookie.java b/src/org/hwo/net/http/HttpCookie.java new file mode 100644 index 0000000..1aabc2d --- /dev/null +++ b/src/org/hwo/net/http/HttpCookie.java @@ -0,0 +1,116 @@ +package org.hwo.net.http; + +import java.io.IOException; +import java.util.LinkedList; + +import org.hwo.ByteArrayHexlifier; + +public class HttpCookie { + + private String name; + private String content; + private String path; + private Integer maxAge; + + static public HttpCookie[] readCookies(HttpServerRequest request) + { + LinkedList cookies = new LinkedList(); + + String cookiesource = request.getRequestHeader("Cookie"); + + System.err.println("Cookie: "+cookiesource); + + if (cookiesource == null) + return new HttpCookie[0]; + + for (String cookieav: cookiesource.split(";")) + { + int p = cookieav.indexOf('='); + if (p > 0) + { + String cn,cv; + cn = cookieav.substring(0,p); + cv = cookieav.substring(p+1); + + HttpCookie hc = new HttpCookie(); + hc.setName(cn); + hc.setContent(new String(ByteArrayHexlifier.stringToByteArray(cv))); + cookies.add(hc); + + } + } + + return cookies.toArray(new HttpCookie[0]); + } + + public HttpCookie() + { + + } + + public void sendCookie(HttpServerRequest request) + { + String hexname,hexcontent; + StringBuilder cookievalue; + + hexname = name; // ByteArrayHexlifier.byteArrayToString(name.getBytes()); + hexcontent = ByteArrayHexlifier.byteArrayToString(getContent().getBytes()); + + cookievalue = new StringBuilder(); + cookievalue.append(String.format("%s=%s;",hexname,hexcontent)); + + if (path != null) + { + cookievalue.append(String.format(" Path=\"%s\";",this.path)); + } + + if (maxAge != null) + { + cookievalue.append(String.format(" Max-Age=\"%d\";",this.maxAge)); + } + + try + { + request.connection.getBufferedWriter().write(String.format("Set-Cookie: %s", cookievalue.toString())); + request.connection.getBufferedWriter().newLine(); + System.err.println(String.format("Set-Cookie: %s", cookievalue.toString())); + } catch (IOException io) + { + System.err.println("HttpCookie.sendCookie(): " + io.toString()); + } + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Integer getMaxAge() { + return maxAge; + } + + public void setMaxAge(Integer maxAge) { + this.maxAge = maxAge; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + +} diff --git a/src/org/hwo/net/http/HttpException.java b/src/org/hwo/net/http/HttpException.java new file mode 100644 index 0000000..07a465d --- /dev/null +++ b/src/org/hwo/net/http/HttpException.java @@ -0,0 +1,18 @@ +package org.hwo.net.http; + +public class HttpException extends Exception { + + private int code; + + public HttpException(int code) + { + this.code = code; + } + + public Integer getCode() + { + return this.code; + } + + +} diff --git a/src/org/hwo/net/http/HttpServer.java b/src/org/hwo/net/http/HttpServer.java index 729be88..8ceaff4 100644 --- a/src/org/hwo/net/http/HttpServer.java +++ b/src/org/hwo/net/http/HttpServer.java @@ -6,7 +6,9 @@ import java.net.ServerSocket; import java.net.Socket; import org.hwo.net.requesthandler.ServerObjectHandler; +import org.hwo.net.serverobjects.DAVCollectionSO; import org.hwo.net.serverobjects.VirtualRootObject; +import org.hwo.sessions.SessionManager; public class HttpServer extends Thread { @@ -16,21 +18,25 @@ public class HttpServer extends Thread { private HttpServerConnectionFactory connectionFactory; private HttpServerRequestFactory requestFactory; private HttpServerRequestHandler requestHandler; - + + private SessionManager sessionManager; + private SessionTracker sessionTracker; + public HttpServer(int port) { this.port = port; this.connectionFactory = new HttpServerConnection.Factory(); this.requestFactory = new HttpServerRequest.Factory(); - this.requestHandler = new HttpServerRequestHandler() { - @Override public void doRequest(HttpServerRequest httpRequest) throws IOException{ httpRequest.setResponseHeader("Content-Type", "text/plain"); httpRequest.getResponseWriter().write("This Page has no other content than this text."); } }; + + this.sessionManager = new SessionManager(); + this.sessionTracker = new SessionTracker(); } public HttpServerConnectionFactory getConnectionFactory() @@ -83,8 +89,26 @@ public class HttpServer extends Thread { HttpServer httpServer = new HttpServer(8080); ServerObjectHandler soh = new ServerObjectHandler(); - httpServer.setRequestHandler(soh); + DAVCollectionSO dav = new DAVCollectionSO(); + soh.getRootObject().addNamedChild("dav", dav); + httpServer.setRequestHandler(soh); httpServer.start(); } + + public SessionManager getSessionManager() { + return sessionManager; + } + + public void setSessionManager(SessionManager sessionManager) { + this.sessionManager = sessionManager; + } + + public SessionTracker getSessionTracker() { + return sessionTracker; + } + + public void setSessionTracker(SessionTracker sessionTracker) { + this.sessionTracker = sessionTracker; + } } diff --git a/src/org/hwo/net/http/HttpServerConnection.java b/src/org/hwo/net/http/HttpServerConnection.java index 386cb6b..2cf73dc 100644 --- a/src/org/hwo/net/http/HttpServerConnection.java +++ b/src/org/hwo/net/http/HttpServerConnection.java @@ -8,6 +8,8 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; +import org.hwo.io.StreamReader; + public class HttpServerConnection extends Thread { public static class Factory implements HttpServerConnectionFactory @@ -22,14 +24,14 @@ public class HttpServerConnection extends Thread { private HttpServer httpServer; private Socket clientSocket; - private BufferedReader bufferedReader; + private StreamReader streamReader; private BufferedWriter bufferedWriter; public HttpServerConnection(HttpServer httpServer,Socket clientSocket) throws IOException { this.httpServer = httpServer; this.clientSocket = clientSocket; - this.bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + this.streamReader = new StreamReader(clientSocket.getInputStream()); this.bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream())); } @@ -43,9 +45,9 @@ public class HttpServerConnection extends Thread { return clientSocket; } - public BufferedReader getBufferedReader() + public StreamReader getStreamReader() { - return bufferedReader; + return streamReader; } public BufferedWriter getBufferedWriter() diff --git a/src/org/hwo/net/http/HttpServerRequest.java b/src/org/hwo/net/http/HttpServerRequest.java index 771eed5..2acf147 100644 --- a/src/org/hwo/net/http/HttpServerRequest.java +++ b/src/org/hwo/net/http/HttpServerRequest.java @@ -1,14 +1,26 @@ package org.hwo.net.http; import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; +import java.util.Hashtable; +import java.util.List; import java.util.Properties; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.hwo.sessions.Session; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + public class HttpServerRequest { public static class Factory implements HttpServerRequestFactory @@ -26,11 +38,18 @@ public class HttpServerRequest { String requestProtocol; HttpRequestURI requestURI; + MessageHeaders + requestHeaders; + Hashtable + requestCookies; - Properties header; + byte[] requestContent; + Document + xmlRequest; - Integer resultCode; - Properties responseHeader; + private Integer resultCode; + MessageHeaders + responseHeaders; byte[] responseBody; ByteArrayOutputStream @@ -40,11 +59,18 @@ public class HttpServerRequest { boolean responseSent; + Session session; + + List responseCookies; + + public HttpServerRequest(HttpServerConnection httpServerConnection) throws IOException { this.connection = httpServerConnection; - this.header = new Properties(); + this.requestHeaders = new MessageHeaders(); + this.requestCookies = new Hashtable(); + this.responseCookies = new ArrayList(); readRequest(); readHeader(); @@ -52,10 +78,81 @@ public class HttpServerRequest { requestURI.decode(); resultCode = 400; - responseHeader = new Properties(); + responseHeaders = new MessageHeaders(); responseOutputStream = new ByteArrayOutputStream(); responseBufferedWriter = new BufferedWriter(new OutputStreamWriter(responseOutputStream)); + readCookies(); + + attachSession(); + + readContent(); + + if ((getRequestHeader("Content-Type") != null) && getRequestHeader("Content-Type").equals("text/xml")) + readXML(); + + } + + private void readCookies() + { + HttpCookie[] cookies = HttpCookie.readCookies(this); + + for (HttpCookie c:cookies) + requestCookies.put(c.getName(), c); + } + + private void attachSession() + { + SessionTracker tracker = connection.getHttpServer().getSessionTracker(); + if (tracker != null) + { + String sid = tracker.retrieveSessionID(this); + if (sid != null) + session = connection.getHttpServer().getSessionManager().getSession(sid); + + if (session == null) + { + session = connection.getHttpServer().getSessionManager().createSession(); + connection.getHttpServer().getSessionTracker().implantSession(this,session); + } + } + } + + private void readContent() throws IOException + { + String cl = getRequestHeader("Content-Length"); + if (cl != null) + { + int len = Integer.decode(cl); + requestContent = new byte[ len ]; + connection.getStreamReader().read(requestContent, 0, len); + } + } + + private void readXML() + { + DocumentBuilder builder; + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + builder = dbf.newDocumentBuilder(); + this.xmlRequest = builder.parse(new ByteArrayInputStream(requestContent)); + this.xmlRequest.normalize(); + } catch (ParserConfigurationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException ex) + { + ex.printStackTrace(); + } catch (SAXException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public Session getSession() + { + return session; } public boolean isResponseSent() @@ -65,7 +162,13 @@ public class HttpServerRequest { private void readRequest() throws IOException { - this.requestLine = connection.getBufferedReader().readLine(); + do + { + this.requestLine = connection.getStreamReader().readLine(); + if (this.requestLine == null) + throw new IOException("0byte read()"); + } while (this.requestLine.length() == 0); + String[] tokens = this.requestLine.split(" ",3); requestMethod = tokens[0]; if (tokens.length > 1) @@ -77,34 +180,7 @@ public class HttpServerRequest { private void readHeader() throws IOException { - String line; - String headerName = null; - StringBuilder headerValue = new StringBuilder(); - - while (true) - { - line = connection.getBufferedReader().readLine(); - if ((line.length() == 0) || (line.charAt(0) > 32)) - { - if (headerName != null) - { - header.setProperty(headerName, headerValue.toString()); - } - if (line.length() > 0) - { - headerName = line.substring(0,line.indexOf(':')); - headerValue = new StringBuilder(); - headerValue.append(line.substring(line.indexOf(':')+1).trim()); - } else - return; - } else - { - if (headerName != null) - headerValue.append(line.trim()); - } - - - } + requestHeaders.read(connection.getStreamReader()); } public HttpRequestURI getRequestURI() @@ -114,9 +190,38 @@ public class HttpServerRequest { public String getRequestHeader(String headerName) { - return header.getProperty(headerName); + return requestHeaders.getHeaderValue(headerName); } + public String getRequestMethod() + { + return requestMethod; + } + + public byte[] getRequestContent() + { + return requestContent; + } + + public Document getXMLRequest() + { + return this.xmlRequest; + } + + public HttpCookie[] getRequestCookies() + { + return this.requestCookies.keySet().toArray(new HttpCookie[0]); + } + + public HttpCookie getRequestCookie(String cookieName) + { + return requestCookies.get(cookieName); + } + + public void addCookie(HttpCookie cookie) + { + responseCookies.add(cookie); + } public OutputStream getResponseOutputStream() { @@ -127,11 +232,20 @@ public class HttpServerRequest { return responseBufferedWriter; } - public void setResponseHeader(String headerName,String headerValue) + public MessageHeaders getRepsonseHeaders() { - responseHeader.setProperty(headerName, headerValue); + return responseHeaders; } + public void setResponseHeader(String headerName,String headerValue) + { + responseHeaders.setHeaderValue(headerName, headerValue); + } + + public void addResponseHeader(String headerName,String headerValue) + { + responseHeaders.addHeaderValue(headerName, headerValue); + } public void sendResponse(int code,byte[] responseBody) throws IOException { @@ -151,9 +265,9 @@ public class HttpServerRequest { { if (responseSent) throw new IOException("The Response has already been sent."); - + responseSent = true; - + if (responseBody == null) { responseBufferedWriter.flush(); @@ -164,25 +278,34 @@ public class HttpServerRequest { responseBody = new byte[0]; } - responseHeader.setProperty("Content-Length", String.format("%d", responseBody.length)); + setResponseHeader("Content-Length", String.format("%d", responseBody.length)); String protocol = "HTTP/1.0"; if (requestProtocol != null) protocol = requestProtocol; connection.getBufferedWriter().write(String.format("%s %d\r\n", protocol, resultCode)); - - Enumeration headerEnum = responseHeader.keys(); - while (headerEnum.hasMoreElements()) + for (String headerName: responseHeaders.getHeaderNames()) { - String headerName = (String)headerEnum.nextElement(); - connection.getBufferedWriter().write(String.format("%s: %s\r\n", headerName, responseHeader.getProperty(headerName))); + String headerValue = responseHeaders.getHeaderValue(headerName); + connection.getBufferedWriter().write(String.format("%s: %s\r\n", headerName, headerValue)); } + for (HttpCookie cookie: responseCookies) + cookie.sendCookie(this); + connection.getBufferedWriter().write("\r\n"); connection.getBufferedWriter().flush(); connection.getClientSocket().getOutputStream().write(responseBody); } + + public Integer getResultCode() { + return resultCode; + } + + public void setResultCode(Integer resultCode) { + this.resultCode = resultCode; + } } diff --git a/src/org/hwo/net/http/MessageHeaders.java b/src/org/hwo/net/http/MessageHeaders.java new file mode 100644 index 0000000..7ce05ef --- /dev/null +++ b/src/org/hwo/net/http/MessageHeaders.java @@ -0,0 +1,107 @@ +package org.hwo.net.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; + +import org.hwo.StringHelper; +import org.hwo.io.StreamReader; + +public class MessageHeaders { + + private Hashtable> headers; + + public MessageHeaders() + { + headers = new Hashtable>(); + } + + public void read(StreamReader reader) throws IOException + { + String line; + String headerName = null; + List values = new ArrayList(); + + while (true) + { + line = reader.readLine(); + if ((line == null) || (line.length() == 0)) + { + break; + } else if (line.charAt(0) <= 32) + { + String stripped = line.trim(); + if (stripped.length() > 0) + { + values.add(stripped); + } + } else + { + int p = line.indexOf(':'); + headerName = line.substring(0,p); + values = getHeaderValueList(headerName); + values.add(line.substring(p+1).trim()); + } + } + } + + private List getHeaderValueList(String headerName) + { + List values; + + if (!headers.containsKey(headerName)) + { + values = new ArrayList(); + headers.put(headerName, values); + System.err.println("New Header: " + headerName); + } else + { + values = headers.get(headerName); + System.err.println("Appending Header: " + headerName); + } + return values; + } + + public void addHeader(String headerName,String headerValue) + { + } + + public String getHeaderValue(String headerName) + { + String[] values = getHeaderValues(headerName); + if (values.length > 0) + return StringHelper.join(values, " "); + return null; + } + + public String[] getHeaderValues(String headerName) + { + if (headers.containsKey(headerName)) + { + return headers.get(headerName).toArray(new String[0]); + } + return new String[0]; + } + + public String[] getHeaderNames() + { + return headers.keySet().toArray(new String[0]); + } + + public void setHeaderValue(String headerName,String headerValue) + { + List values = new ArrayList(); + values.add(headerValue); + headers.put(headerName, values); + } + + public void addHeaderValue(String headerName,String headerValue) + { + getHeaderValueList(headerName).add(headerValue); + } + + +} diff --git a/src/org/hwo/net/http/SessionTracker.java b/src/org/hwo/net/http/SessionTracker.java new file mode 100644 index 0000000..50e25c4 --- /dev/null +++ b/src/org/hwo/net/http/SessionTracker.java @@ -0,0 +1,37 @@ +package org.hwo.net.http; + +import org.hwo.sessions.Session; + +public class SessionTracker { + + + public SessionTracker() + { + } + + public String retrieveSessionID(HttpServerRequest request) + { + HttpCookie sidcookie = request.getRequestCookie("hwo.sid"); + if (sidcookie != null) + { + System.err.println("Session found: " + sidcookie.getContent()); + return sidcookie.getContent(); + } + return null; + } + + public void implantSession(HttpServerRequest request,Session session) + { + HttpCookie cookie = new HttpCookie(); + + cookie.setName("hwo.sid"); + cookie.setContent(session.getSessionID()); + request.addCookie(cookie); + + System.err.println("Session Implant: " + session.getSessionID()); + } + + + + +} diff --git a/src/org/hwo/net/requesthandler/ServerObjectHandler.java b/src/org/hwo/net/requesthandler/ServerObjectHandler.java index 820e7fc..8109b7c 100644 --- a/src/org/hwo/net/requesthandler/ServerObjectHandler.java +++ b/src/org/hwo/net/requesthandler/ServerObjectHandler.java @@ -3,9 +3,13 @@ package org.hwo.net.requesthandler; import java.io.IOException; import java.util.HashMap; +import javax.xml.ws.http.HTTPException; + import org.hwo.net.ServerObject; +import org.hwo.net.http.HttpException; import org.hwo.net.http.HttpServerRequest; import org.hwo.net.http.HttpServerRequestHandler; +import org.hwo.net.serverobjects.ServerObjectNotFoundException; import org.hwo.net.serverobjects.ServerObjectRequest; import org.hwo.net.serverobjects.VirtualRootObject; @@ -38,7 +42,19 @@ public class ServerObjectHandler implements HttpServerRequestHandler{ public void doRequest(HttpServerRequest httpRequest) throws IOException { ServerObjectRequest sor = new ServerObjectRequest(httpRequest); - rootObject.climb(sor); + try + { + rootObject.climb(sor); + } catch (HttpException httpex) + { + try + { + getErrorObject(httpex.getCode()).request(sor); + } catch (Exception ex) + { + + } + } } diff --git a/src/org/hwo/sessions/Session.java b/src/org/hwo/sessions/Session.java new file mode 100644 index 0000000..ce314d5 --- /dev/null +++ b/src/org/hwo/sessions/Session.java @@ -0,0 +1,36 @@ +package org.hwo.sessions; + +import java.util.Hashtable; + +public class Session { + + private String sessionID; + private Hashtable sessionVars; + + public Session() + { + this.sessionID = null; + this.sessionVars = new Hashtable(); + } + public Session(String sid) + { + this.sessionID = sid; + this.sessionVars = new Hashtable(); + } + + public String getSessionID() + { + return this.sessionID; + } + + public Object getObject(String name) + { + return this.sessionVars.get(name); + } + public void setObject(String name,Object o) + { + this.sessionVars.put(name, o); + } + + +} diff --git a/src/org/hwo/sessions/SessionManager.java b/src/org/hwo/sessions/SessionManager.java new file mode 100644 index 0000000..2a52a6e --- /dev/null +++ b/src/org/hwo/sessions/SessionManager.java @@ -0,0 +1,52 @@ +package org.hwo.sessions; + +import java.util.Hashtable; +import java.util.UUID; + +public class SessionManager { + + private Hashtable loadedSessions; + + private ThreadLocal threadSession; + + public SessionManager() + { + loadedSessions = new Hashtable(); + threadSession = new ThreadLocal(); + } + + synchronized private String createUniqueSessionID() + { + String sid; + do + { + UUID uuid = UUID.randomUUID(); + sid = String.format("%s%s", Long.toHexString(uuid.getLeastSignificantBits()),Long.toHexString(uuid.getMostSignificantBits())); + } while (loadedSessions.containsKey(sid)); + return sid; + } + + synchronized public Session createSession() + { + Session s = new Session(createUniqueSessionID()); + this.loadedSessions.put(s.getSessionID(), s); + return s; + } + + public Session getDefaultSession() + { + return threadSession.get(); + } + + public Session getSession(String sid) + { + return this.loadedSessions.get(sid); + } + + public void associateDefaultSession(Session s) + { + this.threadSession.set(s); + } + + +} From e271583f3e8a90f0ffe1a41710ec6011f2fd2ca9 Mon Sep 17 00:00:00 2001 From: Harald Wolff Date: Sun, 24 Aug 2014 13:07:27 +0200 Subject: [PATCH 3/5] DAV ServerObjects updates --- .../net/serverobjects/DAVCollectionSO.java | 116 +++++++++++++ .../net/serverobjects/dav/DAVCollection.java | 53 ++++++ .../serverobjects/dav/DAVDeadPropertyNS.java | 46 ++++++ .../net/serverobjects/dav/DAVProperty.java | 43 +++++ .../serverobjects/dav/DAVPropertyDAVNS.java | 29 ++++ .../serverobjects/dav/DAVPropertyName.java | 52 ++++++ .../dav/DAVPropertyNameList.java | 23 +++ .../dav/DAVPropertyNamespace.java | 40 +++++ .../net/serverobjects/dav/DAVResource.java | 153 ++++++++++++++++++ 9 files changed, 555 insertions(+) create mode 100644 src/org/hwo/net/serverobjects/DAVCollectionSO.java create mode 100644 src/org/hwo/net/serverobjects/dav/DAVCollection.java create mode 100644 src/org/hwo/net/serverobjects/dav/DAVDeadPropertyNS.java create mode 100644 src/org/hwo/net/serverobjects/dav/DAVProperty.java create mode 100644 src/org/hwo/net/serverobjects/dav/DAVPropertyDAVNS.java create mode 100644 src/org/hwo/net/serverobjects/dav/DAVPropertyName.java create mode 100644 src/org/hwo/net/serverobjects/dav/DAVPropertyNameList.java create mode 100644 src/org/hwo/net/serverobjects/dav/DAVPropertyNamespace.java create mode 100644 src/org/hwo/net/serverobjects/dav/DAVResource.java diff --git a/src/org/hwo/net/serverobjects/DAVCollectionSO.java b/src/org/hwo/net/serverobjects/DAVCollectionSO.java new file mode 100644 index 0000000..a0f20cd --- /dev/null +++ b/src/org/hwo/net/serverobjects/DAVCollectionSO.java @@ -0,0 +1,116 @@ +package org.hwo.net.serverobjects; + +import java.io.IOException; + +import javax.xml.ws.http.HTTPException; + +import org.hwo.net.http.HttpException; +import org.hwo.net.serverobjects.dav.DAVCollection; +import org.hwo.net.serverobjects.dav.DAVPropertyName; +import org.hwo.net.serverobjects.dav.DAVPropertyNameList; +import org.hwo.net.serverobjects.dav.DAVResource; +import org.hwo.xml.NodeListIterator; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class DAVCollectionSO extends AbstractServerObject{ + + class RootCollection extends DAVCollection + { + + public RootCollection(String name) { + super(name); + } + + @Override + protected String constructURI() { + return ""; + } + @Override + public DAVResource getParent() { + return null; + } + + } + + + private DAVCollection rootCollection; + + public DAVCollectionSO() + { + rootCollection = new RootCollection(""); + } + + @Override + public void request(ServerObjectRequest request) throws IOException, HttpException + { + request.getHttpRequest().addResponseHeader("DAV", "1"); + + String method = request.getHttpRequest().getRequestMethod(); + + System.err.println("DAV Request: " + method); + + if (method.equals("GET")) + { + request.getHttpRequest().setResponseHeader("Content-Type", "text/plain"); + request.getHttpRequest().getResponseWriter().write("This is a DAV Container."); + } else if (method.equals("PROPFIND")) + { + doPROPFIND(request); + } else if (method.equals("PROPPATCH")) + { + doPROPPATCH(request); + } else if (method.equals("MKCOL")) + { + doMKCOL(request); + } else if (method.equals("DELETE")) + { + doDELETE(request); + } else if (method.equals("OPTIONS")) + { + doOPTIONS(request); + } + } + + protected void doPROPFIND(ServerObjectRequest request) throws HttpException + { + System.err.println("PROPFIND:"); + System.err.println(new String(request.getHttpRequest().getRequestContent())); + + Document d = request.getHttpRequest().getXMLRequest(); + if (d == null) + throw new HttpException(403); + + Element propfind = d.getDocumentElement(); + + if (propfind.getLocalName().equals("propfind") && propfind.getNamespaceURI().equals("DAV:")) + { + for (Element child:NodeListIterator.create(propfind.getChildNodes())) + { + if (child.getLocalName().equals("prop")) + { + DAVPropertyNameList names = DAVPropertyNameList.fromXML(child); + for (DAVPropertyName name:names) + { + System.err.println("DAVProp: " + name.getNamespaceURI() + " : " + name.getPropertyName()); + } + } + } + } + + } + protected void doPROPPATCH(ServerObjectRequest request) throws HttpException + { + } + protected void doMKCOL(ServerObjectRequest request) throws HttpException + { + } + protected void doDELETE(ServerObjectRequest request) throws HttpException + { + } + protected void doOPTIONS(ServerObjectRequest request) throws HttpException + { + request.getHttpRequest().setResultCode(200); + } + +} diff --git a/src/org/hwo/net/serverobjects/dav/DAVCollection.java b/src/org/hwo/net/serverobjects/dav/DAVCollection.java new file mode 100644 index 0000000..2263f8e --- /dev/null +++ b/src/org/hwo/net/serverobjects/dav/DAVCollection.java @@ -0,0 +1,53 @@ +package org.hwo.net.serverobjects.dav; + +import java.util.LinkedList; +import java.util.List; + +public class DAVCollection extends DAVResource{ + + List children; + + public DAVCollection(String name) { + super(name); + children = new LinkedList(); + } + + @Override + public boolean isCollection() { + return true; + } + + @Override + public DAVResource[] getChildren() { + return children.toArray(new DAVResource[0]); + } + + public void appendResource(DAVResource child) + { + DAVResource resource = getChildByName(child.getName()); + if (resource != null) + { + removeChild(resource); + } + children.add(child); + child.setParent(this); + } + + public DAVResource getChildByName(String name) + { + for (DAVResource resource: children) + { + if (resource.getName().equals(name)) + return resource; + } + return null; + } + + public void removeChild(DAVResource child) + { + children.remove(child); + child.setParent(null); + } + + +} diff --git a/src/org/hwo/net/serverobjects/dav/DAVDeadPropertyNS.java b/src/org/hwo/net/serverobjects/dav/DAVDeadPropertyNS.java new file mode 100644 index 0000000..f710f63 --- /dev/null +++ b/src/org/hwo/net/serverobjects/dav/DAVDeadPropertyNS.java @@ -0,0 +1,46 @@ +package org.hwo.net.serverobjects.dav; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +public class DAVDeadPropertyNS extends DAVPropertyNamespace { + + DocumentBuilder builder; + + public DAVDeadPropertyNS(String namespaceURI) + { + super(namespaceURI); + + try { + DocumentBuilderFactory f = DocumentBuilderFactory.newInstance(); + f.setNamespaceAware(true); + builder = f.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } + } + + @Override + public Element getPropertyValue(DAVResource resource, DAVProperty property) { + return resource.getProperty(property.getNamespace().getNamespaceURI(), property.getPropertyName()); + } + + @Override + public void setPropertyValue(DAVResource resource, DAVProperty property,Element value) { + if (property.isValue(value)) + { + Document d = builder.newDocument(); + Node n = d.importNode(value, true); + d.appendChild(n); + System.err.println("setPropertyValue: " + d.toString()); + } + } + + + +} diff --git a/src/org/hwo/net/serverobjects/dav/DAVProperty.java b/src/org/hwo/net/serverobjects/dav/DAVProperty.java new file mode 100644 index 0000000..72bf622 --- /dev/null +++ b/src/org/hwo/net/serverobjects/dav/DAVProperty.java @@ -0,0 +1,43 @@ +package org.hwo.net.serverobjects.dav; + +import org.w3c.dom.Element; + +public abstract class DAVProperty { + + private DAVPropertyNamespace namespace; + private String propertyName; + + public DAVProperty(DAVPropertyNamespace namespace,String propertyName) + { + this.namespace = namespace; + this.propertyName = propertyName; + + } + + public DAVPropertyNamespace getNamespace() { + return namespace; + } + + public String getPropertyName() { + return propertyName; + } + + public Element getValue(DAVResource resource) + { + return namespace.getPropertyValue(resource, this); + } + public void setValue(DAVResource resource,Element value) + { + this.namespace.setPropertyValue(resource, this, value); + } + + + public boolean isValue(Element value) + { + if (value.getNamespaceURI().equals(namespace.getNamespaceURI()) && value.getLocalName().equals(propertyName)) + return true; + return false; + } + + +} diff --git a/src/org/hwo/net/serverobjects/dav/DAVPropertyDAVNS.java b/src/org/hwo/net/serverobjects/dav/DAVPropertyDAVNS.java new file mode 100644 index 0000000..9fd86f7 --- /dev/null +++ b/src/org/hwo/net/serverobjects/dav/DAVPropertyDAVNS.java @@ -0,0 +1,29 @@ +package org.hwo.net.serverobjects.dav; + +import org.w3c.dom.Element; + +public class DAVPropertyDAVNS extends DAVPropertyNamespace { + + public DAVPropertyDAVNS(String namespaceURI) { + super(namespaceURI); + } + + @Override + public Element getPropertyValue(DAVResource resource, DAVProperty property) { + + if (property.getPropertyName().equals("")) + { + + } + return null; + } + + @Override + public void setPropertyValue(DAVResource resource, DAVProperty property, + Element value) { + } + + + + +} diff --git a/src/org/hwo/net/serverobjects/dav/DAVPropertyName.java b/src/org/hwo/net/serverobjects/dav/DAVPropertyName.java new file mode 100644 index 0000000..2abbdb2 --- /dev/null +++ b/src/org/hwo/net/serverobjects/dav/DAVPropertyName.java @@ -0,0 +1,52 @@ +package org.hwo.net.serverobjects.dav; + +import org.w3c.dom.Element; + +public class DAVPropertyName { + + private String namespaceURI; + private String propertyName; + + public static DAVPropertyName fromXML(Element property) + { + DAVPropertyName dpn = new DAVPropertyName(); + dpn.setNamespaceURI(property.getNamespaceURI()); + dpn.setPropertyName(property.getLocalName()); + return dpn; + } + + public DAVPropertyName() + { + setNamespaceURI(null); + setPropertyName(null); + } + public DAVPropertyName(String propertyName) + { + setNamespaceURI(propertyName); + setPropertyName(null); + } + public DAVPropertyName(String propertyName,String namespaceURI) + { + setNamespaceURI(propertyName); + setPropertyName(namespaceURI); + } + + public String getNamespaceURI() { + return namespaceURI; + } + + public void setNamespaceURI(String namespaceURI) { + this.namespaceURI = namespaceURI; + } + + public String getPropertyName() { + return propertyName; + } + + public void setPropertyName(String propertyName) { + this.propertyName = propertyName; + } + + + +} diff --git a/src/org/hwo/net/serverobjects/dav/DAVPropertyNameList.java b/src/org/hwo/net/serverobjects/dav/DAVPropertyNameList.java new file mode 100644 index 0000000..c23e355 --- /dev/null +++ b/src/org/hwo/net/serverobjects/dav/DAVPropertyNameList.java @@ -0,0 +1,23 @@ +package org.hwo.net.serverobjects.dav; + +import java.util.LinkedList; + +import org.hwo.xml.NodeListIterator; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +public class DAVPropertyNameList extends LinkedList{ + + public static DAVPropertyNameList fromXML(Element prop) + { + DAVPropertyNameList l = new DAVPropertyNameList(); + + NodeList props = prop.getChildNodes(); + for (Element p:new NodeListIterator(props)) + l.add(DAVPropertyName.fromXML(p)); + + return l; + } + + +} diff --git a/src/org/hwo/net/serverobjects/dav/DAVPropertyNamespace.java b/src/org/hwo/net/serverobjects/dav/DAVPropertyNamespace.java new file mode 100644 index 0000000..0091dda --- /dev/null +++ b/src/org/hwo/net/serverobjects/dav/DAVPropertyNamespace.java @@ -0,0 +1,40 @@ +package org.hwo.net.serverobjects.dav; + +import java.util.Hashtable; + +import org.w3c.dom.Element; + +public abstract class DAVPropertyNamespace { + + private static Hashtable + namespaces; + + public static DAVPropertyNamespace getNameSpace(String namespaceURI) + { + if (!namespaces.containsKey(namespaceURI)) + namespaces.put(namespaceURI, new DAVDeadPropertyNS(namespaceURI)); + + return namespaces.get(namespaceURI); + } + + private String namespaceURI; + + public DAVPropertyNamespace(String namespaceURI) + { + this.namespaceURI = namespaceURI; + } + + + public String getNamespaceURI() { + return namespaceURI; + } + + abstract public Element getPropertyValue(DAVResource resource,DAVProperty property); + abstract public void setPropertyValue(DAVResource resource,DAVProperty property,Element value); + + + static { + namespaces = new Hashtable(); + } + +} diff --git a/src/org/hwo/net/serverobjects/dav/DAVResource.java b/src/org/hwo/net/serverobjects/dav/DAVResource.java new file mode 100644 index 0000000..22bbfb6 --- /dev/null +++ b/src/org/hwo/net/serverobjects/dav/DAVResource.java @@ -0,0 +1,153 @@ +package org.hwo.net.serverobjects.dav; + +import java.util.Hashtable; +import java.util.Properties; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.hwo.xml.NodeListIterator; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.w3c.dom.traversal.NodeIterator; + +public class DAVResource { + + private byte[] content; + private String name; + private String mimetype; + + private DAVResource + parent; + + private String uri; + + private Hashtable + properties; + + private DocumentBuilder + builder; + + public DAVResource(String name) + { + this.setContent(new byte[0]); + this.setName(name); + this.setMimetype("application/octet-stream"); + + DocumentBuilderFactory f = DocumentBuilderFactory.newInstance(); + f.setNamespaceAware(true); + try { + builder = f.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } + } + + protected void setParent(DAVResource resource) + { + this.parent = resource; + this.setURI(constructURI()); + } + + public DAVResource getParent() + { + return this.parent; + } + + protected String constructURI() + { + if (parent != null) + return String.format("%s/%s",parent.constructURI(),name); + return String.format("/%s", name); + } + + + public boolean isCollection() + { + return false; + } + + public DAVResource[] getChildren() + { + return new DAVResource[0]; + } + + public int getContentLength() + { + return content.length; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public byte[] getContent() { + return content; + } + public void setContent(byte[] content) { + this.content = content; + } + + public String getURI() + { + return this.uri; + } + private void setURI(String uri) + { + this.uri = uri; + } + + + public String getMimetype() { + return mimetype; + } + public void setMimetype(String mimetype) { + this.mimetype = mimetype; + } + + public Document getPropertyDocument(String namespaceURI) + { + if (!properties.containsKey(namespaceURI)) + { + Document d = builder.newDocument(); + d.appendChild(d.createElementNS(namespaceURI, "properties")); + properties.put(namespaceURI, d); + } + return properties.get(namespaceURI); + } + + public Element getProperty(String namespaceURI,String propertyName) + { + Document pdoc = getPropertyDocument(namespaceURI); + + NodeList nl = pdoc.getElementsByTagNameNS(namespaceURI, propertyName); + if (nl.getLength()>0) + return (Element)nl.item(0); + return null; + } + + public void setProperty(String namespaceURI,String propertyName,Element value) + { + Document pdoc = getPropertyDocument(namespaceURI); + + Element propertyElement = getProperty(namespaceURI, propertyName); + if (propertyElement == null) + { + propertyElement = pdoc.createElementNS(namespaceURI, propertyName); + pdoc.getDocumentElement().appendChild( propertyElement ); + } + while (propertyElement.hasChildNodes()) + propertyElement.removeChild(propertyElement.getFirstChild()); + + Element imp = (Element)pdoc.importNode(propertyElement, true); + for (Element c: NodeListIterator.create(imp.getChildNodes())) + propertyElement.appendChild( c ); + } + + + +} From 5c7987551f58c5c2803b3cd87d2582fda86eb90e Mon Sep 17 00:00:00 2001 From: Harald Wolff Date: Sun, 24 Aug 2014 13:07:50 +0200 Subject: [PATCH 4/5] Updates --- src/org/hwo/StringHelper.java | 6 +++ src/org/hwo/io/servicelink/ServiceLink.java | 4 +- .../register/IntegerRegisterEditor.java | 5 ++- .../serverobjects/AbstractServerObject.java | 5 ++- src/org/hwo/security/User.java | 40 +++++++++++++++++++ src/org/hwo/xml/NodeListIterator.java | 38 ++++++++++++++++++ 6 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 src/org/hwo/security/User.java create mode 100644 src/org/hwo/xml/NodeListIterator.java diff --git a/src/org/hwo/StringHelper.java b/src/org/hwo/StringHelper.java index 67df3b0..3d44cdd 100644 --- a/src/org/hwo/StringHelper.java +++ b/src/org/hwo/StringHelper.java @@ -1,10 +1,16 @@ package org.hwo; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; public class StringHelper { + public static String join(String[] c,String del) + { + return join( Arrays.asList(c),del); + } + public static String join(Collection c,String del) { StringBuilder sb = new StringBuilder(); diff --git a/src/org/hwo/io/servicelink/ServiceLink.java b/src/org/hwo/io/servicelink/ServiceLink.java index 0bffa2a..44754b7 100644 --- a/src/org/hwo/io/servicelink/ServiceLink.java +++ b/src/org/hwo/io/servicelink/ServiceLink.java @@ -130,7 +130,7 @@ public class ServiceLink { if (chksum != ChkSum.chksum(rxbuffer, 0, bb.position() - 2)) throw new ServiceLinkException(); - System.err.println(String.format("recv(): %d.%d:%d = 0x%08x",achse,knoten,register,bb.getInt(5))); + //System.err.println(String.format("recv(): %d.%d:%d = 0x%08x",achse,knoten,register,bb.getInt(5))); } catch (IOException e) { getSerialPort().close(); @@ -185,7 +185,7 @@ public class ServiceLink { { this.retries = 3; this.serialPort = serialPort; - this.serialPort.setTimeout(250); + this.serialPort.setTimeout(500); this.serviceRegisterCache = new ServiceRegisterCache(this); this.requestTime = new Smoother(); this.requestTime.setTn(16); diff --git a/src/org/hwo/io/servicelink/register/IntegerRegisterEditor.java b/src/org/hwo/io/servicelink/register/IntegerRegisterEditor.java index bfa914d..5acba0a 100644 --- a/src/org/hwo/io/servicelink/register/IntegerRegisterEditor.java +++ b/src/org/hwo/io/servicelink/register/IntegerRegisterEditor.java @@ -53,7 +53,10 @@ public class IntegerRegisterEditor extends JPanel implements ServiceRegisterCont @Override public void readValue() { - tfValue.setText(this.serviceRegister.readIntegerValue().toString()); + Integer i = this.serviceRegister.readIntegerValue(); + if ( i == null ) + i = 0; + tfValue.setText(i.toString()); } @Override diff --git a/src/org/hwo/net/serverobjects/AbstractServerObject.java b/src/org/hwo/net/serverobjects/AbstractServerObject.java index 1359ef9..d3e2be7 100644 --- a/src/org/hwo/net/serverobjects/AbstractServerObject.java +++ b/src/org/hwo/net/serverobjects/AbstractServerObject.java @@ -4,7 +4,10 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import javax.xml.ws.http.HTTPException; + import org.hwo.net.ServerObject; +import org.hwo.net.http.HttpException; import org.hwo.net.http.HttpServerRequest; public abstract class AbstractServerObject implements ServerObject { @@ -49,7 +52,7 @@ public abstract class AbstractServerObject implements ServerObject { } @Override - public void climb(ServerObjectRequest request) throws IOException { + public void climb(ServerObjectRequest request) throws IOException, HttpException { String nextChildName = request.popNextElement(); if (nextChildName == null) diff --git a/src/org/hwo/security/User.java b/src/org/hwo/security/User.java new file mode 100644 index 0000000..45be7ec --- /dev/null +++ b/src/org/hwo/security/User.java @@ -0,0 +1,40 @@ +package org.hwo.security; + +import java.util.ArrayList; +import java.util.List; + +public class User { + + private String username; + private List associatedRights; + + public User() + { + username = null; + associatedRights = new ArrayList(); + } + + public String[] getAssociatedRights() + { + return associatedRights.toArray(new String[0]); + } + + public String getUserName() + { + return this.username; + } + + public void deauthenticate() + { + this.username = null; + this.associatedRights.clear(); + } + + public boolean authenticate(String username,String password) + { + this.username = username; + this.associatedRights.add("sys.default.user.implementation"); + return true; + } + +} diff --git a/src/org/hwo/xml/NodeListIterator.java b/src/org/hwo/xml/NodeListIterator.java new file mode 100644 index 0000000..c194986 --- /dev/null +++ b/src/org/hwo/xml/NodeListIterator.java @@ -0,0 +1,38 @@ +package org.hwo.xml; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class NodeListIterator implements Iterable{ + + + private List elements; + + public static NodeListIterator create(NodeList nodes) + { + return new NodeListIterator(nodes); + } + + public NodeListIterator(NodeList nodes) + { + this.elements = new ArrayList(); + for (int i=0;i iterator() { + return this.elements.iterator(); + } + + +} From f8b8e0bab519139dfa656f0459c2fd8f5191f468 Mon Sep 17 00:00:00 2001 From: Harald Wolff Date: Sun, 24 Aug 2014 13:08:05 +0200 Subject: [PATCH 5/5] native/libhwoio.so: Update --- src/native/libhwoio.so | Bin 9317 -> 9337 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/native/libhwoio.so b/src/native/libhwoio.so index f9b1b15f8c7915dfb7faf00a426aac27576d4d16..5a9e35a8384ea05955da828b01461e788b17e804 100755 GIT binary patch literal 9337 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 Hv&z2#Nr?(8 literal 9317 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 WZdY7-Zf%fZTHO_>_7o~VrThzCb3-`*