diff --git a/simple-demo/simple-demo-rest/.classpath b/simple-demo/simple-demo-rest/.classpath new file mode 100644 index 0000000..bc5aeac --- /dev/null +++ b/simple-demo/simple-demo-rest/.classpath @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/simple-demo/simple-demo-rest/.gitignore b/simple-demo/simple-demo-rest/.gitignore new file mode 100644 index 0000000..cc17820 --- /dev/null +++ b/simple-demo/simple-demo-rest/.gitignore @@ -0,0 +1,41 @@ +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini +target +report +build +.settings +log + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + +.DS_Store +.AppleDouble +.LSOverride + +# Icon must ends with two \r. +Icon + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes diff --git a/simple-demo/simple-demo-rest/.project b/simple-demo/simple-demo-rest/.project new file mode 100644 index 0000000..501e5e8 --- /dev/null +++ b/simple-demo/simple-demo-rest/.project @@ -0,0 +1,35 @@ + + + simple-demo-rest + + + + + + org.eclipse.jdt.core.javabuilder + + + + + javafx.eclipse.f3editor.builder.F3Builder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.springframework.ide.eclipse.core.springnature + org.eclipse.m2e.core.maven2Nature + org.eclipse.jdt.core.javanature + javafx.eclipse.f3editor.builder.F3Nature + + diff --git a/simple-demo/simple-demo-rest/bin/start.bat b/simple-demo/simple-demo-rest/bin/start.bat new file mode 100644 index 0000000..95f0b88 --- /dev/null +++ b/simple-demo/simple-demo-rest/bin/start.bat @@ -0,0 +1,14 @@ +@echo off +echo Starting application. Please wait... + +cd .. + +echo %CLASSPATH% + +set CLASSPATH=etc/.;lib/*;~1%;. + +echo %CLASSPATH% + +rem java "%JAVA_OPTS%" -classpath %CLASSPATH% org.simpleframework.demo.ApplicationLauncher etc/spring.xml etc/common.properties etc/local.properties +java "%JAVA_OPTS%" -classpath %CLASSPATH% org.simpleframework.demo.ApplicationLauncher etc/spring.xml etc/common.properties etc/local.properties + diff --git a/simple-demo/simple-demo-rest/data/chat/index.html b/simple-demo/simple-demo-rest/data/chat/index.html new file mode 100644 index 0000000..4cac24a --- /dev/null +++ b/simple-demo/simple-demo-rest/data/chat/index.html @@ -0,0 +1,12 @@ + + + Login Page + + +

Please Login

+
+ + +
+ + \ No newline at end of file diff --git a/simple-demo/simple-demo-rest/data/chat/login.html b/simple-demo/simple-demo-rest/data/chat/login.html new file mode 100644 index 0000000..50f3cbd --- /dev/null +++ b/simple-demo/simple-demo-rest/data/chat/login.html @@ -0,0 +1,35 @@ + + + Chat Room + + + +

Chat Room

