Browse files

Initial commit of the reformulated multi-module project.

  • Loading branch information...
0 parents commit 1dac489519f6a5e6dced74f7cfc7f8286417037b @heinousjay committed Mar 26, 2012
Showing with 4,102 additions and 0 deletions.
  1. +5 −0 .gitignore
  2. +41 −0 JibbrJabbr-server/JibbrJabbr-api/pom.xml
  3. +54 −0 JibbrJabbr-server/JibbrJabbr-api/src/main/java/jj/api/Dispose.java
  4. +56 −0 JibbrJabbr-server/JibbrJabbr-api/src/main/java/jj/api/Select.java
  5. +61 −0 JibbrJabbr-server/JibbrJabbr-api/src/main/java/jj/api/Shutdown.java
  6. +65 −0 JibbrJabbr-server/JibbrJabbr-api/src/main/java/jj/api/Startup.java
  7. +72 −0 JibbrJabbr-server/JibbrJabbr-api/src/main/java/jj/api/Version.java
  8. +20 −0 JibbrJabbr-server/JibbrJabbr-api/src/main/java/jj/api/package-info.java
  9. +2 −0 JibbrJabbr-server/JibbrJabbr-api/src/main/resources/jj/api/VERSION
  10. +18 −0 JibbrJabbr-server/JibbrJabbr-api/src/test/java/jj/api/VersionTest.java
  11. +62 −0 JibbrJabbr-server/JibbrJabbr-kernel/pom.xml
  12. +42 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/Application.java
  13. +168 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/HttpServer.java
  14. +117 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/HttpThreadPoolExecutor.java
  15. +337 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/JJ.java
  16. +92 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/JJLifecycleStrategy.java
  17. +92 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/JJThreadPoolExecutor.java
  18. +285 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/Kernel.java
  19. +59 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/KernelMessages.java
  20. +141 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/KernelSettings.java
  21. +45 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/KernelTask.java
  22. +119 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/KernelThreadPoolExecutor.java
  23. +69 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/LocLoggerProvidingAdapter.java
  24. +45 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/MessageConveyorProvidingAdapter.java
  25. +283 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/NettyRequestBridge.java
  26. +131 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/html/HTMLFragment.java
  27. +29 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/resource/Reloadable.java
  28. +30 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/resource/Resource.java
  29. +34 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/response/HttpResponder.java
  30. BIN JibbrJabbr-server/JibbrJabbr-kernel/src/main/resources/jj/assets/favicon.ico
  31. +45 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/resources/jj/assets/index.html
  32. +10 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/resources/jj/errors/404.html
  33. +10 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/resources/jj/errors/405.html
  34. +17 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/main/resources/jj/kernel-messages_en.properties
  35. +70 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/test/java/fun/Example.java
  36. +48 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/test/java/fun/ExampleAdapter.java
  37. +103 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/test/java/fun/Pointless.java
  38. +92 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/test/java/jj/HttpThreadPoolExecutorTest.java
  39. +421 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/test/java/jj/MockLogger.java
  40. BIN JibbrJabbr-server/JibbrJabbr-kernel/src/test/resources/com/clamwhores/clamwhores.com.png
  41. +113 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/test/resources/com/clamwhores/index.html
  42. +12 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/test/resources/com/clamwhores/style.css
  43. +12 −0 JibbrJabbr-server/JibbrJabbr-kernel/src/test/resources/joj/example.html
  44. +63 −0 JibbrJabbr-server/JibbrJabbr/pom.xml
  45. +50 −0 JibbrJabbr-server/JibbrJabbr/src/main/assembly/package.xml
  46. +202 −0 JibbrJabbr-server/LICENSE
  47. 0 JibbrJabbr-server/NOTICE
  48. +11 −0 JibbrJabbr-server/README
  49. +161 −0 JibbrJabbr-server/pom.xml
  50. +39 −0 JibbrJabbr-site/pom.xml
  51. +18 −0 JibbrJabbr-site/src/site/apt/index.apt
  52. +14 −0 JibbrJabbr-site/src/site/site.xml
  53. +17 −0 pom.xml
