Since the Guice exception during injector creation appears unavoidable on GAE in prod (as opposed to the dev server) and, in any case, is harmless, there is no need to use an application and a servlet config. Much more like the "reference" tweetstore demo now.

git-svn-id: http://jclouds.googlecode.com/svn/trunk@2601 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
sharedocs1@gmail.com 2010-01-04 21:50:41 +00:00
parent 90c529ed47
commit a9b5680946
5 changed files with 107 additions and 191 deletions

View File

@ -24,9 +24,11 @@ It should not be regarded as a sample of how to write a web application using Sp
however! The original jclouds-demo-gae-tweetstore has been modified in as few places as however! The original jclouds-demo-gae-tweetstore has been modified in as few places as
possible; it has not been rewritten in the style of a Spring MVC application. possible; it has not been rewritten in the style of a Spring MVC application.
This sample uses the Google App Engine for Java SDK located at http://googleappengine.googlecode.com/files/appengine-java-sdk-1.2.5.zip This sample uses the Google App Engine for Java SDK located at
http://code.google.com/p/googleappengine/downloads/list
Please unzip the above file and modify your maven settings.xml like below before attempting to run 'mvn -Plive install' Please unzip the above file and modify your maven settings.xml like below before
attempting to run 'mvn -Plive install'
<profile> <profile>
<id>appengine</id> <id>appengine</id>

View File

@ -1,164 +0,0 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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 org.jclouds.demo.tweetstore.config;
import static com.google.appengine.api.labs.taskqueue.TaskOptions.Builder.url;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_BLOBSTORE_CONTEXTBUILDERS;
import static org.jclouds.demo.tweetstore.reference.TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.Properties;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.BlobStoreContextBuilder;
import org.jclouds.gae.config.GaeHttpCommandExecutorServiceModule;
import org.jclouds.twitter.TwitterClient;
import org.jclouds.twitter.TwitterContextFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import com.google.appengine.api.labs.taskqueue.Queue;
import com.google.appengine.api.labs.taskqueue.QueueFactory;
import com.google.appengine.api.labs.taskqueue.TaskOptions.Method;
import com.google.appengine.repackaged.com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.io.Closeables;
/**
* Reads properties and creates resources for servlets.
*
* @author Andrew Phillips
* @see SpringServletConfig
*/
@Configuration
public class SpringAppConfig extends LoggingConfig implements InitializingBean, DisposableBean, ResourceLoaderAware {
/*
* The call to TwitterContextFactory.createContext in initialize()
* must be carried out before the servlet context loads, otherwise the
* GAE will throw an access exception.
* For this reason, this code cannot be in the default servlet context
* loaded by the DispatcherServlet, but is executed in the root application
* context (which is processed by a listener).
*
* Note that none of the common annotations (such as @PostConstruct)
* will work here because Spring does not detect JSR-250 support (since it
* cannot load javax.annotation.Resource).
*/
Map<String, BlobStoreContext<?, ?>> providerTypeToBlobStoreMap;
TwitterClient twitterClient;
String container;
private final Properties props = new Properties();
/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@SuppressWarnings("unchecked")
@Override
public void afterPropertiesSet() throws Exception {
logger.trace("About to initialize properties.");
// shared across all blobstores and used to retrieve tweets
twitterClient = TwitterContextFactory.createContext(props,
new GaeHttpCommandExecutorServiceModule()).getApi();
// common namespace for storing tweets
container = checkNotNull(props.getProperty(PROPERTY_TWEETSTORE_CONTAINER),
PROPERTY_TWEETSTORE_CONTAINER);
ImmutableList<String> contextBuilderClassNames = ImmutableList.<String>of(
checkNotNull(props.getProperty(PROPERTY_BLOBSTORE_CONTEXTBUILDERS),
PROPERTY_BLOBSTORE_CONTEXTBUILDERS)
.split(","));
// instantiate and store references to all blobstores by provider name
providerTypeToBlobStoreMap = Maps.newHashMap();
for (String className : contextBuilderClassNames) {
Class<BlobStoreContextBuilder<?, ?>> builderClass;
Constructor<BlobStoreContextBuilder<?, ?>> constructor;
String name;
BlobStoreContext<?, ?> context;
try {
builderClass = (Class<BlobStoreContextBuilder<?, ?>>) Class.forName(className);
name = builderClass.getSimpleName().replaceAll("BlobStoreContextBuilder", "");
constructor = builderClass.getConstructor(Properties.class);
context = constructor.newInstance(props)
.withModules(new GaeHttpCommandExecutorServiceModule())
.buildContext();
} catch (Exception e) {
throw new RuntimeException("error instantiating " + className, e);
}
providerTypeToBlobStoreMap.put(name, context);
}
// get a queue for submitting store tweet requests
Queue queue = QueueFactory.getQueue("twitter");
// submit a job to store tweets for each configured blobstore
for (String name : providerTypeToBlobStoreMap.keySet()) {
queue.add(url("/store/do").header("context", name).method(Method.GET));
}
logger.trace("Properties initialized. TwitterClient: '%s', container: '%s', provider types: '%s'",
twitterClient, container, providerTypeToBlobStoreMap.keySet());
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
@Override
public void destroy() throws Exception {
logger.trace("About to close contexts.");
for (BlobStoreContext<?, ?> context : providerTypeToBlobStoreMap.values()) {
context.close();
}
logger.trace("Contexts closed.");
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.context.ResourceLoaderAware#setResourceLoader(org
* .springframework.core.io.ResourceLoader)
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
logger.trace("About to read properties from '%s'", "/WEB-INF/jclouds.properties");
InputStream input = null;
try {
input = resourceLoader.getResource("/WEB-INF/jclouds.properties").getInputStream();
props.load(input);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
Closeables.closeQuietly(input);
}
logger.trace("Properties successfully read.");
}
}