+ Refresh browser to clear page and resubscribe +
+
+ +
+ + \ No newline at end of file diff --git a/simple-demo/simple-demo-rest/etc/common.properties b/simple-demo/simple-demo-rest/etc/common.properties new file mode 100644 index 0000000..eb4bbfa --- /dev/null +++ b/simple-demo/simple-demo-rest/etc/common.properties @@ -0,0 +1,3 @@ +log4j.configFile=etc/log4j.xml + +server.listenPort=6060 diff --git a/simple-demo/simple-demo-rest/etc/local.properties b/simple-demo/simple-demo-rest/etc/local.properties new file mode 100644 index 0000000..0aa4ef7 --- /dev/null +++ b/simple-demo/simple-demo-rest/etc/local.properties @@ -0,0 +1 @@ +context.path=chat \ No newline at end of file diff --git a/simple-demo/simple-demo-rest/etc/log4j.xml b/simple-demo/simple-demo-rest/etc/log4j.xml new file mode 100644 index 0000000..979f1cc --- /dev/null +++ b/simple-demo/simple-demo-rest/etc/log4j.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/simple-demo/simple-demo-rest/etc/spring.xml b/simple-demo/simple-demo-rest/etc/spring.xml new file mode 100644 index 0000000..d53750c --- /dev/null +++ b/simple-demo/simple-demo-rest/etc/spring.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/simple-demo/simple-demo-rest/pom.xml b/simple-demo/simple-demo-rest/pom.xml new file mode 100644 index 0000000..0247456 --- /dev/null +++ b/simple-demo/simple-demo-rest/pom.xml @@ -0,0 +1,137 @@ + + 4.0.0 + simple-demo-rest + org.simpleframework + 6.0.1 + jar + + + 1.6 + 1.6 + ISO-8859-1 + 1.82 + + + + + junit + junit + 3.8.1 + test + + + org.simpleframework + simple-http + 6.0.1 + + + org.simpleframework + simple-common + 6.0.1 + + + org.simpleframework + simple-transport + 6.0.1 + + + org.simpleframework + simple-demo + 6.0.1 + + + org.apache.httpcomponents + httpclient + 4.2.3 + + + com.google.code.gson + gson + 2.3.1 + + + + + + org.codehaus.mojo + cobertura-maven-plugin + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + + + src/main/resources + true + + + + + + local + + + + maven-antrun-plugin + + + local + package + + run + + + + + + + + + + + + + + + + maven-assembly-plugin + + websocket-chat-${version} + + + + local + package + + single + + + + ${basedir}/src/main/assembly/filter-local.properties + + + ${project.build.directory}/assembly/assembly-local.xml + + + + + + + + + + diff --git a/simple-demo/simple-demo-rest/src/main/assembly/assembly.xml b/simple-demo/simple-demo-rest/src/main/assembly/assembly.xml new file mode 100644 index 0000000..85088be --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/assembly/assembly.xml @@ -0,0 +1,78 @@ + + @archive@ + + tar + + + + /lib + ${artifact.artifactId}.${artifact.extension} + + + + + ${project.basedir}/bin + bin + 0755 + 0755 + unix + true + + *.sh + *.bat + + + + ${project.basedir}/etc + etc + 0644 + 0755 + true + + @env@.properties + common.properties + jmxremote.password + log4j.xml + spring.xml + spring-*.xml + + + + ${project.basedir}/etc + etc + 0644 + 0755 + false + + @env@.yieldbroker.com.pfx + + + + ${project.basedir}/error + error + 0644 + 0755 + false + + **/*.html + + + + ${project.basedir}/template + template + 0644 + 0755 + false + + **/*.png + **/*.gif + **/*.jpg + **/*.vm + **/*.html + **/*.css + **/*.js + + + + diff --git a/simple-demo/simple-demo-rest/src/main/assembly/filter-local.properties b/simple-demo/simple-demo-rest/src/main/assembly/filter-local.properties new file mode 100644 index 0000000..9be7e73 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/assembly/filter-local.properties @@ -0,0 +1,3 @@ +env=local + +jvm.memory=-Xrunjdwp:transport=dt_socket,server=y,address=8942,suspend=n -Xms512m -Xmx512m -XX:MaxPermSize=128m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:+CMSPermGenSweepingEnabled diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRequest.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRequest.java new file mode 100644 index 0000000..2ec12a5 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRequest.java @@ -0,0 +1,26 @@ +package org.simpleframework.demo.rest; + +public class ChatRequest { + + private final String message; + private final String user; + private final String time; + + public ChatRequest(String message, String user, String time) { + this.message = message; + this.user = user; + this.time = time; + } + + public String getMessage() { + return message; + } + + public String getUser() { + return user; + } + + public String getTime() { + return time; + } +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRequestDistributor.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRequestDistributor.java new file mode 100644 index 0000000..3b7a762 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRequestDistributor.java @@ -0,0 +1,44 @@ +package org.simpleframework.demo.rest; + +import org.simpleframework.http.socket.Frame; +import org.simpleframework.http.socket.FrameListener; +import org.simpleframework.http.socket.Reason; +import org.simpleframework.http.socket.Session; + +import com.google.gson.Gson; + +public class ChatRequestDistributor implements FrameListener { + + private final MessagePublisher publisher; + private final Gson gson; + + public ChatRequestDistributor(MessagePublisher publisher) { + this.gson = new Gson(); + this.publisher = publisher; + } + + + @Override + public void onFrame(Session session, Frame frame) { + String text = frame.getText(); + Object value = gson.fromJson(text, ChatRequest.class); + + try { + publisher.publish(value); + } catch(Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onError(Session session, Exception cause) { + cause.printStackTrace(); + } + + @Override + public void onClose(Session session, Reason reason) { + String message = reason.getText(); + System.err.println(message); + } + +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRequestHandler.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRequestHandler.java new file mode 100644 index 0000000..3fc188a --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRequestHandler.java @@ -0,0 +1,26 @@ +package org.simpleframework.demo.rest; + +import org.simpleframework.http.socket.FrameChannel; +import org.simpleframework.http.socket.Session; +import org.simpleframework.http.socket.service.Service; + +public class ChatRequestHandler implements Service { // forwards on the chat messages from the WebSocket!! + + private final ChatRequestDistributor distributor; + + public ChatRequestHandler(MessagePublisher publisher) { + this.distributor = new ChatRequestDistributor(publisher); + } + + @Override + public void connect(Session connection) { + FrameChannel socket = connection.getChannel(); + + try { + socket.register(distributor); + } catch(Exception e) { + e.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRoom.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRoom.java new file mode 100644 index 0000000..236b29e --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRoom.java @@ -0,0 +1,79 @@ +package org.simpleframework.demo.rest; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.apache.log4j.Logger; +import org.simpleframework.http.Cookie; +import org.simpleframework.http.Request; +import org.simpleframework.http.socket.Frame; +import org.simpleframework.http.socket.Session; +import org.simpleframework.http.socket.FrameChannel; +import org.simpleframework.http.socket.service.Service; + +public class ChatRoom implements Service { + + private static final Logger LOG = Logger.getLogger(ChatRoom.class); + + private final ChatRoomListener listener; + private final Map sockets; + private final Set users; + + public ChatRoom() { + this.listener = new ChatRoomListener(this); + this.sockets = new ConcurrentHashMap(); + this.users = new CopyOnWriteArraySet(); + } + + public void connect(Session connection) { + FrameChannel socket = connection.getChannel(); + Request req = connection.getRequest(); + Cookie user = req.getCookie("user"); + + if(user == null) { + user = new Cookie("user", "anonymous"); + } + String name = user.getValue(); + + try { + socket.register(listener); + join(name, socket); + } catch(Exception e) { + LOG.info("Problem joining chat room", e); + } + + } + + public void join(String user, FrameChannel operation) { + sockets.put(user, operation); + users.add(user); + } + + public void leave(String user, FrameChannel operation){ + sockets.put(user, operation); + users.add(user); + } + + public void distribute(String from, Frame frame) { + try { + for(String user : users) { + FrameChannel operation = sockets.get(user); + + try { + if(!from.equals(user)) { + operation.send(frame); + } + } catch(Exception e){ + sockets.remove(user); + users.remove(user); + operation.close(); + LOG.info("Problem sending message", e); + } + } + } catch(Exception e) { + LOG.info("Problem distributing message", e); + } + } +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRoomListener.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRoomListener.java new file mode 100644 index 0000000..c6cb25b --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRoomListener.java @@ -0,0 +1,45 @@ +package org.simpleframework.demo.rest; + +import org.apache.log4j.Logger; +import org.simpleframework.http.Cookie; +import org.simpleframework.http.Request; +import org.simpleframework.http.socket.DataFrame; +import org.simpleframework.http.socket.Frame; +import org.simpleframework.http.socket.FrameListener; +import org.simpleframework.http.socket.FrameType; +import org.simpleframework.http.socket.Reason; +import org.simpleframework.http.socket.Session; + +public class ChatRoomListener implements FrameListener { + + private static final Logger LOG = Logger.getLogger(ChatRoomListener.class); + + private final ChatRoom room; + + public ChatRoomListener(ChatRoom room) { + this.room = room; + } + + public void onFrame(Session socket, Frame frame) { + FrameType type = frame.getType(); + String text = frame.getText(); + Request request = socket.getRequest(); + Cookie user = request.getCookie("user"); + String name = user.getValue(); + + if(type == FrameType.TEXT){ + Frame replay = new DataFrame(type, "(" + name + ") " +text); + + room.distribute(name, replay); + } + LOG.info("onFrame(" + type + ")"); + } + + public void onError(Session socket, Exception cause) { + LOG.info("onError(" + cause + ")", cause); + } + + public void onClose(Session session, Reason reason) { + LOG.info("onClose(" + reason + ")"); + } +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRoomLogin.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRoomLogin.java new file mode 100644 index 0000000..7977e59 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatRoomLogin.java @@ -0,0 +1,23 @@ +package org.simpleframework.demo.rest; + +import org.simpleframework.demo.http.resource.Resource; +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; + +public class ChatRoomLogin implements Resource { + + private final Resource resource; + + public ChatRoomLogin(Resource resource) { + this.resource = resource; + } + + @Override + public void handle(Request request, Response response) throws Throwable { + String name = request.getParameter("user"); + + response.setCookie("user", name); + resource.handle(request, response); + } + +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatServer.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatServer.java new file mode 100644 index 0000000..00f3853 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ChatServer.java @@ -0,0 +1,13 @@ +package org.simpleframework.demo.rest; + +public class ChatServer { + + public static final int PORT = 8991; + + public static void main(String[] list) throws Exception { + MessageServer server = new MessageServer(); + MessagePublisher publisher = server.create(PORT); + ChatRequestDistributor distributor = new ChatRequestDistributor(publisher); + + } +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/LeaseSubscriber.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/LeaseSubscriber.java new file mode 100644 index 0000000..21043da --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/LeaseSubscriber.java @@ -0,0 +1,68 @@ +package org.simpleframework.demo.rest; + +import java.util.concurrent.ThreadFactory; + +import org.simpleframework.common.thread.DaemonFactory; +import org.simpleframework.http.Method; + +import com.google.gson.Gson; + +public class LeaseSubscriber { + + private final ThreadFactory factory; + private final Gson gson; + private final String remote; + private final String local; + private final String key; + + public LeaseSubscriber(String key, String remote, String local) { + this.factory = new DaemonFactory(SubscriptionPublisher.class); + this.gson = new Gson(); + this.key = key; + this.local = local; + this.remote = remote; + } + + public void subscribe(String address, String filter) throws Exception { + SubscribeRequest request = new SubscribeRequest(key, local, filter, 10000); + RequestBuilder builder = new RequestBuilder(remote); + SubscriptionPublisher publisher = new SubscriptionPublisher(remote); + String value = gson.toJson(request); + Thread thread = factory.newThread(publisher); + + builder.setPath("/" + MessageServer.SUBSCRIBE_PREFIX); + builder.setMethod(Method.POST); + builder.setBody(value); + builder.execute(String.class); + thread.start(); + } + + private class SubscriptionPublisher implements Runnable { + + private final String remote; + + public SubscriptionPublisher(String remote) { + this.remote = remote; + } + + @Override + public void run() { + while(true) { + try { + RenewRequest request = new RenewRequest(key, 10000); + RequestBuilder builder = new RequestBuilder(remote); + String value = gson.toJson(request); + + builder.setPath("/" + MessageServer.RENEW_PREFIX); + builder.setMethod(Method.POST); + builder.setBody(value); + builder.execute(String.class); + Thread.sleep(5000); + } catch(Exception e) { + e.printStackTrace(); + } + } + } + + } +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/LeaseSubscriptionManager.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/LeaseSubscriptionManager.java new file mode 100644 index 0000000..9e9366c --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/LeaseSubscriptionManager.java @@ -0,0 +1,101 @@ +package org.simpleframework.demo.rest; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import org.simpleframework.common.lease.Cleaner; +import org.simpleframework.common.lease.Lease; +import org.simpleframework.common.lease.LeaseManager; + +public class LeaseSubscriptionManager implements SubscriptionManager { + + private final Map subscriptions; + private final LeaseManager manager; + private final SubscriptionCleaner cleaner; + + public LeaseSubscriptionManager() { + this.subscriptions = new ConcurrentHashMap(); + this.cleaner = new SubscriptionCleaner(); + this.manager = new LeaseManager(cleaner); + } + + public List match(MatchRequest request) { + List addresses = new ArrayList(); + + if(!subscriptions.isEmpty()) { + Set> entries = subscriptions.entrySet(); + Class type = request.getType(); + String name = type.getName(); + + for(Entry entry : entries) { + Subscription subscription = entry.getValue(); + String filter = subscription.getFilter(); + String address = subscription.getAddress(); + + if(filter.matches(name)) { + addresses.add(address); + } + } + } + return addresses; + } + + public void subscribe(SubscribeRequest request) { + String key = request.getKey(); + String address = request.getAddress(); + String filter = request.getFilter(); + long duration = request.getDuration(); + Lease lease = manager.lease(key, duration, TimeUnit.MILLISECONDS); + Subscription subscription = new Subscription(lease, address, filter); + subscriptions.put(key, subscription); + } + + public void renew(RenewRequest request) { + String key = request.getKey(); + long duration = request.getDuration(); + Subscription subscription = subscriptions.get(key); + + if(subscription != null) { + Lease lease = subscription.getLease(); + lease.renew(duration, TimeUnit.MILLISECONDS); + } + } + + private class SubscriptionCleaner implements Cleaner { + + public void clean(String key) { + subscriptions.remove(key); + } + } + + private class Subscription { + + private final Lease lease; + private final String address; + private final String filter; + + public Subscription(Lease lease, String address, String filter) { + this.lease = lease; + this.address = address; + this.filter = filter; + } + + public Lease getLease() { + return lease; + } + + public String getAddress() { + return address; + } + + public String getFilter() { + return filter; + } + + } +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/MatchRequest.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/MatchRequest.java new file mode 100644 index 0000000..c212c61 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/MatchRequest.java @@ -0,0 +1,14 @@ +package org.simpleframework.demo.rest; + +public class MatchRequest { + + private final Class type; + + public MatchRequest(Class type) { + this.type = type; + } + + public Class getType() { + return type; + } +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/MessageListener.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/MessageListener.java new file mode 100644 index 0000000..35545ef --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/MessageListener.java @@ -0,0 +1,5 @@ +package org.simpleframework.demo.rest; + +public interface MessageListener { + void onMessage(T message) throws Exception; +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/MessagePublisher.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/MessagePublisher.java new file mode 100644 index 0000000..bae0337 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/MessagePublisher.java @@ -0,0 +1,5 @@ +package org.simpleframework.demo.rest; + +public interface MessagePublisher { + void publish(Object value) throws Exception; +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/MessageServer.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/MessageServer.java new file mode 100644 index 0000000..2e9e7ea --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/MessageServer.java @@ -0,0 +1,36 @@ +package org.simpleframework.demo.rest; + +import java.util.Collections; +import java.util.List; + +public class MessageServer { + + public static final String SUBSCRIBE_PREFIX = "subscribe"; + public static final String RENEW_PREFIX = "renew"; + + private final List registrations; + + public MessageServer() { + this(Collections.EMPTY_LIST); + } + + public MessageServer(List registrations) { + this.registrations = registrations; + } + + public MessagePublisher create(int port) throws Exception { + SubscriptionManager manager = new LeaseSubscriptionManager(); + SubscribeRequestHandler subscriber = new SubscribeRequestHandler(manager); + RenewRequestHandler renewer = new RenewRequestHandler(manager); + RequestProcessor processor = new RequestProcessor(port); + + processor.register(new RequestRegistration(SUBSCRIBE_PREFIX, subscriber, SubscribeRequest.class)); + processor.register(new RequestRegistration(RENEW_PREFIX, renewer, RenewRequest.class)); + + for(RequestRegistration registration : registrations) { + processor.register(registration); + } + processor.start(); + return new SubscriptionMessagePublisher(manager); + } +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RenewRequest.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RenewRequest.java new file mode 100644 index 0000000..351de7e --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RenewRequest.java @@ -0,0 +1,20 @@ +package org.simpleframework.demo.rest; + +public class RenewRequest { + + private final String key; + private final long duration; + + public RenewRequest(String key, long duration){ + this.duration = duration; + this.key = key; + } + + public String getKey() { + return key; + } + + public long getDuration() { + return duration; + } +} \ No newline at end of file diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RenewRequestHandler.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RenewRequestHandler.java new file mode 100644 index 0000000..d83a702 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RenewRequestHandler.java @@ -0,0 +1,38 @@ +package org.simpleframework.demo.rest; + +import java.io.OutputStream; + +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; + +import com.google.gson.Gson; + +public class RenewRequestHandler implements RequestHandler { + + private final SubscriptionManager manager; + private final Gson gson; + + public RenewRequestHandler(SubscriptionManager manager) { + this.gson = new Gson(); + this.manager = manager; + } + + @Override + public void handle(Request request, Response response, RenewRequest message) throws Exception { + String key = message.getKey(); + StatusResponse status = new StatusResponse(key, true); + manager.renew(message); + String content = gson.toJson(status); + OutputStream output = response.getOutputStream(); + byte[] data = content.getBytes("UTF-8"); + + response.setContentType("text/json"); + response.setContentLength(data.length); + output.write(data); + output.flush(); + response.close(); + + + } + +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestBuilder.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestBuilder.java new file mode 100644 index 0000000..972a6d2 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestBuilder.java @@ -0,0 +1,409 @@ +package org.simpleframework.demo.rest; + +import static org.apache.http.conn.params.ConnRoutePNames.DEFAULT_PROXY; +import static org.apache.http.params.CoreConnectionPNames.CONNECTION_TIMEOUT; +import static org.apache.http.params.CoreConnectionPNames.SO_TIMEOUT; + +import java.io.InputStream; +import java.net.URI; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.NoConnectionReuseStrategy; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.BasicClientConnectionManager; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.params.HttpParams; +import org.apache.http.util.EntityUtils; + +public class RequestBuilder { + + private final AtomicReference override; + private final AtomicReference body; + private final Map parameters; + private final Map headers; + private final Map cookies; + private final ClientConnectionManager manager; + private final ConnectionReuseStrategy strategy; + private final ResponseConverter converter; + private final AtomicBoolean post; + private final DefaultHttpClient client; + private final String address; + private final URI template; + + public RequestBuilder(String address) throws Exception { + this.parameters = new HashMap(); + this.headers = new HashMap(); + this.cookies = new HashMap(); + this.override = new AtomicReference(); + this.body = new AtomicReference(); + this.manager = new BasicClientConnectionManager(); + this.strategy = new NoConnectionReuseStrategy(); + this.converter = new ResponseConverter(); + this.client = new DefaultHttpClient(manager); + this.template = new URI(address); + this.post = new AtomicBoolean(); + this.address = address; + } + + public String getAddress() { + return address; + } + + public RequestBuilder setBody(String value) { + if (body != null) { + body.set(value); + } + return this; + } + + public RequestBuilder setPath(String path) { + if (path != null) { + override.set(path); + } + return this; + } + + public RequestBuilder addCookie(String name, String value) { + if (value != null) { + cookies.put(name, value); + } + return this; + } + + public RequestBuilder addHeader(String name, String value) { + if (value != null) { + headers.put(name, value); + } + return this; + } + + public RequestBuilder addParameter(String name, String value) { + if (value != null) { + parameters.put(name, value); + } + return this; + } + + public RequestBuilder setMethod(String method) { + if (method.equals(HttpPost.METHOD_NAME)) { + post.set(true); + return this; + } + if (method.equals(HttpGet.METHOD_NAME)) { + post.set(false); + return this; + } + throw new IllegalStateException("Method '" + method + "' is not supported"); + } + + public RequestBuilder setAuthorization(String name, String password) { + Credentials credentials = createCredentials(name, password); + CredentialsProvider provider = client.getCredentialsProvider(); + + if (provider != null) { + provider.setCredentials(AuthScope.ANY, credentials); + } + return this; + } + + public RequestBuilder setConnectTimeout(int duration) throws Exception { + HttpParams parameters = client.getParams(); + + parameters.setParameter(CONNECTION_TIMEOUT, duration); + + return this; + } + + public RequestBuilder setReadTimeout(int duration) throws Exception { + HttpParams parameters = client.getParams(); + + parameters.setParameter(SO_TIMEOUT, duration); + + return this; + } + + public RequestBuilder setProxy(String address) throws Exception { + HttpHost proxy = createHost(address); + HttpParams parameters = client.getParams(); + + parameters.setParameter(DEFAULT_PROXY, proxy); + + return this; + } + + public T execute(Class type) throws Exception { + try { + if (post.get()) { + return executePost(type); + } + return executeGet(type); + } finally { + manager.shutdown(); + } + } + + private T executeGet(Class type) throws Exception { + HttpResponse response = executeGet(); + + if (converter.accept(type)) { + return convertResponse(response, type); + } + return createResponse(response, type); + } + + private T executePost(Class type) throws Exception { + HttpResponse response = executePost(); + + if (converter.accept(type)) { + return convertResponse(response, type); + } + return createResponse(response, type); + } + + private T createResponse(HttpResponse response, Class type) throws Exception { + HttpEntity entity = response.getEntity(); + + if (type == HttpResponse.class) { + return (T) response; + } + if (type == byte[].class) { + return (T) EntityUtils.toByteArray(entity); + } + if (type == InputStream.class) { + return (T) entity.getContent(); + } + throw new IllegalStateException("Cannot convert response to " + type); + } + + private T convertResponse(HttpResponse response, Class type) throws Exception { + HttpEntity entity = response.getEntity(); + String body = EntityUtils.toString(entity); + + if (body != null) { + return (T) converter.convert(type, body); + } + return null; + } + + private HttpResponse executePost() throws Exception { + HttpPost message = createPost(); + HttpEntity entity = createEntity(); + URI target = createURI(message); + + client.setReuseStrategy(strategy); + message.setURI(target); + message.setEntity(entity); + + return client.execute(message); + } + + private HttpResponse executeGet() throws Exception { + HttpGet message = createGet(); + URI target = createURI(message); + + client.setReuseStrategy(strategy); + message.setURI(target); + + return client.execute(message); + } + + private HttpGet createGet() throws Exception { + HttpGet get = new HttpGet(address); + + if (!headers.isEmpty()) { + Set names = headers.keySet(); + + for (String name : names) { + String value = headers.get(name); + get.addHeader(name, value); + } + } + if (!cookies.isEmpty()) { + Set names = cookies.keySet(); + + for (String name : names) { + String value = cookies.get(name); + String cookie = String.format("%s=%s", name, value); + + get.addHeader("Cookie", cookie); + } + } + return get; + } + + private HttpPost createPost() throws Exception { + HttpPost post = new HttpPost(address); + + if (!headers.isEmpty()) { + Set names = headers.keySet(); + + for (String name : names) { + String value = headers.get(name); + post.addHeader(name, value); + } + } + if (!cookies.isEmpty()) { + Set names = cookies.keySet(); + + for (String name : names) { + String value = cookies.get(name); + String cookie = String.format("%s=%s", name, value); + + post.addHeader("Cookie", cookie); + } + } + return post; + } + + private HttpHost createHost(String address) throws Exception { + URI target = URI.create(address); + String scheme = target.getScheme(); + String host = target.getHost(); + int port = target.getPort(); + + return new HttpHost(host, port, scheme); + } + + private URI createURI(HttpPost message) throws Exception { + URI target = message.getURI(); + String query = target.getQuery(); + + return createURI(message, query); + } + + private URI createURI(HttpGet message) throws Exception { + String query = createQuery(message); + + return createURI(message, query); + } + + private URI createURI(HttpUriRequest message, String query) throws Exception { + URI target = message.getURI(); + String fragment = target.getFragment(); + String info = target.getUserInfo(); + String path = extractPath(target); + String scheme = extractScheme(target); + String host = extractHost(target); + int port = extractPort(target); + + return new URI(scheme, info, host, port, path, query, fragment); + } + + private String createQuery(HttpGet message) throws Exception { + List parameters = createParameters(); + URI target = message.getURI(); + String query = target.getQuery(); + + if (!parameters.isEmpty()) { + if (query != null) { + query += "&"; + } else { + query = ""; + } + for (NameValuePair parameter : parameters) { + String name = parameter.getName(); + String value = parameter.getValue(); + + query += String.format("%s=%s&", name, value); + } + } + return query; + } + + private String extractPath(URI target) throws Exception { + String pathOverride = override.get(); + + if (pathOverride == null) { + String targetPath = target.getPath(); + + if (targetPath != null) { + return targetPath; + } + return template.getPath(); + } + return pathOverride; + } + + private int extractPort(URI target) throws Exception { + String targetHost = target.getHost(); + int targetPort = target.getPort(); + + if (targetHost != null) { + return targetPort; + } + return template.getPort(); + } + + private String extractHost(URI target) throws Exception { + String targetHost = target.getHost(); + + if (targetHost != null) { + return targetHost; + } + return template.getHost(); + } + + private String extractScheme(URI target) throws Exception { + String targetScheme = target.getScheme(); + + if (targetScheme != null) { + return targetScheme; + } + return template.getScheme(); + } + + private HttpEntity createEntity() throws Exception { + List parameters = createParameters(); + + if(parameters.isEmpty()) { + String content = body.get(); + return new StringEntity(content, "UTF-8"); + } + return new UrlEncodedFormEntity(parameters, "UTF-8"); + } + + private List createParameters() throws Exception { + List list = new LinkedList(); + + if (!parameters.isEmpty()) { + Set names = parameters.keySet(); + + for (String name : names) { + String value = parameters.get(name); + NameValuePair pair = createAttribute(name, value); + + list.add(pair); + } + } + return list; + } + + private NameValuePair createAttribute(String name, String value) { + return new BasicNameValuePair(name, value); + } + + private Credentials createCredentials(String user, String password) { + return new UsernamePasswordCredentials(user, password); + } +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestHandler.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestHandler.java new file mode 100644 index 0000000..bf28e8b --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestHandler.java @@ -0,0 +1,8 @@ +package org.simpleframework.demo.rest; + +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; + +public interface RequestHandler { + void handle(Request request, Response response, T message) throws Exception; +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestProcessor.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestProcessor.java new file mode 100644 index 0000000..44de650 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestProcessor.java @@ -0,0 +1,80 @@ +package org.simpleframework.demo.rest; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.simpleframework.http.Path; +import org.simpleframework.http.Protocol; +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; +import org.simpleframework.http.core.Container; +import org.simpleframework.http.core.ContainerSocketProcessor; +import org.simpleframework.transport.SocketProcessor; +import org.simpleframework.transport.connect.Connection; +import org.simpleframework.transport.connect.SocketConnection; + +import com.google.gson.Gson; + +public class RequestProcessor { + + private final Map registrations; + private final SocketProcessor processor; + private final SocketAddress address; + private final Container container; + private final Connection connection; + private final Gson gson; + + public RequestProcessor(int port) throws Exception { + this.registrations = new ConcurrentHashMap(); + this.address = new InetSocketAddress(port); + this.container = new RequestRouter(); + this.processor = new ContainerSocketProcessor(container); + this.connection = new SocketConnection(processor); + this.gson = new Gson(); + } + + public void register(RequestRegistration registration) throws Exception { + String prefix = registration.getPrefix(); + RequestRegistration previous = registrations.put(prefix, registration); + + if(previous != null) { + throw new IllegalArgumentException("Prefix '" + prefix + "' has already been used"); + } + } + + public void start() throws Exception { + connection.connect(address); + } + + public void stop() throws Exception { + connection.close(); + } + + private class RequestRouter implements Container { + + + @Override + public void handle(Request request, Response response) { + try { + Path path = request.getPath(); + String normal = path.getPath(); + RequestRegistration registration = registrations.get(normal); + RequestHandler handler = registration.getHandler(); + Class type = registration.getType(); + String content = request.getContent(); + Object message = gson.fromJson(content, type); + long time = System.currentTimeMillis(); + + response.setDate(Protocol.DATE, time); + handler.handle(request, response, message); + response.close(); + } catch(Exception e) { + e.printStackTrace(); + } + } + + } + +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestRegistration.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestRegistration.java new file mode 100644 index 0000000..23aee51 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestRegistration.java @@ -0,0 +1,30 @@ +package org.simpleframework.demo.rest; + +public class RequestRegistration { + + private final RequestHandler handler; + private final Class type; + private final String prefix; + + public RequestRegistration(String prefix, RequestHandler handler) { + this(prefix, handler, null); + } + + public RequestRegistration(String prefix, RequestHandler handler, Class type) { + this.handler = handler; + this.type = type; + this.prefix = prefix; + } + + public RequestHandler getHandler() { + return handler; + } + + public Class getType() { + return type; + } + + public String getPrefix() { + return prefix; + } +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestRouter.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestRouter.java new file mode 100644 index 0000000..bffbe85 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/RequestRouter.java @@ -0,0 +1,5 @@ +package org.simpleframework.demo.rest; + +public interface RequestRouter { + void register(RequestHandler handler, Class type, String prefix); +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ResponseConverter.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ResponseConverter.java new file mode 100644 index 0000000..3a46291 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/ResponseConverter.java @@ -0,0 +1,102 @@ +package org.simpleframework.demo.rest; + +public class ResponseConverter { + + public ResponseConverter() { + super(); + } + + public boolean accept(Class type) { + Class actual = convert(type); + + if (actual == String.class) { + return true; + } + if (actual == Integer.class) { + return true; + } + if (actual == Double.class) { + return true; + } + if (actual == Float.class) { + return true; + } + if (actual == Boolean.class) { + return true; + } + if (actual == Byte.class) { + return true; + } + if (actual == Short.class) { + return true; + } + if (actual == Long.class) { + return true; + } + if (actual == Character.class) { + return true; + } + return false; + } + + public Object convert(Class type, String value) { + Class actual = convert(type); + + if (actual == String.class) { + return value; + } + if (actual == Integer.class) { + return Integer.parseInt(value); + } + if (actual == Double.class) { + return Double.parseDouble(value); + } + if (actual == Float.class) { + return Float.parseFloat(value); + } + if (actual == Boolean.class) { + return Boolean.parseBoolean(value); + } + if (actual == Byte.class) { + return Byte.parseByte(value); + } + if (actual == Short.class) { + return Short.parseShort(value); + } + if (actual == Long.class) { + return Long.parseLong(value); + } + if (actual == Character.class) { + return value.charAt(0); + } + return value; + } + + private Class convert(Class type) { + if (type == int.class) { + return Integer.class; + } + if (type == double.class) { + return Double.class; + } + if (type == float.class) { + return Float.class; + } + if (type == boolean.class) { + return Boolean.class; + } + if (type == byte.class) { + return Byte.class; + } + if (type == short.class) { + return Short.class; + } + if (type == long.class) { + return Long.class; + } + if (type == char.class) { + return Character.class; + } + return type; + } +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/StatusResponse.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/StatusResponse.java new file mode 100644 index 0000000..db99021 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/StatusResponse.java @@ -0,0 +1,20 @@ +package org.simpleframework.demo.rest; + +public class StatusResponse { + + private final String address; + private final boolean success; + + public StatusResponse(String address, boolean success) { + this.address = address; + this.success = success; + } + + public String getAddress() { + return address; + } + + public boolean isSuccess() { + return success; + } +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/SubscribeRequest.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/SubscribeRequest.java new file mode 100644 index 0000000..a88f51e --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/SubscribeRequest.java @@ -0,0 +1,32 @@ +package org.simpleframework.demo.rest; + +public class SubscribeRequest { + + private final String key; + private final String filter; + private final String address; + private final long duration; + + public SubscribeRequest(String key, String filter, String address, long duration){ + this.filter = filter; + this.address = address; + this.duration = duration; + this.key = key; + } + + public String getKey() { + return key; + } + + public String getFilter() { + return filter; + } + + public String getAddress() { + return address; + } + + public long getDuration() { + return duration; + } +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/SubscribeRequestHandler.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/SubscribeRequestHandler.java new file mode 100644 index 0000000..836e3cc --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/SubscribeRequestHandler.java @@ -0,0 +1,38 @@ +package org.simpleframework.demo.rest; + +import java.io.OutputStream; + +import org.simpleframework.http.Request; +import org.simpleframework.http.Response; + +import com.google.gson.Gson; + +public class SubscribeRequestHandler implements RequestHandler { + + private final SubscriptionManager manager; + private final Gson gson; + + public SubscribeRequestHandler(SubscriptionManager manager) { + this.gson = new Gson(); + this.manager = manager; + } + + @Override + public void handle(Request request, Response response, SubscribeRequest message) throws Exception { + String key = message.getKey(); + StatusResponse status = new StatusResponse(key, true); + manager.subscribe(message); + String content = gson.toJson(status); + OutputStream output = response.getOutputStream(); + byte[] data = content.getBytes("UTF-8"); + + response.setContentType("text/json"); + response.setContentLength(data.length); + output.write(data); + output.flush(); + response.close(); + + + } + +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/SubscriptionManager.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/SubscriptionManager.java new file mode 100644 index 0000000..edcd158 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/SubscriptionManager.java @@ -0,0 +1,9 @@ +package org.simpleframework.demo.rest; + +import java.util.List; + +public interface SubscriptionManager { + List match(MatchRequest request); + void subscribe(SubscribeRequest request); + void renew(RenewRequest request); +} diff --git a/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/SubscriptionMessagePublisher.java b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/SubscriptionMessagePublisher.java new file mode 100644 index 0000000..70999b7 --- /dev/null +++ b/simple-demo/simple-demo-rest/src/main/java/org/simpleframework/demo/rest/SubscriptionMessagePublisher.java @@ -0,0 +1,35 @@ +package org.simpleframework.demo.rest; + +import java.util.List; + +import org.simpleframework.http.Method; + +import com.google.gson.Gson; + +public class SubscriptionMessagePublisher implements MessagePublisher { + + private final SubscriptionManager manager; + private final Gson gson; + + public SubscriptionMessagePublisher(SubscriptionManager manager) { + this.gson = new Gson(); + this.manager = manager; + } + + @Override + public void publish(Object value) throws Exception { + Class type = value.getClass(); + MatchRequest request = new MatchRequest(type); + List matches = manager.match(request); + String data = gson.toJson(value); + + for(String address : matches) { + RequestBuilder builder = new RequestBuilder(address); + + builder.setMethod(Method.POST); + builder.setBody(data); + builder.execute(String.class); + } + } + +}