5 .gitignore
@@ -0,0 +1,5 @@
+target
+.DS_Store
+.classpath
+.project
+.settings/
41 JibbrJabbr-server/JibbrJabbr-api/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>JibbrJabbr-server</artifactId>
+ <groupId>com.jibbrjabbr</groupId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>JibbrJabbr-api</artifactId>
+ <name>JibbrJabbr API</name>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>true</filtering>
+ <includes>
+ <include>jj/api/VERSION</include>
+ </includes>
+ </resource>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>false</filtering>
+ <excludes>
+ <exclude>jj/api/VERSION</exclude>
+ </excludes>
+ </resource>
+ </resources>
+ </build>
+ <properties>
+ <friendlyname>JibbrJabbr</friendlyname>
+ </properties>
+</project>
54 JibbrJabbr-server/JibbrJabbr-api/src/main/java/jj/api/Dispose.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj.api;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Annotates a method that is to be called before the object is taken out of
+ * service. Once this method completes, the instance will be discarded.
+ * </p>
+ *
+ * <p>
+ * The lifecycle is
+ * <ul>
+ * <li>Initialization (construction)</li>
+ * <li>{@link Startup}</li>
+ * <li>{@link Shutdown}</li>
+ * <li>({@link Startup})</li>
+ * <li>({@link Shutdown})...</li>
+ * <li>Dispose</li>
+ * <li>{@link Object#finalize()} (which obviously, avoid)</li>
+ * </ul>
+ * <p>
+ *
+ * @author jason
+ *
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+@Documented
+@Inherited
+public @interface Dispose {
+
+}
56 JibbrJabbr-server/JibbrJabbr-api/src/main/java/jj/api/Select.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj.api;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Annotates a method be called with a {@link org.jsoup.select.Elements Elements}
+ * collection that results from running the supplied selector against the active
+ * document.
+ * </p>
+ *
+ * <p>
+ * Examples:
+ * </p>
+ * <pre>
+ * public void doSomething(@Select("div") Elements divs, @Select("body") Element body) {
+ * //do something interesting..
+ * }
+ *
+ * @Select("*")
+ * public void doSomethingElse(Elements all) {
+ * //do something else interesting..
+ * }
+ * </pre>
+ * @author jason
+ *
+ */
+@Retention(RUNTIME)
+@Target({METHOD, PARAMETER})
+@Documented
+@Inherited
+public @interface Select {
+ String value();
+}
61 JibbrJabbr-server/JibbrJabbr-api/src/main/java/jj/api/Shutdown.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj.api;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Annotates a method that should be called to signal shutdown,
+ * which occurs some time after startup. This is an opportunity
+ * to clean up all resources acquired. Once the method completes,
+ * the object should be ready to be started again.
+ * </p>
+ *
+ * <p>
+ * The method may be called at additional points over the lifetime of an object,
+ * after a method annotated with {@link Startup} (if it is defined) has been
+ * called. (In general, if you define a Startup, you should define a Shutdown)
+ * <p>
+ *
+ * <p>
+ * The lifecycle is
+ * <ul>
+ * <li>Initialization (construction)</li>
+ * <li>Startup</li>
+ * <li>Shutdown</li>
+ * <li>(Startup)</li>
+ * <li>(Shutdown)...</li>
+ * <li>{@link Dispose}</li>
+ * <li>{@link Object#finalize()} (which obviously, avoid)</li>
+ * </ul>
+ * <p>
+ * @author jason
+ *
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+@Documented
+@Inherited
+public @interface Shutdown {
+
+}
65 JibbrJabbr-server/JibbrJabbr-api/src/main/java/jj/api/Startup.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj.api;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Annotates a method that should be called to signal startup,
+ * which occurs after initialization. Once the method completes,
+ * the object should be ready to service requests.
+ * </p>
+ *
+ * <p>
+ * The method may be called at additional points over the lifetime of an object,
+ * after a method annotated with {@link Shutdown} (if it is defined) has been
+ * called. (In general, if you define a Startup, you should define a Shutdown)
+ * <p>
+ *
+ * <p>
+ * This method is this first place to read in files or establish network
+ * connections that are needed to perform the job this object is to perform.
+ * </p>
+ *
+ * <p>
+ * The lifecycle is
+ * <ul>
+ * <li>Initialization (construction)</li>
+ * <li>Startup</li>
+ * <li>Shutdown</li>
+ * <li>(Startup)</li>
+ * <li>(Shutdown)...</li>
+ * <li>{@link Dispose}</li>
+ * <li>{@link Object#finalize()} (which obviously, avoid)</li>
+ * </ul>
+ * <p>
+ * @author jason
+ *
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+@Documented
+@Inherited
+public @interface Startup {
+
+}
72 JibbrJabbr-server/JibbrJabbr-api/src/main/java/jj/api/Version.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj.api;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * Exposes the system version as constants.
+ *
+ * @author Jason Miller
+ *
+ */
+public final class Version {
+
+ /** The name of the system */
+ public static final String name;
+ /** The full version of the system */
+ public static final String version;
+ /** The major version of the system */
+ public static final int major;
+ /** The minor version of the system */
+ public static final int minor;
+ /** Flag indicating if this is a snapshot */
+ public static final boolean snapshot;
+
+ private static final Pattern versionParser =
+ Pattern.compile("(\\d*)\\.(\\d*)(-SNAPSHOT)?");
+
+ static {
+ // we do things this way to avoid depending on any jj internal classes in
+ // order to create these values. The jj.api package is intended to have
+ // no visibility into system internals.
+
+ try (BufferedReader r = new BufferedReader(
+ new InputStreamReader(
+ Version.class.getResourceAsStream("VERSION"), Charset.forName("UTF-8")
+ )
+ )
+ ) {
+
+ name = r.readLine();
+ version = r.readLine();
+
+ Matcher matcher = versionParser.matcher(version);
+ matcher.matches();
+ major = Integer.parseInt(matcher.group(1));
+ minor = Integer.parseInt(matcher.group(2));
+ snapshot = matcher.group(3) != null;
+
+ } catch (Exception e) {
+ throw new IllegalStateException("MY JAR IS BROKEN", e);
+ }
+ }
+}
20 JibbrJabbr-server/JibbrJabbr-api/src/main/java/jj/api/package-info.java
@@ -0,0 +1,20 @@
+/**
+ * <p>
+ * Defines the interface to running inside the JibbrJabbr container. There
+ * are several considerations in the design of this API
+ * <p>
+ * <ol>
+ * <li>Simple to use, easy to understand. Hopefully.</li>
+ * <li>Nothing to extend or implement. The inheritance hierarchy of your
+ * application is totally under your control. JibbrJabbr interacts with
+ * your application through convention and the use of annotations.
+ * </li>
+ * <li>System internals are hidden. There is no way to use this
+ * API to divine what is happening behind the scenes. Hopefully this
+ * will help to ease migration across versions and avoid any
+ * leakage of abstractions. This implies that once functionality appears
+ * here, it never goes away. There should only be additions to the API.
+ * </li>
+ * </ol>
+ */
+package jj.api;
2 JibbrJabbr-server/JibbrJabbr-api/src/main/resources/jj/api/VERSION
@@ -0,0 +1,2 @@
+${friendlyname}
+${version}
18 JibbrJabbr-server/JibbrJabbr-api/src/test/java/jj/api/VersionTest.java
@@ -0,0 +1,18 @@
+package jj.api;
+
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+
+import org.junit.Test;
+
+
+public class VersionTest {
+
+ @Test
+ public void testVersionClass() {
+ // more or less if this doesn't throw exceptions
+ // and these values aren't null, we're golden
+ assertThat(Version.name, is(not(nullValue())));
+ assertThat(Version.version, is(not(nullValue())));
+ }
+}
62 JibbrJabbr-server/JibbrJabbr-kernel/pom.xml
@@ -0,0 +1,62 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>JibbrJabbr-server</artifactId>
+ <groupId>com.jibbrjabbr</groupId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>JibbrJabbr-kernel</artifactId>
+ <name>JibbrJabbr Kernel</name>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>JibbrJabbr-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jsoup</groupId>
+ <artifactId>jsoup</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.picocontainer</groupId>
+ <artifactId>picocontainer</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-ext</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>ch.qos.cal10n.plugins</groupId>
+ <artifactId>maven-cal10n-plugin</artifactId>
+ <version>0.7.4</version>
+ <executions>
+ <execution>
+ <id>verify-message-integrity</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>verify</goal>
+ </goals>
+ <configuration>
+ <enumTypes>
+ <!-- list every enum type you would like to see checked -->
+ <enumType>jj.KernelMessages</enumType>
+ </enumTypes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
42 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/Application.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj;
+
+/**
+ * Represents some level of Application containment,
+ * coordinating the set of resources that make it up
+ * - picocontainer
+ * - kernel connector
+ * - classloader
+ * - filesystem path
+ * - library jars
+ * - sub applications
+ *
+ * Primary responsibility is to mediate between the app
+ * and kernel services, which basically is to say all
+ * i/o runs through the kernel. This class and anything
+ * it controls should never block.
+ *
+ * The root Application in a given hierarchy establishes
+ * the thread pool for that application, and is responsible
+ * for creating and maintaining its own children.
+ *
+ * @author Jason Miller
+ *
+ */
+public class Application {
+
+}
168 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/HttpServer.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.jboss.netty.channel.ChannelState.BOUND;
+import static jj.KernelMessages.*;
+
+import java.net.InetSocketAddress;
+
+import jj.api.Dispose;
+
+import org.jboss.netty.bootstrap.ServerBootstrap;
+import org.jboss.netty.channel.ChannelEvent;
+import org.jboss.netty.channel.ChannelHandler;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.ChannelUpstreamHandler;
+import org.jboss.netty.channel.DefaultChannelPipeline;
+import org.jboss.netty.channel.UpstreamChannelStateEvent;
+import org.jboss.netty.channel.group.ChannelGroup;
+import org.jboss.netty.channel.group.DefaultChannelGroup;
+import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
+import org.jboss.netty.handler.codec.http.HttpContentCompressor;
+import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
+import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
+import org.jboss.netty.handler.stream.ChunkedWriteHandler;
+import org.slf4j.cal10n.LocLogger;
+
+public final class HttpServer {
+
+ private final LocLogger logger;
+
+ private final NettyRequestBridge requestHandler;
+
+ private final KernelSettings kernelSettings;
+
+ private final ServerBootstrap bootstrap;
+
+ /** All channels currently in use by the server */
+ private final ChannelGroup allChannels =
+ new DefaultChannelGroup(HttpServer.class.getName());
+
+ /**
+ * Simple tracker for channels created by this server.
+ * May be expanded at some future point.
+ */
+ private final ChannelHandler clientChannelTrackingHandler = new ChannelUpstreamHandler() {
+
+ @Override
+ public void handleUpstream(final ChannelHandlerContext ctx, final ChannelEvent e) throws Exception {
+ allChannels.add(e.getChannel());
+ ctx.sendUpstream(e);
+ }
+
+ };
+
+ private static final String CLIENT_TRACKER_NAME = "Client tracker";
+
+ private final ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
+
+ @Override
+ public ChannelPipeline getPipeline() {
+ ChannelPipeline pipeline = new DefaultChannelPipeline();
+ pipeline.addLast(CLIENT_TRACKER_NAME, clientChannelTrackingHandler);
+ // if SSL is enabled
+ // pipeline.addLast(SslHandler.class.getSimpleName(), new SslHandler(engine));
+ pipeline.addLast(HttpRequestDecoder.class.getSimpleName(),
+ new HttpRequestDecoder(
+ kernelSettings.httpMaxInitialLineLength(),
+ kernelSettings.httpMaxHeaderSize(),
+ kernelSettings.httpMaxChunkSize()
+ )
+ );
+ pipeline.addLast(HttpChunkAggregator.class.getSimpleName(),
+ new HttpChunkAggregator(kernelSettings.httpMaxRequestContentLength())
+ );
+ pipeline.addLast(HttpResponseEncoder.class.getSimpleName(), new HttpResponseEncoder());
+ pipeline.addLast(HttpContentCompressor.class.getSimpleName(),
+ new HttpContentCompressor(kernelSettings.httpCompressionLevel())
+ );
+ pipeline.addLast(ChunkedWriteHandler.class.getSimpleName(), new ChunkedWriteHandler());
+ pipeline.addLast(requestHandler.getClass().getSimpleName(), requestHandler);
+
+ return pipeline;
+ }
+ };
+
+ public HttpServer(
+ final LocLogger logger,
+ final NettyRequestBridge requestHandler,
+ final KernelSettings kernelSettings,
+ final KernelThreadPoolExecutor bossExecutor,
+ final HttpThreadPoolExecutor httpExecutor,
+ final Kernel.Controller kernelSync
+ ) {
+ assert logger != null;
+ assert requestHandler != null;
+ assert kernelSettings != null;
+ assert bossExecutor != null;
+
+ this.logger = logger;
+ this.requestHandler = requestHandler;
+ this.kernelSettings = kernelSettings;
+
+ logger.debug(ObjectInstantiating, HttpServer.class);
+
+ bootstrap = new ServerBootstrap(
+ new NioServerSocketChannelFactory(
+ bossExecutor,
+ httpExecutor,
+ kernelSettings.httpThreadMaxCount()
+ )
+ );
+
+ bootstrap.setPipelineFactory(pipelineFactory);
+
+ bootstrap.setParentHandler(new ChannelUpstreamHandler() {
+
+ @Override
+ public void handleUpstream(final ChannelHandlerContext ctx, final ChannelEvent e) throws Exception {
+ if (e instanceof UpstreamChannelStateEvent) {
+ UpstreamChannelStateEvent ucse = (UpstreamChannelStateEvent)e;
+ if (ucse.getState() == BOUND &&
+ ucse.getValue() != null) {
+ allChannels.add(e.getChannel());
+ logger.info(ReachedStartSyncPoint);
+ kernelSync.awaitHttpServerStart();
+ logger.info(InterfaceBound, ((UpstreamChannelStateEvent)e).getValue());
+ }
+ }
+ ctx.sendUpstream(e);
+ }
+ });
+
+ int port = kernelSettings.port();
+ logger.debug(BindingPort, port);
+ bootstrap.bind(new InetSocketAddress(port));
+ }
+
+
+ @Dispose
+ public void dispose() {
+ logger.info(HttpServerResourcesReleasing);
+ if (!allChannels.close().awaitUninterruptibly(kernelSettings.httpMaxShutdownTimeout(), SECONDS)) {
+ logger.warn(ConnectionsRemainPastTimeout, kernelSettings.httpMaxShutdownTimeout());
+ }
+ // TODO kill this after moving the i/o threadpool out
+ bootstrap.releaseExternalResources();
+ logger.info(HttpServerResourcesReleased);
+ }
+
+}
117 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/HttpThreadPoolExecutor.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj;
+
+import static java.util.Calendar.YEAR;
+import static java.util.Calendar.MONTH;
+import static java.util.Calendar.DAY_OF_MONTH;
+import static java.util.Calendar.HOUR;
+import static java.util.Calendar.MINUTE;
+import static java.util.Calendar.SECOND;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.util.Calendar;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.RunnableFuture;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.slf4j.Logger;
+
+/**
+ * <p>
+ * Dedicated pool for decoding incoming HTTP requests
+ * and handshaking WebSocket connections.
+ * </p>
+ *
+ * @author Jason Miller
+ *
+ */
+public class HttpThreadPoolExecutor
+ extends JJThreadPoolExecutor
+ implements RejectedExecutionHandler {
+
+ private final class HttpTask<V> extends FutureTask<V> {
+
+ public HttpTask(Callable<V> callable) {
+ super(callable);
+ }
+
+ public HttpTask(Runnable runnable, V result) {
+ super(runnable, result);
+ }
+
+ }
+
+ private final Logger logger;
+
+ private final AtomicInteger idSource = new AtomicInteger(1);
+
+ private final ThreadGroup threadGroup = new ThreadGroup(HttpThreadPoolExecutor.class.getSimpleName());
+
+ public HttpThreadPoolExecutor(final Logger logger, final KernelSettings kernelSettings) {
+ super(
+ logger,
+ kernelSettings.httpThreadCoreCount(),
+ kernelSettings.httpThreadMaxCount(),
+ kernelSettings.httpThreadTimeOut(), SECONDS,
+ new LinkedBlockingQueue<Runnable>()
+ );
+ this.logger = logger;
+ this.setRejectedExecutionHandler(this);
+
+ logger.debug("Instantiating {}", HttpThreadPoolExecutor.class);
+ }
+
+ @Override
+ protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
+ return new HttpTask<T>(callable);
+ }
+
+ @Override
+ protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
+ return new HttpTask<T>(runnable, value);
+ };
+
+ @Override
+ public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+ // can't happen with the current configuration
+ logger.warn("dropping connections");
+ }
+
+ @Override
+ String threadName() {
+ Calendar now = Calendar.getInstance(UTC);
+ return String.format("HTTP thread %d (%d-%d-%d %d:%d:%dUTC)",
+ idSource.getAndIncrement(),
+ now.get(YEAR),
+ now.get(MONTH) + 1,
+ now.get(DAY_OF_MONTH),
+ now.get(HOUR),
+ now.get(MINUTE),
+ now.get(SECOND)
+ );
+ }
+
+ @Override
+ ThreadGroup threadGroup() {
+ return threadGroup;
+ }
+
+}
337 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/JJ.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj;
+
+import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.UserPrincipal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import jj.api.Version;
+
+/**
+ * <p>
+ * Entry point to start the JibbrJabbr container from the command line.
+ * Behaves as the root classloader for the server.
+ * Also contains some container-wide constants and util methods needed
+ * to start up.
+ * </p>
+ *
+ * <p>
+ * The direct dependencies of this class are limited to inner classes,
+ * commons-daemon, the jj.api package, and the JDK.
+ * </p>
+ *
+ * @author Jason Miller
+ */
+public final class JJ {
+
+ // these names get defined here and then copied in the
+ // classes that manage directory layouts
+ public static final String SYSTEM_BASE_PATH = "system";
+ public static final String LIB_PATH = "lib";
+ public static final String META_INF_PATH = "META-INF";
+
+ private static final String JJ_KERNEL_CLASS = "jj.Kernel";
+
+ private static final String COMMONS_DAEMON_PROCESS_ID_KEY = "commons.daemon.process.id";
+
+ private static final Pattern JAR_URL_PARSER =
+ Pattern.compile("^jar:([^!]+)!.+$");
+
+ /**
+ * Get a resource path for a given Class, for instance
+ * "/jj/JJ.class" for this class. Arrays are unwrapped
+ * to their component type, and primitives and synthetic
+ * classes are ignored.
+ * @param clazz The class
+ * @return The resource path
+ */
+ public static String resourcePath(final Class<?> clazz) {
+ if (clazz.isPrimitive() || clazz.isSynthetic()) return null;
+ if (clazz.isArray()) return resourcePath(clazz.getComponentType());
+ return resourcePath(clazz.getName());
+ }
+
+ /**
+ * Get a resource path for a fully qualified class name, for
+ * instance "/jj/JJ.class" for "jj.JJ"
+ * @param className
+ * @return
+ */
+ public static String resourcePath(final String className) {
+ return "/" + className.replace('.', '/') + ".class";
+ }
+
+ /**
+ * Basic utility to turn a Class instance into its URI for
+ * resource lookup.
+ * @param clazz The Class to look up
+ * @return The URI representing that class, or null if the class cannot be
+ * represented as a resource (for instance if it is generated)
+ */
+ public static URI uri(final Class<?> clazz) {
+ try {
+ return clazz.getResource(resourcePath(clazz)).toURI();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Basic utility to get a URI to the jar that contains the
+ * resource identified by the URI, or null if the resource
+ * is not in a jar.
+ * @param uri The resource URI to locate
+ * @return The Path to the jar containing the identified resource, or null
+ * if the resource is not in a jar.
+ */
+ public static Path jarPath(URI uri) {
+ try {
+ Matcher m = JAR_URL_PARSER.matcher(uri.toString());
+ m.matches();
+ return Paths.get(new URI(m.group(1)));
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Basic utility to get a Path to the jar the contains the file that
+ * defined the given Class.
+ * @param clazz The Class to look up
+ * @return A Path to the jar containing the class file, or null if the
+ * jar cannot be found (for generated classes or class files not in
+ * a jar, for instance.)
+ */
+ public static Path jarForClass(Class<?> clazz) {
+ return jarPath(uri(clazz));
+ }
+
+ private static final Path myJarPath = jarForClass(JJ.class);
+ private static boolean initialized = false;
+
+ public static void main(String[] args)
+ throws Exception {
+ try {
+ new JJ(true).init(args);
+ } catch (IllegalStateException ise) {
+ ise.printStackTrace();
+ }
+ }
+
+ private Class<?> kernelClass;
+ private Object kernelInstance;
+ private boolean daemonStart = System.getProperty(COMMONS_DAEMON_PROCESS_ID_KEY) != null;
+
+ public JJ() {
+ if (initialized) {
+ throw new IllegalStateException("Already run once.");
+ }
+
+// for (String key : System.getProperties().stringPropertyNames()) {
+// System.err.printf("%s = %s\n", key, System.getProperty(key));
+// }
+ }
+
+ private JJ(boolean internal) {
+ if (daemonStart) {
+ throw new IllegalStateException("Main called under a daemon");
+ }
+ }
+
+ public void init(String[] args) throws Exception {
+ if (initialized) {
+ throw new IllegalStateException("Already run once.");
+ }
+ initialized = true;
+ if (myJarPath != null) {
+ kernelClass = new BootstrapClassLoader().loadClass(JJ_KERNEL_CLASS);
+
+ if (!daemonStart && args.length == 1 && "install".equals(args[0])) {
+ // we just stop here. the class loader made the install
+ return;
+ }
+
+ } else {
+ System.out.println("⟾⟾⟾⟾ ⟾⟾ ⟾⟾ ⟾⟾ ⟾⟾ ⟾⟾ ⟾⟾ ⟾⟾ ⟾⟾");
+ System.out.println("⟾⟾ WARNING - this operation is not fully implemented.");
+ System.out.println("⟾⟾ For best results, you should package this project");
+ System.out.println("⟾⟾ and run from the resulting jar.");
+ System.out.println("⟾⟾");
+ System.out.println("⟾⟾ export $JAVA_HOME=[JDK 7 home]");
+ System.out.println("⟾⟾");
+ System.out.println("⟾⟾ mvn clean install");
+ System.out.println("⟾⟾");
+ System.out.println("⟾⟾ $JAVA_HOME/bin/java -jar server/distro/target/" + Version.name + "-" + Version.version + "-all.jar");
+ System.out.println("⟾⟾");
+ System.out.println("⟾⟾⟾⟾ ⟾⟾ ⟾⟾ ⟾⟾ ⟾⟾ ⟾⟾ ⟾⟾ ⟾⟾ ⟾⟾");
+
+ return;
+ }
+ kernelInstance = kernelClass.getConstructor(args.getClass(), Boolean.TYPE).newInstance(args, daemonStart);
+ }
+
+ public void start() throws Exception {
+ kernelClass.getMethod("start").invoke(kernelInstance);
+ }
+
+ public void stop() throws Exception {
+ kernelClass.getMethod("stop").invoke(kernelInstance);
+ }
+
+ public void destroy() {
+ try {
+ kernelClass.getMethod("dispose").invoke(kernelInstance);
+ } catch (Exception e) {
+ System.err.println("Trouble shutting down");
+ e.printStackTrace();
+ }
+ }
+
+ private static final class BasicSelfInstaller {
+
+ private Path myLibPath;
+
+ private void install() {
+
+ try (FileSystem myJarFS = FileSystems.newFileSystem(myJarPath, null)) {
+
+ // basic self installation
+ // - ensure the existence of the system directory and the system/lib
+ // - if necessary, copy the libs from inside the jar to the lib directory
+ // for now, always do it. properly this would require version checks
+ // maybe have Maven put the classpath entries in the manifest? that might work
+ // since all the jars are named with versions
+
+ // whoever owns the installation directory is going to own
+ // everything we make - if we are running as root on unix
+ // we need to do this or we can't read our dependencies later
+ UserPrincipal owner = Files.getOwner(myJarPath);
+
+ myLibPath = Files.createDirectories(myJarPath.resolveSibling(SYSTEM_BASE_PATH).resolve(LIB_PATH));
+ Files.setOwner(myLibPath.getParent(), owner);
+ Files.setOwner(myLibPath, owner);
+
+ try (DirectoryStream<Path> libDir =
+ Files.newDirectoryStream(myJarFS.getPath(META_INF_PATH, LIB_PATH), "*.jar")) {
+ for (Path jarPath : libDir) {
+ Path jar = myLibPath.resolve(jarPath.getFileName().toString());
+ Files.copy(jarPath, jar, COPY_ATTRIBUTES, REPLACE_EXISTING);
+ Files.setOwner(jar, owner);
+ }
+ }
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Could not open", ioe);
+ }
+ }
+
+ }
+
+ private static final class BootstrapClassLoader
+ extends ClassLoader{
+
+ static {
+ // does this speed up the start-up? exciting...
+ registerAsParallelCapable();
+ }
+
+ private final BasicSelfInstaller installer = new BasicSelfInstaller();
+
+ private BootstrapClassLoader() {
+ // whatever loaded this class is the root of all classloaders in the system
+ super(JJ.class.getClassLoader());
+
+ // this stuff does not belong here at all
+ installer.install();
+
+ }
+
+ private DirectoryStream<Path> libJarsStream() throws IOException {
+ return Files.newDirectoryStream(installer.myLibPath, "*.jar");
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ try (DirectoryStream<Path> libDir = libJarsStream()) {
+ for (Path jarPath : libDir) {
+ try (FileSystem myJarFS = FileSystems.newFileSystem(jarPath, null)) {
+ Path attempt = myJarFS.getPath("/" + name.replace('.', '/') + ".class");
+ if (Files.exists(attempt)) {
+ byte[] classBytes = Files.readAllBytes(attempt);
+ return defineClass(name, classBytes, 0, classBytes.length);
+ }
+ }
+ }
+
+ } catch (IOException e) {
+ System.err.printf("Something went wrong reading a class [%s]\n", name);
+ e.printStackTrace();
+ throw new ClassNotFoundException(name, e);
+ }
+ System.err.printf("Couldn't find [%s]\n", name);
+ throw new ClassNotFoundException(name);
+ }
+
+ @Override
+ protected URL findResource(String name) {
+ URL result = null;
+ try (DirectoryStream<Path> libJars = libJarsStream()) {
+ for (Path jarPath : libJars) {
+ try (FileSystem myJarFS = FileSystems.newFileSystem(jarPath, null)) {
+ Path attempt = myJarFS.getPath("/" + name);
+ if (Files.exists(attempt)) {
+ result = attempt.toUri().toURL();
+ break;
+ }
+ }
+ }
+ } catch (IOException e) {}
+ return result;
+ }
+
+ @Override
+ protected Enumeration<URL> findResources(String name) throws IOException {
+ List<URL> result = new ArrayList<>();
+ try (DirectoryStream<Path> libJars = libJarsStream()) {
+ for (Path jarPath : libJars) {
+ try (FileSystem myJarFS = FileSystems.newFileSystem(jarPath, null)) {
+ Path attempt = myJarFS.getPath("/" + name);
+ if (Files.exists(attempt)) {
+ result.add(attempt.toUri().toURL());
+ }
+ }
+ }
+ }
+ return Collections.enumeration(result);
+ }
+ }
+}
92 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/JJLifecycleStrategy.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import jj.api.Dispose;
+import jj.api.Shutdown;
+import jj.api.Startup;
+
+import org.picocontainer.ComponentAdapter;
+import org.picocontainer.LifecycleStrategy;
+import org.picocontainer.PicoLifecycleException;
+
+/**
+ * <p>
+ * Simple mapping from picocontainer lifecycle to JOJ lifecycle.
+ * </p>
+ *
+ * <p>
+ * See {@link Startup}, {@link Shutdown}, and {@link Dispose}.
+ * </p>
+ *
+ * @author jason
+ *
+ */
+class JJLifecycleStrategy implements LifecycleStrategy {
+
+ @Override
+ public void start(Object component) {
+ invoke(component, Startup.class);
+ }
+
+ @Override
+ public void stop(Object component) {
+ invoke(component, Shutdown.class);
+ }
+
+ @Override
+ public void dispose(Object component) {
+ invoke(component, Dispose.class);
+ }
+
+ private void invoke(Object component, Class<? extends Annotation> annotation) {
+ for (Method method : component.getClass().getMethods()) {
+ if (method.isAnnotationPresent(annotation)) {
+ try {
+ method.invoke(component);
+ } catch (InvocationTargetException ite) {
+ throw new PicoLifecycleException(method, component, ite.getCause());
+ } catch (Exception e) {
+ throw new PicoLifecycleException(method, component, e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean hasLifecycle(Class<?> type) {
+ boolean result = false;
+ for (Method method : type.getMethods()) {
+ if (method.isAnnotationPresent(Startup.class) ||
+ method.isAnnotationPresent(Shutdown.class) ||
+ method.isAnnotationPresent(Dispose.class)) {
+ result = true;
+ break;
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public boolean isLazy(ComponentAdapter<?> adapter) {
+ return false; // we don't do lazy
+ }
+
+}
92 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/JJThreadPoolExecutor.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj;
+
+import java.util.TimeZone;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+
+abstract class JJThreadPoolExecutor
+ extends ThreadPoolExecutor
+ implements ThreadFactory {
+
+ static final TimeZone UTC = TimeZone.getTimeZone("UTC");
+
+ private final Logger logger;
+
+ JJThreadPoolExecutor(
+ final Logger logger,
+ final int corePoolSize,
+ final int maximumPoolSize,
+ final long keepAliveTime,
+ final TimeUnit unit,
+ final BlockingQueue<Runnable> workQueue
+ ) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
+ this.logger = logger;
+ this.setThreadFactory(this);
+ }
+
+ abstract String threadName();
+
+ abstract ThreadGroup threadGroup();
+
+ @Override
+ protected void beforeExecute(Thread t, Runnable r) {
+ logger.debug("Starting task [{}]", r.getClass());
+ super.beforeExecute(t, r);
+ }
+
+ @Override
+ protected void afterExecute(Runnable r, Throwable t) {
+
+ if (t != null) {
+ logger.error("Task [{}] ended with exception", r, t);
+ } else {
+ logger.debug("Completed task [{}]", r.getClass());
+ }
+ super.afterExecute(r, t);
+ }
+
+ @Override
+ public Thread newThread(final Runnable inner) {
+
+ String name = threadName();
+
+
+ logger.debug("Creating a thread [{}] from [{}]", name, Thread.currentThread().getName());
+
+ Thread thread = new Thread(
+ threadGroup(),
+ new Runnable() {
+
+ @Override
+ public void run() {
+ logger.trace("Thread starting");
+ inner.run();
+ logger.trace("Thread exiting");
+ }
+ },
+ name
+ );
+
+ return thread;
+ }
+}
285 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/Kernel.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj;
+
+import static org.picocontainer.Characteristics.NONE;
+import static org.jboss.netty.util.ThreadNameDeterminer.CURRENT;
+
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import jj.api.Version;
+
+import org.jboss.netty.logging.InternalLoggerFactory;
+import org.jboss.netty.logging.Slf4JLoggerFactory;
+import org.jboss.netty.util.ThreadRenamingRunnable;
+import org.picocontainer.DefaultPicoContainer;
+import org.picocontainer.MutablePicoContainer;
+import org.picocontainer.behaviors.Caching;
+import org.picocontainer.injectors.ConstructorInjection;
+import org.picocontainer.monitors.NullComponentMonitor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Puts the server components together and manages their lifecycle.
+ *
+ * @author jason
+ *
+ */
+public class Kernel {
+
+ static {
+ // this is just a convenience for jason - netty does not yet correctly
+ // determine the constraint level for JDK 7 on Mac.
+ System.setProperty("org.jboss.netty.channel.socket.nio.constraintLevel", "0");
+
+
+ // set netty to log to our logger
+ InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory());
+ // and tell netty to stop renaming our threads
+ ThreadRenamingRunnable.setThreadNameDeterminer(CURRENT);
+ }
+
+ private final Logger logger = LoggerFactory.getLogger(Kernel.class);
+
+ /**
+ * Injected into kernel objects so they can be controlled in arbitrary
+ * fashion by the kernel object.
+ *
+ * This design is only vaguely testable, which is worrisome.
+ * @author Jason Miller
+ *
+ */
+ final class Controller {
+
+ private final boolean daemon;
+
+ private Controller(final boolean daemon) {
+ this.daemon = daemon;
+ }
+
+ // synchronizing server start
+ // sequence is
+ // - start HttpServer initialization thread (Kernel)
+ // - awaitHttpSocketBound
+ // - bind socket (HttpServer)
+ // - awaitHttpServerStart
+ // at this point, drop privileges in the outside daemon code
+ // - notifyHttpServerStarted
+ private final ReentrantLock serverStartGate = new ReentrantLock();
+ private final Condition socketBound = serverStartGate.newCondition();
+ private final Condition serverStarted = serverStartGate.newCondition();
+ private volatile boolean socketBoundFlag = false;
+ private volatile boolean serverStartedFlag = false;
+ private void awaitHttpSocketBound() {
+ if (daemon) {
+ serverStartGate.lock();
+ try {
+ while (!socketBoundFlag) {
+ logger.info("awaiting socket bound");
+ socketBound.awaitUninterruptibly();
+ }
+ logger.info("socket bound notification delivered");
+ } finally {
+ serverStartGate.unlock();
+ }
+ }
+ }
+ void awaitHttpServerStart() {
+ if (daemon) {
+ serverStartGate.lock();
+ try {
+ socketBoundFlag = true;
+ logger.info("notifying socket bound");
+ socketBound.signal();
+ while (!serverStartedFlag) {
+ logger.info("awaiting server start");
+ serverStarted.awaitUninterruptibly();
+ }
+ logger.info("server start notification delivered");
+ } finally {
+ serverStartGate.unlock();
+ }
+ }
+ }
+ private void notifyHttpServerStarted() {
+ if (daemon) {
+ serverStartGate.lock();
+ try {
+ serverStartedFlag = true;
+ logger.info("notifying server started");
+ serverStarted.signal();
+ } finally {
+ serverStartGate.unlock();
+ }
+ }
+ }
+
+ private volatile boolean clearToServe = false;
+ public boolean clearToServe() {
+ return clearToServe;
+ }
+ }
+
+ /**
+ * Defines how the kernel manages its lifecycle,
+ * depending on how it was invoked
+ * @author Jason Miller
+ *
+ */
+ private interface KernelLifecycleStrategy {
+
+ void init();
+ void start();
+ void stop();
+ void dispose();
+ }
+
+ /**
+ * Kernel lifecycle when we are our own process
+ * @author Jason Miller
+ *
+ */
+ private final class ProcessLifecycle
+ implements KernelLifecycleStrategy {
+
+ @Override
+ public void init() {
+ logger.info("Process.init");
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ // this is to be split up later, when there is a way
+ // of externally controlling the kernel lifecycle.
+ // for now it can just stay in here
+ ProcessLifecycle.this.stop();
+ ProcessLifecycle.this.dispose();
+ }
+ });
+ coreContainer.start();
+ logger.info("Process.init complete");
+ }
+
+ @Override
+ public void start() {
+ sync.clearToServe = true;
+ }
+
+ @Override
+ public void stop() {
+ sync.clearToServe = false;
+ }
+
+ @Override
+ public void dispose() {
+ coreContainer.dispose();
+ }
+
+ }
+
+ private final class DaemonLifecycle
+ implements KernelLifecycleStrategy {
+
+ @Override
+ public void init() {
+ logger.info("Daemon.init");
+
+ new Thread("kernel initialization helper") {
+ @Override
+ public void run() {
+ // TODO Auto-generated method stub
+ // ugly mess of a thing, this is...
+ // gonna have to extract the lifecycle stuff,
+ // picocontainer keeps making me sad.
+ coreContainer.start();
+ }
+ }.start();
+
+ sync.awaitHttpSocketBound();
+ logger.info("Daemon.init complete");
+ }
+
+ @Override
+ public void start() {
+ sync.notifyHttpServerStarted();
+ sync.clearToServe = true;
+ }
+
+ @Override
+ public void stop() {
+ sync.clearToServe = false;
+ }
+
+ @Override
+ public void dispose() {
+ coreContainer.dispose();
+ }
+
+ }
+
+
+ private final Controller sync;
+
+ private final KernelLifecycleStrategy lifecycle;
+
+ /**
+ * The core PicoContainer used to hold the most basic server
+ * objects.
+ */
+ private final MutablePicoContainer coreContainer =
+ new DefaultPicoContainer(
+ new Caching().wrap(new ConstructorInjection()),
+ new JJLifecycleStrategy(),
+ null,
+ new NullComponentMonitor()
+ );
+
+ public Kernel(String[] args, boolean daemon) {
+
+ logger.info("Welcome to {} {}", Version.name, Version.version);
+
+ sync = new Controller(daemon);
+
+ lifecycle = daemon ? new DaemonLifecycle() : new ProcessLifecycle();
+
+ coreContainer.setName("Kernel");
+
+ coreContainer.addComponent(sync)
+ .addComponent(args)
+ .addComponent(KernelSettings.class)
+ .addComponent(HttpServer.class)
+ .addComponent(NettyRequestBridge.class)
+ .addComponent(KernelThreadPoolExecutor.class)
+ .addComponent(HttpThreadPoolExecutor.class)
+ .addAdapter(new MessageConveyorProvidingAdapter())
+ .as(NONE).addAdapter(new LocLoggerProvidingAdapter());
+
+ lifecycle.init();
+ }
+
+ public void start() {
+ lifecycle.start();
+ }
+
+ public void stop() {
+ lifecycle.stop();
+ }
+
+ public void dispose() {
+ lifecycle.dispose();
+ }
+}
59 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/KernelMessages.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj;
+
+import ch.qos.cal10n.BaseName;
+import ch.qos.cal10n.Locale;
+import ch.qos.cal10n.LocaleData;
+
+/**
+ * Keys for logging for the Kernel subsystems
+ *
+ * @author Jason Miller
+ *
+ */
+@BaseName("jj.kernel-messages")
+@LocaleData(
+ defaultCharset="UTF8",
+ value = {
+ @Locale("en")
+ }
+)
+public enum KernelMessages {
+ // general messages
+ ObjectInstantiating,
+ ObjectInstantiated,
+
+ KernelInitialized,
+ UsingUnknownLogger,
+ ReturningLogger,
+
+ // HttpServer messages
+ BindingPort,
+ InterfaceBound,
+ ReachedStartSyncPoint,
+ ConnectionsRemainPastTimeout,
+ HttpServerResourcesReleasing,
+ HttpServerResourcesReleased,
+
+ //NettyRequestBridge messages
+ ServerErrorFallbackResponse,
+
+ //KernelTaskMessages
+ KernelTaskRejected,
+ KernelTaskDone,
+ KernelThreadName
+}
141 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/KernelSettings.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj;
+
+import static jj.KernelSettings.Mode.DEV;
+
+import org.slf4j.Logger;
+
+/**
+ * Provides settings for the kernel. All options for all kernel managed objects
+ * should be here, even if just hardcoded for now. There are big plans for this,
+ * baby.
+ *
+ * @author jason
+ *
+ */
+public class KernelSettings {
+
+ public static enum Mode {
+ DEV,
+ TEST,
+ PROD
+ }
+
+ // probably moving this one out later, parsing services are
+ // sure to be a bigun
+ private static final class Parser {
+
+ private final String input;
+
+ Parser(String input) {
+ this.input = input;
+ }
+
+ public int parseInt(int asDefault) {
+ int result = asDefault;
+ try {
+ result = Integer.parseInt(input);
+ } catch (Exception e) {}
+ return result;
+ }
+ }
+
+ public KernelSettings(Logger logger, String[] args) {
+ logger.debug("Instantiating {}", KernelSettings.class);
+ // for now, just looking for a port
+ // later we can do something interesting...
+
+ for (String arg : args) {
+ logger.debug("Read argument {}", arg);
+ Parser parser = new Parser(arg);
+ if (parser.parseInt(0) != 0) {
+ logger.debug("It's a port!");
+ port = parser.parseInt(0);
+ }
+ }
+ }
+
+ private int port = 8080;
+ public int port() {
+ return port;
+ }
+
+ private int kernelThreadCoreCount = 4;
+ public int kernelThreadCoreCount() {
+ return kernelThreadCoreCount;
+ }
+
+ private int kernelThreadMaxCount = 20;
+ public int kernelThreadMaxCount() {
+ return kernelThreadMaxCount;
+ }
+
+ private long kernelThreadTimeOut = 20L;
+ public long kernelThreadTimeOut() {
+ return kernelThreadTimeOut;
+ }
+
+ private int httpThreadCoreCount = 4;
+ public int httpThreadCoreCount() {
+ return httpThreadCoreCount;
+ }
+
+ private int httpThreadMaxCount = 4;
+ public int httpThreadMaxCount() {
+ return httpThreadMaxCount;
+ }
+
+ private long httpThreadTimeOut = 20L;
+ public long httpThreadTimeOut() {
+ return httpThreadTimeOut;
+ }
+
+ private Mode mode = DEV;
+ public Mode mode() {
+ return mode;
+ }
+
+ private int httpMaxInitialLineLength = 2048;
+ public int httpMaxInitialLineLength() {
+ return httpMaxInitialLineLength;
+ }
+
+ private int httpMaxHeaderSize = 8192;
+ public int httpMaxHeaderSize() {
+ return httpMaxHeaderSize;
+ }
+
+ private int httpMaxChunkSize = 8192;
+ public int httpMaxChunkSize() {
+ return httpMaxChunkSize;
+ }
+
+ private int httpMaxRequestContentLength = 65536;
+ public int httpMaxRequestContentLength() {
+ return httpMaxRequestContentLength;
+ }
+
+ private int httpCompressionLevel = 6;
+ public int httpCompressionLevel() {
+ return httpCompressionLevel;
+ }
+
+ private int httpMaxShutdownTimeout = 30;
+ public int httpMaxShutdownTimeout() {
+ return httpMaxShutdownTimeout;
+ }
+}
45 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/KernelTask.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj;
+
+import java.nio.channels.CompletionHandler;
+import java.util.concurrent.FutureTask;
+
+/**
+ * A two-part task to process some sort of potentially blocking
+ * service, like file i/o. Basic concept is the first task is the
+ * i/o itself, second task is what to do with the result.
+ *
+ * Probably needs to be not tied to the kernel pool, since there will
+ * likely be a separate thread pool for non-HTTP I/O and a thread pool
+ * for app tasks.
+ *
+ * @author Jason Miller
+ *
+ */
+public class KernelTask<T> extends FutureTask<T> {
+
+ public KernelTask(Runnable task, CompletionHandler<?, ?> completion) {
+ super(task, null);
+
+ }
+
+ @Override
+ protected void done() {
+ // gah gah gah gah gah
+ }
+}
+
119 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/KernelThreadPoolExecutor.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static jj.KernelMessages.*;
+
+import java.util.Date;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.RunnableFuture;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.slf4j.cal10n.LocLogger;
+
+import ch.qos.cal10n.MessageConveyor;
+
+public class KernelThreadPoolExecutor
+ extends JJThreadPoolExecutor
+ implements RejectedExecutionHandler {
+
+ private final class KernelTask<V>
+ extends FutureTask<V> {
+
+ /**
+ * @param callable
+ */
+ public KernelTask(Callable<V> callable) {
+ super(callable);
+ }
+
+ /**
+ *
+ */
+ public KernelTask(Runnable runnable, V result) {
+ super(runnable, result);
+ }
+
+ @Override
+ protected void done() {
+ logger.info(KernelTaskDone);
+ }
+
+ }
+
+ private final LocLogger logger;
+
+ private final MessageConveyor messageConveyor;
+
+ private final AtomicInteger idSource = new AtomicInteger(1);
+
+ private final ThreadGroup threadGroup = new ThreadGroup(KernelThreadPoolExecutor.class.getSimpleName());
+
+ private final Kernel.Controller controller;
+
+ public KernelThreadPoolExecutor(
+ final LocLogger logger,
+ final MessageConveyor messageConveyor,
+ final KernelSettings mainSettings,
+ final Kernel.Controller controller
+ ) {
+ super(
+ logger,
+ mainSettings.kernelThreadCoreCount(),
+ mainSettings.kernelThreadMaxCount(),
+ mainSettings.kernelThreadTimeOut(), SECONDS,
+ new LinkedBlockingQueue<Runnable>()
+ );
+ this.logger = logger;
+ this.messageConveyor = messageConveyor;
+ this.setRejectedExecutionHandler(this);
+ this.controller = controller;
+ logger.debug(ObjectInstantiated, KernelThreadPoolExecutor.class);
+ }
+
+ @Override
+ protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
+ return new KernelTask<T>(callable);
+ }
+
+ protected <T> RunnableFuture<T> newTaskFor(final Runnable runnable, final T value) {
+ return new KernelTask<T>(runnable, value);
+ };
+
+ @Override
+ public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+ logger.error(KernelTaskRejected);
+ // switch the io threads into 503 mode?
+ r.run(); // this can't happen right now anyway, so why not
+ }
+
+ String threadName() {
+ return messageConveyor.getMessage(KernelThreadName,
+ idSource.getAndIncrement(),
+ new Date()
+ );
+ }
+
+ ThreadGroup threadGroup() {
+ return threadGroup;
+ }
+
+}
69 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/LocLoggerProvidingAdapter.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj;
+
+import static jj.KernelMessages.*;
+
+import java.lang.reflect.Type;
+
+import org.picocontainer.ComponentAdapter;
+import org.picocontainer.PicoCompositionException;
+import org.picocontainer.PicoContainer;
+import org.picocontainer.injectors.FactoryInjector;
+import org.picocontainer.injectors.InjectInto;
+
+import org.slf4j.cal10n.LocLogger;
+import org.slf4j.cal10n.LocLoggerFactory;
+
+import ch.qos.cal10n.MessageConveyor;
+
+/**
+ * Adapter to provide Class-specific Logger instances from an SLF4J
+ * LoggerFactory.
+ * @author jason
+ *
+ */
+final class LocLoggerProvidingAdapter extends FactoryInjector<LocLogger> {
+
+ private static final String UNKNOWN_LOGGER_NAME = "jj.Unknown";
+
+ @Override
+ public LocLogger getComponentInstance(PicoContainer container, Type into)
+ throws PicoCompositionException {
+
+ MessageConveyor messageConveyor = container.getComponent(MessageConveyor.class);
+
+ LocLoggerFactory factory = new LocLoggerFactory(messageConveyor);
+
+ LocLogger logger = factory.getLocLogger(LocLoggerProvidingAdapter.class);
+
+ if (into == null ||
+ into == ComponentAdapter.NOTHING.class) {
+ logger.warn(UsingUnknownLogger);
+ factory.getLocLogger(UNKNOWN_LOGGER_NAME);
+ }
+
+ String loggerType = into.toString();
+ if (into.getClass() == InjectInto.class) {
+ loggerType = ((InjectInto)into).getIntoClass().getName();
+ }
+
+ logger.trace(ReturningLogger, loggerType);
+
+ return factory.getLocLogger(loggerType);
+ }
+
+}
45 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/MessageConveyorProvidingAdapter.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj;
+
+import java.lang.reflect.Type;
+import java.util.Locale;
+
+import org.picocontainer.PicoContainer;
+import org.picocontainer.injectors.FactoryInjector;
+
+import ch.qos.cal10n.MessageConveyor;
+
+/**
+ * @author Jason Miller
+ *
+ */
+public class MessageConveyorProvidingAdapter extends FactoryInjector<MessageConveyor> {
+
+ private final MessageConveyor messageConveyor;
+
+ public MessageConveyorProvidingAdapter() {
+ // need to decide if the default locale can be resolved, and if not, then
+ // we default to English. but later.
+ messageConveyor = new MessageConveyor(Locale.getDefault());
+ }
+
+ @Override
+ public MessageConveyor getComponentInstance(PicoContainer container, Type into) {
+ return messageConveyor;
+ }
+
+}
283 JibbrJabbr-server/JibbrJabbr-kernel/src/main/java/jj/NettyRequestBridge.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2012 Jason Miller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jj;
+
+import static jj.KernelMessages.*;
+import static org.jboss.netty.handler.codec.http.HttpHeaders.*;
+import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*;
+import static org.jboss.netty.handler.codec.http.HttpMethod.GET;
+import static org.jboss.netty.handler.codec.http.HttpResponseStatus.*;
+import static org.jboss.netty.handler.codec.http.HttpVersion.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutorService;
+
+import jj.html.HTMLFragment;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelFuture;
+import org.jboss.netty.channel.ChannelFutureListener;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ExceptionEvent;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
+import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
+import org.jboss.netty.handler.codec.http.HttpHeaders;
+import org.jboss.netty.handler.codec.http.HttpMethod;
+import org.jboss.netty.handler.codec.http.HttpRequest;
+import org.jboss.netty.handler.codec.http.HttpResponse;
+import org.jboss.netty.handler.codec.http.HttpResponseStatus;
+import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
+import org.jboss.netty.handler.codec.http.websocketx.PingWebSocketFrame;
+import org.jboss.netty.handler.codec.http.websocketx.PongWebSocketFrame;
+import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+import org.jboss.netty.handler.codec.http.websocketx.WebSocketFrame;
+import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
+import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
+import org.jboss.netty.util.CharsetUtil;
+import org.slf4j.cal10n.LocLogger;
+
+import ch.qos.cal10n.MessageConveyor;
+
+/**
+ * Pulls the
+ * @author Jason Miller
+ *
+ */
+public class NettyRequestBridge extends SimpleChannelUpstreamHandler {
+
+ // still doesn't belong here but it's simpler to move out now
+
+ private static final HTMLFragment index;
+ private static final HTMLFragment error404;
+ private static final HTMLFragment error405;
+
+ static {
+ Path jar = JJ.jarForClass(NettyRequestBridge.class);
+ try (FileSystem jarFS = FileSystems.newFileSystem(jar, null)) {
+ index = new HTMLFragment(jarFS.getPath("jj", "assets", "index.html"));
+ error404 = new HTMLFragment(jarFS.getPath("jj", "errors", "404.html"));
+ error405 = new HTMLFragment(jarFS.getPath("jj", "errors", "405.html"));
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private final LocLogger logger;
+
+ private final ExecutorService requestExecutor;
+
+ private final Kernel.Controller controller;
+
+ private final byte[] favicon;
+
+ private final HttpResponse error503;
+
+ public NettyRequestBridge(
+ final MessageConveyor messageConveyor,
+ final LocLogger logger,
+ final KernelThreadPoolExecutor requestExecutor,
+ final Kernel.Controller controller
+ ) throws Exception {
+
+ assert messageConveyor != null;
+ assert logger != null;
+ assert requestExecutor != null;
+ assert controller != null;
+
+ logger.debug(ObjectInstantiating, NettyRequestBridge.class);
+
+ this.logger = logger;
+ this.requestExecutor = requestExecutor;
+ this.controller = controller;
+
+ error503 = new DefaultHttpResponse(HTTP_1_1, SERVICE_UNAVAILABLE);
+ error503.setHeader(CONTENT_TYPE, "text/html; charset=" + CharsetUtil.UTF_8.name());
+ error503.setContent(
+ ChannelBuffers.wrappedBuffer(
+ messageConveyor.getMessage(ServerErrorFallbackResponse).getBytes(CharsetUtil.UTF_8)
+ )
+ );
+ // did this work?
+ logger.info(messageConveyor.getMessage(ServerErrorFallbackResponse));
+
+ try (InputStream indexStream = NettyRequestBridge.class.getResourceAsStream("assets/favicon.ico")) {
+ assert indexStream != null;
+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024)) {
+ byte[] buffer = new byte[1024];
+ int read = -1;
+ while ((read = indexStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, read);
+ }
+ favicon = outputStream.toByteArray();
+ }
+ }
+ }
+
+ @Override
+ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
+
+ // if we're paused, return a 503 directly
+ if (controller.clearToServe()) {
+ // if it's a websocket request, adjust the pipeline, then... i dunno
+ // otherwise make a kernel task to process it
+ Object msg = e.getMessage();
+ if (msg instanceof HttpRequest) {
+ HttpRequest request = (HttpRequest)e.getMessage();
+ if (WEBSOCKET_URI.equals(request.getUri())) {
+ // Handshake
+ WebSocketServerHandshakerFactory wsFactory =
+ new WebSocketServerHandshakerFactory(getWebSocketLocation(request), null, false);
+ WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(request);
+ if (handshaker == null) {
+ wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel());
+ } else {
+ handshaker.handshake(ctx.getChannel(), request);
+ ctx.setAttachment(handshaker);
+ }
+ } else {
+ requestExecutor.execute(new HttpResponseTask(request, e.getChannel()));
+ }
+ } else if (msg instanceof WebSocketFrame) {
+ handleWebSocketFrame(ctx, (WebSocketFrame) msg);
+ }
+ } else {
+ e.getChannel()
+ .write(error503)
+ .addListener(ChannelFutureListener.CLOSE);
+ }
+ }
+
+ private static final String WEBSOCKET_URI = "/websocket";
+
+ private String getWebSocketLocation(HttpRequest req) {
+ return "ws://" + req.getHeader(HttpHeaders.Names.HOST) + WEBSOCKET_URI;
+ }
+
+ // handle this in the I/O thread for now
+ private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
+ // Check for closing frame
+ if (frame instanceof CloseWebSocketFrame) {
+ ((WebSocketServerHandshaker)ctx.getAttachment()).close(ctx.getChannel(), (CloseWebSocketFrame) frame);
+
+ } else if (frame instanceof PingWebSocketFrame) {
+ ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData()));
+
+ } else {
+
+ // Send the uppercase string back.
+ String request = ((TextWebSocketFrame) frame).getText();
+ logger.debug("Channel {} received {}", ctx.getChannel().getId(), request);
+ ctx.getChannel().write(new TextWebSocketFrame(request.toUpperCase()));
+ }
+ }
+
+ private final class HttpResponseTask implements Runnable {
+
+ private final HttpRequest request;
+ private final String uri;
+ private final HttpMethod method;
+ private final Channel responseChannel;
+
+ HttpResponseTask(final HttpRequest request, final Channel responseChannel) {
+ this.request = request;
+ this.uri = request.getUri();
+ this.method = request.getMethod();
+ this.responseChannel = responseChannel;
+ }
+
+ @Override
+ public void run() {
+ logger.info("Servicing {} for {}", method, uri);
+
+ if (GET != method) {
+ send405();
+ } else {
+ if (is100ContinueExpected(request)) {
+ send100Continue();
+ } if ("/favicon.ico".equals(uri)) {
+ sendFavicon();
+ } else if ("/".equals(uri) || "/index".equals(uri)) {
+ sendIndex();
+ } else {
+ send404();
+ }
+ }
+ }
+
+ private void send100Continue() { // no need to be fancy here
+ HttpResponse response = new DefaultHttpResponse(HTTP_1_1, CONTINUE);
+ responseChannel.write(response);
+ }
+
+ private void send404() {
+ writeResponse(NOT_FOUND, ChannelBuffers.copiedBuffer(error404.element().html(), CharsetUtil.UTF_8),
+ "text/html; charset=UTF-8");
+ }
+
+ private void send405() {
+ writeResponse(METHOD_NOT_ALLOWED, ChannelBuffers.copiedBuffer(error405.element().html(), CharsetUtil.UTF_8),
+ "text/html; charset=UTF-8");
+ }
+
+ private void sendFavicon() {
+ writeResponse(OK, ChannelBuffers.wrappedBuffer(favicon), "image/vnd.microsoft.icon");
+ }
+
+ private void sendIndex() {
+ writeResponse(OK, ChannelBuffers.copiedBuffer(index.element().html(), CharsetUtil.UTF_8), "text/html; charset=UTF-8");
+ }
+
+ private void writeResponse(HttpResponseStatus status, ChannelBuffer content, String contentType) {
+ // Decide whether to close the connection or not. <