killbill-memoizeit

Details

diff --git a/.idea/dictionaries/pierre.xml b/.idea/dictionaries/pierre.xml
index e586f6b..b1bb5c9 100644
--- a/.idea/dictionaries/pierre.xml
+++ b/.idea/dictionaries/pierre.xml
@@ -2,6 +2,7 @@
   <dictionary name="pierre">
     <words>
       <w>aoped</w>
+      <w>guice</w>
       <w>jdbc</w>
       <w>killbill</w>
       <w>shiro</w>

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index e5836ee..79d5ebb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>com.ning.billing</groupId>
-        <version>0.4.15</version>
+        <version>0.4.16</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.6.17-SNAPSHOT</version>

server/pom.xml 9(+9 -0)

diff --git a/server/pom.xml b/server/pom.xml
index cf6afb6..760dd3a 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -25,6 +25,10 @@
     <artifactId>killbill-server</artifactId>
     <packaging>war</packaging>
     <name>killbill-server</name>
+    <properties>
+        <!-- http://jira.codehaus.org/browse/MRESOURCES-99 -->
+        <build.timestamp>${maven.build.timestamp}</build.timestamp>
+    </properties>
     <dependencies>
         <dependency>
             <groupId>ch.qos.logback</groupId>
@@ -37,6 +41,10 @@
             <scope>runtime</scope>
         </dependency>
         <dependency>
+            <groupId>com.dmurph</groupId>
+            <artifactId>JGoogleAnalyticsTracker</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
             <scope>compile</scope>
@@ -283,6 +291,7 @@
     <build>
         <resources>
             <resource>
+                <filtering>true</filtering>
                 <directory>${basedir}/src/main/resources</directory>
             </resource>
         </resources>
diff --git a/server/src/main/java/com/ning/billing/server/config/KillbillServerConfig.java b/server/src/main/java/com/ning/billing/server/config/KillbillServerConfig.java
index 7ca5c94..dca993b 100644
--- a/server/src/main/java/com/ning/billing/server/config/KillbillServerConfig.java
+++ b/server/src/main/java/com/ning/billing/server/config/KillbillServerConfig.java
@@ -16,6 +16,21 @@
 
 package com.ning.billing.server.config;
 