View File

@ -18,18 +18,31 @@
*/ */
package org.jclouds.demo.tweetstore.config; package org.jclouds.demo.tweetstore.config;
import static com.google.appengine.api.labs.taskqueue.TaskOptions.Builder.url;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_BLOBSTORE_CONTEXTBUILDERS;
import static org.jclouds.demo.tweetstore.reference.TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.Map; import java.util.Map;
import java.util.Properties;
import javax.inject.Inject; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.servlet.Servlet; import javax.servlet.Servlet;
import javax.servlet.ServletConfig; import javax.servlet.ServletConfig;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.BlobStoreContextBuilder;
import org.jclouds.demo.tweetstore.controller.AddTweetsController; import org.jclouds.demo.tweetstore.controller.AddTweetsController;
import org.jclouds.demo.tweetstore.controller.StoreTweetsController; import org.jclouds.demo.tweetstore.controller.StoreTweetsController;
import org.jclouds.demo.tweetstore.functions.ServiceToStoredTweetStatuses; import org.jclouds.demo.tweetstore.functions.ServiceToStoredTweetStatuses;
import org.jclouds.gae.config.GaeHttpCommandExecutorServiceModule;
import org.jclouds.twitter.TwitterClient;
import org.jclouds.twitter.TwitterContextFactory;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -39,7 +52,12 @@ import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.SimpleServletHandlerAdapter; import org.springframework.web.servlet.handler.SimpleServletHandlerAdapter;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import com.google.appengine.api.labs.taskqueue.Queue;
import com.google.appengine.api.labs.taskqueue.QueueFactory;
import com.google.appengine.api.labs.taskqueue.TaskOptions.Method;
import com.google.appengine.repackaged.com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.io.Closeables;
/** /**
* Creates servlets (using resources from the {@link SpringAppConfig}) and mappings. * Creates servlets (using resources from the {@link SpringAppConfig}) and mappings.
@ -51,20 +69,84 @@ import com.google.common.collect.Maps;
public class SpringServletConfig extends LoggingConfig implements ServletConfigAware { public class SpringServletConfig extends LoggingConfig implements ServletConfigAware {
private ServletConfig servletConfig; private ServletConfig servletConfig;
@Inject private Map<String, BlobStoreContext<?, ?>> providerTypeToBlobStoreMap;
private SpringAppConfig appConfig; private TwitterClient twitterClient;
private String container;
@SuppressWarnings("unchecked")
@PostConstruct
public void initialize() {
Properties props = loadJCloudsProperties();
logger.trace("About to initialize members.");
// shared across all blobstores and used to retrieve tweets
twitterClient = TwitterContextFactory.createContext(props,
new GaeHttpCommandExecutorServiceModule()).getApi();
// common namespace for storing tweets
container = checkNotNull(props.getProperty(PROPERTY_TWEETSTORE_CONTAINER),
PROPERTY_TWEETSTORE_CONTAINER);
ImmutableList<String> contextBuilderClassNames = ImmutableList.<String>of(
checkNotNull(props.getProperty(PROPERTY_BLOBSTORE_CONTEXTBUILDERS),
PROPERTY_BLOBSTORE_CONTEXTBUILDERS)
.split(","));
// instantiate and store references to all blobstores by provider name
providerTypeToBlobStoreMap = Maps.newHashMap();
for (String className : contextBuilderClassNames) {
Class<BlobStoreContextBuilder<?, ?>> builderClass;
Constructor<BlobStoreContextBuilder<?, ?>> constructor;
String name;
BlobStoreContext<?, ?> context;
try {
builderClass = (Class<BlobStoreContextBuilder<?, ?>>) Class.forName(className);
name = builderClass.getSimpleName().replaceAll("BlobStoreContextBuilder", "");
constructor = builderClass.getConstructor(Properties.class);
context = constructor.newInstance(props)
.withModules(new GaeHttpCommandExecutorServiceModule())
.buildContext();
} catch (Exception e) {
throw new RuntimeException("error instantiating " + className, e);
}
providerTypeToBlobStoreMap.put(name, context);
}
// get a queue for submitting store tweet requests
Queue queue = QueueFactory.getQueue("twitter");
// submit a job to store tweets for each configured blobstore
for (String name : providerTypeToBlobStoreMap.keySet()) {
queue.add(url("/store/do").header("context", name).method(Method.GET));
}
logger.trace("Members initialized. TwitterClient: '%s', container: '%s', provider types: '%s'",
twitterClient, container, providerTypeToBlobStoreMap.keySet());
}
private Properties loadJCloudsProperties() {
logger.trace("About to read properties from '%s'", "/WEB-INF/jclouds.properties");
Properties props = new Properties();
InputStream input = servletConfig.getServletContext()
.getResourceAsStream("/WEB-INF/jclouds.properties");
try {
props.load(input);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
Closeables.closeQuietly(input);
}
logger.trace("Properties successfully read.");
return props;
}
@Bean @Bean
public StoreTweetsController storeTweetsController() { public StoreTweetsController storeTweetsController() {
StoreTweetsController controller = new StoreTweetsController(appConfig.providerTypeToBlobStoreMap, StoreTweetsController controller = new StoreTweetsController(providerTypeToBlobStoreMap,
appConfig.container, appConfig.twitterClient); container, twitterClient);
injectServletConfig(controller); injectServletConfig(controller);
return controller; return controller;
} }
@Bean @Bean
public AddTweetsController addTweetsController() { public AddTweetsController addTweetsController() {
AddTweetsController controller = new AddTweetsController(appConfig.providerTypeToBlobStoreMap, AddTweetsController controller = new AddTweetsController(providerTypeToBlobStoreMap,
serviceToStoredTweetStatuses()); serviceToStoredTweetStatuses());
injectServletConfig(controller); injectServletConfig(controller);
return controller; return controller;
@ -72,20 +154,17 @@ public class SpringServletConfig extends LoggingConfig implements ServletConfigA
private void injectServletConfig(Servlet servlet) { private void injectServletConfig(Servlet servlet) {
logger.trace("About to inject servlet config '%s'", servletConfig); logger.trace("About to inject servlet config '%s'", servletConfig);
try { try {
servlet.init(checkNotNull(servletConfig)); servlet.init(checkNotNull(servletConfig));
} catch (ServletException exception) { } catch (ServletException exception) {
throw new BeanCreationException("Unable to instantiate " + servlet, exception); throw new BeanCreationException("Unable to instantiate " + servlet, exception);
} }
logger.trace("Successfully injected servlet config."); logger.trace("Successfully injected servlet config.");
} }
@Bean @Bean
ServiceToStoredTweetStatuses serviceToStoredTweetStatuses() { ServiceToStoredTweetStatuses serviceToStoredTweetStatuses() {
return new ServiceToStoredTweetStatuses(appConfig.providerTypeToBlobStoreMap, return new ServiceToStoredTweetStatuses(providerTypeToBlobStoreMap, container);
appConfig.container);
} }
@Bean @Bean
@ -107,6 +186,15 @@ public class SpringServletConfig extends LoggingConfig implements ServletConfigA
public HandlerAdapter servletHandlerAdapter() { public HandlerAdapter servletHandlerAdapter() {
return new SimpleServletHandlerAdapter(); return new SimpleServletHandlerAdapter();
} }
@PreDestroy
public void destroy() throws Exception {
logger.trace("About to close contexts.");
for (BlobStoreContext<?, ?> context : providerTypeToBlobStoreMap.values()) {
context.close();
}
logger.trace("Contexts closed.");
}
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.web.context.ServletConfigAware#setServletConfig(javax.servlet.ServletConfig) * @see org.springframework.web.context.ServletConfigAware#setServletConfig(javax.servlet.ServletConfig)

View File

@ -9,6 +9,10 @@
tries to load javax.annotation.Resource --> tries to load javax.annotation.Resource -->
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" /> <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean class="org.springframework.context.annotation.ConfigurationClassPostProcessor" /> <bean class="org.springframework.context.annotation.ConfigurationClassPostProcessor" />
<bean class="org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor">
<property name="initAnnotationType" value="javax.annotation.PostConstruct" />
<property name="destroyAnnotationType" value="javax.annotation.PreDestroy" />
</bean>
<bean class="org.jclouds.demo.tweetstore.config.SpringServletConfig" /> <bean class="org.jclouds.demo.tweetstore.config.SpringServletConfig" />
</beans> </beans>

View File

@ -22,21 +22,7 @@
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_5.xsd"
version="2.5"> version="2.5">
<display-name>jclouds-demo-gae-tweetstore-spring</display-name> <display-name>jclouds-tweetstore-spring</display-name>
<!-- use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>org.jclouds.demo.tweetstore.config.SpringAppConfig</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Servlets --> <!-- Servlets -->
<servlet> <servlet>