-public interface KillbillServerConfig {
+import org.skife.config.Config;
+import org.skife.config.Default;
+import org.skife.config.Description;
 
+import com.ning.billing.util.config.KillbillConfig;
+
+public interface KillbillServerConfig extends KillbillConfig {
+
+    @Config("killbill.server.multitenant")
+    @Default("true")
+    @Description("Whether multi-tenancy is enabled")
+    public boolean isMultiTenancyEnabled();
+
+    @Config("killbill.server.test.mode")
+    @Default("false")
+    @Description("Whether to start in test mode")
+    public boolean isTestModeEnabled();
 }
diff --git a/server/src/main/java/com/ning/billing/server/config/UpdateCheckConfig.java b/server/src/main/java/com/ning/billing/server/config/UpdateCheckConfig.java
new file mode 100644
index 0000000..713cdcc
--- /dev/null
+++ b/server/src/main/java/com/ning/billing/server/config/UpdateCheckConfig.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you 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 com.ning.billing.server.config;
+
+import java.net.URI;
+
+import org.skife.config.Config;
+import org.skife.config.Default;
+import org.skife.config.Description;
+
+import com.ning.billing.util.config.KillbillConfig;
+
+public interface UpdateCheckConfig extends KillbillConfig {
+
+    @Config("killbill.server.updateCheck.skip")
+    @Default("false")
+    @Description("Whether to skip update checks")
+    public boolean shouldSkipUpdateCheck();
+
+    @Config("killbill.server.updateCheck.url")
+    @Default("https://raw.github.com/killbill/killbill/master/server/src/main/resources/update-checker/killbill-server-update-list.properties")
+    @Description("URL to retrieve the latest version of Kill Bill")
+    public URI updateCheckURL();
+
+    @Config("killbill.server.updateCheck.connectTimeout")
+    @Default("3000")
+    @Description("Update check connection timeout")
+    public int updateCheckConnectionTimeout();
+}
diff --git a/server/src/main/java/com/ning/billing/server/filters/KillbillGuiceFilter.java b/server/src/main/java/com/ning/billing/server/filters/KillbillGuiceFilter.java
new file mode 100644
index 0000000..1a65128
--- /dev/null
+++ b/server/src/main/java/com/ning/billing/server/filters/KillbillGuiceFilter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you 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 com.ning.billing.server.filters;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.server.updatechecker.UpdateChecker;
+
+import com.google.inject.servlet.GuiceFilter;
+
+public class KillbillGuiceFilter extends GuiceFilter {
+
+    private static final Logger log = LoggerFactory.getLogger(KillbillGuiceFilter.class);
+
+    private final UpdateChecker checker = new UpdateChecker();
+
+    @Override
+    public void init(final FilterConfig filterConfig) throws ServletException {
+        super.init(filterConfig);
+
+        // At this point, Kill Bill server is fully initialized
+        log.info("Kill Bill server has started");
+
+        checker.check(filterConfig.getServletContext());
+    }
+}
diff --git a/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java b/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
index 923560c..6821eeb 100644
--- a/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
+++ b/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
@@ -22,6 +22,7 @@ import javax.management.MBeanServer;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletContextEvent;
 
+import org.skife.config.ConfigurationObjectFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -47,17 +48,15 @@ import net.sf.ehcache.management.ManagementService;
 public class KillbillGuiceListener extends SetupServer {
 
     public static final Logger logger = LoggerFactory.getLogger(KillbillGuiceListener.class);
-    public static final String KILLBILL_MULTITENANT_PROPERTY = "killbill.server.multitenant";
-    public static final String KILLBILL_TEST_MODE_PROPERTY = "killbill.server.test.mode";
 
+    private KillbillServerConfig config;
     private Injector injector;
     private DefaultLifecycle killbillLifecycle;
     private BusService killbillBusService;
     private KillbillEventHandler killbilleventHandler;
 
     protected Module getModule(final ServletContext servletContext) {
-        final boolean testModeEnabled = Boolean.parseBoolean(System.getProperty(KILLBILL_TEST_MODE_PROPERTY, "false"));
-        return new KillbillServerModule(servletContext, testModeEnabled);
+        return new KillbillServerModule(servletContext, config.isTestModeEnabled());
     }
 
     private void registerMBeansForCache(final CacheManager cacheManager) {
@@ -69,7 +68,7 @@ public class KillbillGuiceListener extends SetupServer {
 
     @Override
     public void contextInitialized(final ServletContextEvent event) {
-        final boolean multitenant = Boolean.parseBoolean(System.getProperty(KILLBILL_MULTITENANT_PROPERTY, "true"));
+        config = new ConfigurationObjectFactory(System.getProperties()).build(KillbillServerConfig.class);
 
         final ServerModuleBuilder builder = new ServerModuleBuilder()
                 .addConfig(KillbillServerConfig.class)
@@ -84,7 +83,7 @@ public class KillbillGuiceListener extends SetupServer {
                 .addJerseyResource("com.ning.billing.jaxrs.mappers")
                 .addJerseyResource("com.ning.billing.jaxrs.resources");
 
-        if (multitenant) {
+        if (config.isMultiTenancyEnabled()) {
             builder.addFilter("/*", TenantFilter.class);
         }
 
diff --git a/server/src/main/java/com/ning/billing/server/updatechecker/ClientInfo.java b/server/src/main/java/com/ning/billing/server/updatechecker/ClientInfo.java
new file mode 100644
index 0000000..1cb9f21
--- /dev/null
+++ b/server/src/main/java/com/ning/billing/server/updatechecker/ClientInfo.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you 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 com.ning.billing.server.updatechecker;
+
+import java.net.InetAddress;
+import java.util.Properties;
+
+import javax.servlet.ServletContext;
+
+import com.google.common.base.StandardSystemProperty;
+import com.google.common.base.Strings;
+
+/**
+ * Gather client-side information
+ * <p/>
+ * We try not to gather any personally identifiable information, only
+ * specifications about the installation (OS, JVM). This helps us
+ * focus our development efforts.
+ */
+public class ClientInfo {
+
+    private static final String UNKNOWN = "UNKNOWN";
+
+    private static int CLIENT_ID;
+
+    static {
+        try {
+            CLIENT_ID = InetAddress.getLocalHost().hashCode();
+        } catch (Throwable t) {
+            CLIENT_ID = 0;
+        }
+    }
+
+    private final ServletContext servletContext;
+    private final Properties props;
+
+    public ClientInfo(final ServletContext servletContext) {
+        this.servletContext = servletContext;
+        this.props = System.getProperties();
+    }
+
+    public String getServletMajorVersion() {
+        return getSanitizedString(String.valueOf(servletContext.getMajorVersion()));
+    }
+
+    public String getServletMinorVersion() {
+        return getSanitizedString(String.valueOf(servletContext.getMinorVersion()));
+    }
+
+    public String getServletEffectiveMajorVersion() {
+        return getSanitizedString(String.valueOf(servletContext.getEffectiveMajorVersion()));
+    }
+
+    public String getServletEffectiveMinorVersion() {
+        return getSanitizedString(String.valueOf(servletContext.getEffectiveMinorVersion()));
+    }
+
+    public String getServerInfo() {
+        return getSanitizedString(servletContext.getServerInfo());
+    }
+
+    public String getClientId() {
+        return String.valueOf(CLIENT_ID);
+    }
+
+    public String getJavaVersion() {
+        return getProperty(StandardSystemProperty.JAVA_VERSION);
+    }
+
+    public String getJavaVendor() {
+        return getProperty(StandardSystemProperty.JAVA_VENDOR);
+    }
+
+    public String getJavaVendorURL() {
+        return getProperty(StandardSystemProperty.JAVA_VENDOR_URL);
+    }
+
+    public String getJavaVMSpecificationVersion() {
+        return getProperty(StandardSystemProperty.JAVA_VM_SPECIFICATION_VERSION);
+    }
+
+    public String getJavaVMSpecificationVendor() {
+        return getProperty(StandardSystemProperty.JAVA_VM_SPECIFICATION_VENDOR);
+    }
+
+    public String getJavaVMSpecificationName() {
+        return getProperty(StandardSystemProperty.JAVA_VM_SPECIFICATION_NAME);
+    }
+
+    public String getJavaVMVersion() {
+        return getProperty(StandardSystemProperty.JAVA_VM_VERSION);
+    }
+
+    public String getJavaVMVendor() {
+        return getProperty(StandardSystemProperty.JAVA_VM_VENDOR);
+    }
+
+    public String getJavaVMName() {
+        return getProperty(StandardSystemProperty.JAVA_VM_NAME);
+    }
+
+    public String getJavaSpecificationVersion() {
+        return getProperty(StandardSystemProperty.JAVA_SPECIFICATION_VERSION);
+    }
+
+    public String getJavaSpecificationVendor() {
+        return getProperty(StandardSystemProperty.JAVA_SPECIFICATION_VENDOR);
+    }
+
+    public String getJavaSpecificationName() {
+        return getProperty(StandardSystemProperty.JAVA_SPECIFICATION_NAME);
+    }
+
+    public String getJavaClassVersion() {
+        return getProperty(StandardSystemProperty.JAVA_CLASS_VERSION);
+    }
+
+    public String getJavaCompiler() {
+        return getProperty(StandardSystemProperty.JAVA_COMPILER);
+    }
+
+    public String getPlatform() {
+        return getProperty(StandardSystemProperty.OS_ARCH);
+    }
+
+    public String getOSName() {
+        return getProperty(StandardSystemProperty.OS_NAME);
+    }
+
+    public String getOSArch() {
+        return getProperty(StandardSystemProperty.OS_ARCH);
+    }
+
+    public String getOSVersion() {
+        return getProperty(StandardSystemProperty.OS_VERSION);
+    }
+
+    private String getProperty(final StandardSystemProperty standardKey) {
+        return getSanitizedString(props.getProperty(standardKey.key(), UNKNOWN));
+    }
+
+    private String getSanitizedString(final String string) {
+        return Strings.isNullOrEmpty(string) ? UNKNOWN : string.trim();
+    }
+}
diff --git a/server/src/main/java/com/ning/billing/server/updatechecker/ProductInfo.java b/server/src/main/java/com/ning/billing/server/updatechecker/ProductInfo.java
new file mode 100644
index 0000000..ff0fac4
--- /dev/null
+++ b/server/src/main/java/com/ning/billing/server/updatechecker/ProductInfo.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you 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 com.ning.billing.server.updatechecker;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.Properties;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Strings;
+import com.google.common.io.InputSupplier;
+import com.google.common.io.Resources;
+
+/**
+ * Kill Bill specific information
+ * <p/>
+ * At build time, we generated a magic file (version.properties) which should be on the classpath.
+ */
+public class ProductInfo {
+
+    private static final Logger log = LoggerFactory.getLogger(ProductInfo.class);
+
+    private static final String KILLBILL_SERVER_VERSION_RESOURCE = "/com/ning/billing/server/version.properties";
+
+    private static final String UNKNOWN = "UNKNOWN";
+
+    private static final String PRODUCT_NAME = "product-name";
+    private static final String VERSION = "version";
+    private static final String BUILT_BY = "built-by";
+    private static final String BUILD_JDK = "build-jdk";
+    private static final String BUILD_TIME = "build-time";
+    private static final String ENTERPRISE = "enterprise";
+
+    private final Properties props = new Properties();
+
+    public ProductInfo() {
+        try {
+            parseProductInfo(KILLBILL_SERVER_VERSION_RESOURCE);
+        } catch (IOException e) {
+            log.debug("Unable to detect current product info", e);
+        }
+    }
+
+    private void parseProductInfo(final String resource) throws IOException {
+        final URL resourceURL = Resources.getResource(resource);
+        final InputSupplier<InputStreamReader> inputSupplier = Resources.newReaderSupplier(resourceURL, Charset.forName("UTF-8"));
+        props.load(inputSupplier.getInput());
+    }
+
+    public String getName() {
+        return getProperty(PRODUCT_NAME);
+    }
+
+    public String getVersion() {
+        return getProperty(VERSION);
+    }
+
+    public String getBuiltBy() {
+        return getProperty(BUILT_BY);
+    }
+
+    public String getBuildJdk() {
+        return getProperty(BUILD_JDK);
+    }
+
+    public String getBuildTime() {
+        return getProperty(BUILD_TIME);
+    }
+
+    public boolean isEnterprise() {
+        return Boolean.parseBoolean(props.getProperty(ENTERPRISE));
+    }
+
+    private String getProperty(final String key) {
+        return getSanitizedString(props.getProperty(key, UNKNOWN));
+    }
+
+    private String getSanitizedString(final String string) {
+        return Strings.isNullOrEmpty(string) ? UNKNOWN : string.trim();
+    }
+
+    @Override
+    public String toString() {
+        final String fullProductName = String.format("%s (%s)", getName(), isEnterprise() ? "enterprise" : "community");
+        return String.format("%s version %s was built on %s, with jdk %s by %s",
+                             fullProductName, getVersion(), getBuildTime(), getBuildJdk(), getBuiltBy());
+    }
+}
diff --git a/server/src/main/java/com/ning/billing/server/updatechecker/Tracker.java b/server/src/main/java/com/ning/billing/server/updatechecker/Tracker.java
new file mode 100644
index 0000000..bef870a
--- /dev/null
+++ b/server/src/main/java/com/ning/billing/server/updatechecker/Tracker.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you 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 com.ning.billing.server.updatechecker;
+
+import javax.servlet.ServletContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.server.config.UpdateCheckConfig;
+
+import com.dmurph.tracking.AnalyticsConfigData;
+import com.dmurph.tracking.JGoogleAnalyticsTracker;
+import com.dmurph.tracking.JGoogleAnalyticsTracker.GoogleAnalyticsVersion;
+
+public class Tracker {
+
+    private static final Logger log = LoggerFactory.getLogger(Tracker.class);
+    private static final String TRACKING_CODE = "UA-44821278-1";
+
+    // Information about this version of Kill Bill
+    final ProductInfo productInfo;
+    // Information about this JVM
+    private final ClientInfo clientInfo;
+    private final JGoogleAnalyticsTracker tracker;
+
+    public Tracker(final ProductInfo productInfo, final ServletContext context) {
+        this.productInfo = productInfo;
+        this.clientInfo = new ClientInfo(context);
+
+        final AnalyticsConfigData analyticsConfigData = new AnalyticsConfigData(TRACKING_CODE);
+        this.tracker = new JGoogleAnalyticsTracker(analyticsConfigData, GoogleAnalyticsVersion.V_4_7_2);
+    }
+
+    public void track() {
+        trackProperty("product", "name", productInfo.getName());
+        trackProperty("product", "version", productInfo.getVersion());
+        trackProperty("product", "builtBy", productInfo.getBuiltBy());
+        trackProperty("product", "buildJdk", productInfo.getBuildJdk());
+        trackProperty("product", "buildTime", productInfo.getBuildTime());
+        trackProperty("product", "enterprise", String.valueOf(productInfo.isEnterprise()));
+
+        trackProperty("client", "servletMajorVersion", clientInfo.getServletMajorVersion());
+        trackProperty("client", "servletMinorVersion", clientInfo.getServletMinorVersion());
+        trackProperty("client", "servletEffectiveMajorVersion", clientInfo.getServletEffectiveMajorVersion());
+        trackProperty("client", "servletEffectiveMinorVersion", clientInfo.getServletEffectiveMinorVersion());
+        trackProperty("client", "serverInfo", clientInfo.getServerInfo());
+        trackProperty("client", "clientId", clientInfo.getClientId());
+        trackProperty("client", "javaVersion", clientInfo.getJavaVersion());
+        trackProperty("client", "javaVendor", clientInfo.getJavaVendor());
+        trackProperty("client", "javaVendorURL", clientInfo.getJavaVendorURL());
+        trackProperty("client", "javaVMSpecificationVersion", clientInfo.getJavaVMSpecificationVersion());
+        trackProperty("client", "javaVMSpecificationVendor", clientInfo.getJavaVMSpecificationVendor());
+        trackProperty("client", "javaVMSpecificationName", clientInfo.getJavaVMSpecificationName());
+        trackProperty("client", "javaVMVersion", clientInfo.getJavaVMVersion());
+        trackProperty("client", "javaVMVendor", clientInfo.getJavaVMVendor());
+        trackProperty("client", "javaVMName", clientInfo.getJavaVMName());
+        trackProperty("client", "javaSpecificationVersion", clientInfo.getJavaSpecificationVersion());
+        trackProperty("client", "javaSpecificationVendor", clientInfo.getJavaSpecificationVendor());
+        trackProperty("client", "javaSpecificationName", clientInfo.getJavaSpecificationName());
+        trackProperty("client", "javaClassVersion", clientInfo.getJavaClassVersion());
+        trackProperty("client", "javaCompiler", clientInfo.getJavaCompiler());
+        trackProperty("client", "platform", clientInfo.getPlatform());
+        trackProperty("client", "osName", clientInfo.getOSName());
+        trackProperty("client", "osArch", clientInfo.getOSArch());
+        trackProperty("client", "osVersion", clientInfo.getOSVersion());
+    }
+
+    private void trackProperty(final String category, final String key, final String value) {
+        // Workaround for https://code.google.com/p/analytics-issues/issues/detail?id=219
+        String sanitizedValue = value;
+        sanitizedValue = sanitizedValue.replace('(', '-');
+        sanitizedValue = sanitizedValue.replace(')', '-');
+
+        log.debug("Tracking {}: {}={}", category, key, sanitizedValue);
+        tracker.trackEvent(category, key, sanitizedValue);
+    }
+}
diff --git a/server/src/main/java/com/ning/billing/server/updatechecker/UpdateChecker.java b/server/src/main/java/com/ning/billing/server/updatechecker/UpdateChecker.java
new file mode 100644
index 0000000..970341e
--- /dev/null
+++ b/server/src/main/java/com/ning/billing/server/updatechecker/UpdateChecker.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you 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 com.ning.billing.server.updatechecker;
+
+import java.io.IOException;
+
+import javax.servlet.ServletContext;
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.server.config.UpdateCheckConfig;
+
+public class UpdateChecker {
+
+    private static final Logger log = LoggerFactory.getLogger(UpdateChecker.class);
+
+    final UpdateCheckConfig config = new ConfigurationObjectFactory(System.getProperties()).build(UpdateCheckConfig.class);
+
+    public void check(final ServletContext servletContext) {
+        log.info("For Kill Bill Commercial Support, visit http://thebillingproject.com or send an email to support@thebillingproject.com");
+
+        if (shouldSkipUpdateCheck()) {
+            return;
+        }
+
+        final Thread t = new Thread() {
+            @Override
+            public void run() {
+                try {
+                    doCheck(servletContext);
+                } catch (IOException e) {
+                    // Don't pollute logs, maybe no internet access?
+                    log.debug("Unable to perform update check", e);
+                }
+            }
+        };
+        t.setDaemon(true);
+        t.run();
+    }
+
+    private void doCheck(final ServletContext servletContext) throws IOException {
+        // Information about this version of Kill Bill
+        final ProductInfo productInfo = new ProductInfo();
+        // Information about other versions of Kill Bill
+        final UpdateListProperties updateListProperties = new UpdateListProperties(config.updateCheckURL().toURL(), config.updateCheckConnectionTimeout());
+
+        // Log generic information about Kill Bill
+        if (updateListProperties.getGeneralNotice() != null) {
+            log.info(updateListProperties.getGeneralNotice());
+        }
+
+        // Log generic information about this release
+        if (updateListProperties.getNoticeForVersion(productInfo.getVersion()) != null) {
+            log.info(updateListProperties.getNoticeForVersion(productInfo.getVersion()));
+        }
+
+        // Log if there is a new version of Kill Bill available
+        final StringBuilder updates = new StringBuilder();
+        for (final String update : updateListProperties.getUpdatesForVersion(productInfo.getVersion())) {
+            if (updates.length() > 0) {
+                updates.append(", ");
+            }
+
+            updates.append(update);
+            final String changeLog = updateListProperties.getReleaseNotesForVersion(update);
+            if (changeLog != null) {
+                updates.append(" [").append(changeLog).append("]");
+            }
+        }
+        if (updates.length() > 0) {
+            log.info("New update(s) found: " + updates.toString() + ". Please check http://kill-bill.org for the latest version.");
+        }
+
+        // Send anonymous data
+        final Tracker tracker = new Tracker(productInfo, servletContext);
+        tracker.track();
+    }
+
+
+    private boolean shouldSkipUpdateCheck() {
+        if (config.shouldSkipUpdateCheck()) {
+            return true;
+        }
+
+        try {
+            Class.forName("org.testng.Assert");
+            return true;
+        } catch (ClassNotFoundException e) {
+            return false;
+        }
+    }
+}
diff --git a/server/src/main/java/com/ning/billing/server/updatechecker/UpdateListProperties.java b/server/src/main/java/com/ning/billing/server/updatechecker/UpdateListProperties.java
new file mode 100644
index 0000000..b7c4061
--- /dev/null
+++ b/server/src/main/java/com/ning/billing/server/updatechecker/UpdateListProperties.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you 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 com.ning.billing.server.updatechecker;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.List;
+import java.util.Properties;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+
+public class UpdateListProperties {
+
+    private static final Logger log = LoggerFactory.getLogger(UpdateListProperties.class);
+
+    private static final Splitter SPLITTER = Splitter.on(",").trimResults().omitEmptyStrings();
+
+    private final Properties properties = new Properties();
+
+    public UpdateListProperties(final URL updateCheckURL, final int connectionTimeout) {
+        try {
+            loadUpdateListProperties(updateCheckURL, connectionTimeout);
+        } catch (IOException e) {
+            log.debug("Unable to load update list properties", e);
+        }
+    }
+
+    public String getGeneralNotice() {
+        return getProperty("general.notice");
+    }
+
+    public String getNoticeForVersion(final String version) {
+        return getProperty(version + ".notice");
+    }
+
+    public List<String> getUpdatesForVersion(final String version) {
+        final String updates = getProperty(version + ".updates");
+        return updates == null ? ImmutableList.<String>of() : SPLITTER.splitToList(updates);
+    }
+
+    public String getReleaseNotesForVersion(final String version) {
+        return getProperty(version + ".release-notes");
+    }
+
+    private String getProperty(final String key) {
+        return getSanitizedString(properties.getProperty(key));
+    }
+
+    private String getSanitizedString(final String string) {
+        return Strings.isNullOrEmpty(string) ? null : string.trim();
+    }
+
+    private void loadUpdateListProperties(final URL updateCheckURL, final int connectionTimeout) throws IOException {
+        log.debug("Checking {} for updates", updateCheckURL.toExternalForm());
+        final URLConnection connection = updateCheckURL.openConnection();
+        connection.setConnectTimeout(connectionTimeout);
+
+        final InputStream in = connection.getInputStream();
+        try {
+            properties.load(in);
+        } finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+    }
+}
diff --git a/server/src/main/resources/com/ning/billing/server/version.properties b/server/src/main/resources/com/ning/billing/server/version.properties
new file mode 100644
index 0000000..3ea3ec3
--- /dev/null
+++ b/server/src/main/resources/com/ning/billing/server/version.properties
@@ -0,0 +1,6 @@
+product-name    = ${project.name}
+version         = ${project.version}
+built-by        = ${user.name}
+build-jdk       = ${java.version}
+build-time      = ${build.timestamp}
+enterprise      = false
diff --git a/server/src/main/resources/update-checker/killbill-server-update-list.properties b/server/src/main/resources/update-checker/killbill-server-update-list.properties
new file mode 100644
index 0000000..4502bcb
--- /dev/null
+++ b/server/src/main/resources/update-checker/killbill-server-update-list.properties
@@ -0,0 +1,7 @@
+## Top level keys
+# general.notice = This notice should rarely, if ever, be used as everyone will see it
+
+## 0.6.16 -- latest release
+0.6.16.updates           =
+0.6.16.notices           = This is the latest GA release.
+0.6.16.release-notes     = http://kill-bill.org
diff --git a/server/src/main/webapp/WEB-INF/web.xml b/server/src/main/webapp/WEB-INF/web.xml
index 482e6c1..f5fac62 100644
--- a/server/src/main/webapp/WEB-INF/web.xml
+++ b/server/src/main/webapp/WEB-INF/web.xml
@@ -38,7 +38,7 @@
     <filter>
         <!-- Guice emulates Servlet API with DI -->
         <filter-name>guiceFilter</filter-name>
-        <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
+        <filter-class>com.ning.billing.server.filters.KillbillGuiceFilter</filter-class>
     </filter>
     <filter-mapping>
         <filter-name>guiceFilter</filter-name>