Initial commit.

git-svn-id: http://jclouds.googlecode.com/svn/trunk@2575 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
sharedocs1@gmail.com 2010-01-02 19:03:42 +00:00
parent 7c4a46fa6d
commit 414dcaa845
25 changed files with 2173 additions and 0 deletions

View File

@ -0,0 +1,8 @@
target
.settings
.classpath
.project
jclouds-gae-tweetstore-spring.iml
jclouds-gae-tweetstore-spring.ipr
jclouds-gae-tweetstore-spring.iws
temp-testng-customsuite.xml

View File

@ -0,0 +1,54 @@
====
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.
====================================================================
====
This samples uses the Google App Engine for Java SDK located at http://googleappengine.googlecode.com/files/appengine-java-sdk-1.2.5.zip
Please unzip the above file and modify your maven settings.xml like below before attempting to run 'mvn -Plive install'
<profile>
<id>appengine</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<appengine.home>/path/to/appengine-java-sdk-1.2.5</appengine.home>
<appengine.applicationid>yourappid</appengine.applicationid>
</properties>
</profile>
<profile>
<id>keys</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<jclouds.aws.accesskeyid>YOUR_ACCESS_KEY_ID</jclouds.aws.accesskeyid>
<jclouds.aws.secretaccesskey>YOUR_SECRET_KEY</jclouds.aws.secretaccesskey>
<jclouds.rackspace.user>YOUR_USER</jclouds.rackspace.user>
<jclouds.rackspace.key>YOUR_HEX_KEY</jclouds.rackspace.key>
<jclouds.azure.storage.account>YOUR_ACCOUNT</jclouds.azure.storage.account>
<jclouds.azure.storage.key>YOUR_BASE64_ENCODED_KEY</jclouds.azure.storage.key>
</properties>
</profile>
<repositories>
<repository>
<id>jclouds</id>
<url>http://jclouds.googlecode.com/svn/trunk/repo</url>
</repository>
</repositories>

View File

@ -0,0 +1,312 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
====================================================================
-->
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jclouds</groupId>
<artifactId>jclouds-demos-project</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>jclouds-demo-gae-tweetstore-spring</artifactId>
<packaging>war</packaging>
<name>Spring version of JClouds TweetStore for Google App Engine</name>
<description>JClouds TweetStore for Google App Engine using Spring for Dependency Injection</description>
<properties>
<!--
note you must set the property ${appengine.home} to a valid
extraction of appengine-java-sdk
-->
<appengine.home>C:/Users/aphillips/Temp/appengine-java-sdk-1.3.0</appengine.home>
<appengine.applicationid>jclouds-tweetstore</appengine.applicationid>
<devappserver.address>localhost</devappserver.address>
<devappserver.port>8088</devappserver.port>
<jclouds.tweetstore.container>jclouds-tweetstore</jclouds.tweetstore.container>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jclouds-blobstore</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jclouds-blobstore</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jclouds-core</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jclouds-twitter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jclouds-aws</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jclouds-azure</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jclouds-rackspace</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>jclouds-gae</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>displaytag</groupId>
<artifactId>displaytag</artifactId>
<version>1.2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<artifactId>standard</artifactId>
<groupId>taglibs</groupId>
<version>1.1.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<artifactId>jstl</artifactId>
<groupId>javax.servlet</groupId>
<version>1.1.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-el_1.0_spec</artifactId>
<version>1.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jsp_2.1_spec</artifactId>
<version>1.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-servlet_2.5_spec</artifactId>
<version>1.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.appengine</groupId>
<artifactId>appengine-api-labs</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>com.google.appengine</groupId>
<artifactId>appengine-tools-api</artifactId>
<version>1.3.0</version>
<scope>system</scope>
<systemPath>${appengine.home}/lib/appengine-tools-api.jar</systemPath>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<webResources>
<resource>
<directory>src/main/appengine</directory>
<targetPath>WEB-INF/</targetPath>
<filtering>true</filtering>
</resource>
</webResources>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>integration</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<systemProperties>
<property>
<name>appengine.home</name>
<value>${appengine.home}</value>
</property>
<property>
<name>devappserver.address</name>
<value>${devappserver.address}</value>
</property>
<property>
<name>devappserver.port</name>
<value>${devappserver.port}</value>
</property>
<property>
<name>warfile</name>
<value>${project.build.directory}/${project.artifactId}</value>
</property>
</systemProperties>
<additionalClasspathElements>
<additionalClasspathElement>${appengine.home}/lib/appengine-tools-api.jar</additionalClasspathElement>
</additionalClasspathElements>
<environmentVariables>
<DEBUG>true</DEBUG>
<SDK_BIN>${appengine.home}/bin</SDK_BIN>
<SDK_LIB>${appengine.home}/lib</SDK_LIB>
<SDK_CONFIG>${appengine.home}/config/sdk</SDK_CONFIG>
</environmentVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>live</id>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>integration</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<systemProperties>
<property>
<name>jclouds.twitter.user</name>
<value>${jclouds.twitter.user}</value>
</property>
<property>
<name>jclouds.twitter.password</name>
<value>${jclouds.twitter.password}</value>
</property>
<property>
<name>jclouds.azure.storage.account</name>
<value>${jclouds.azure.storage.account}</value>
</property>
<property>
<name>jclouds.azure.storage.key</name>
<value>${jclouds.azure.storage.key}</value>
</property>
<property>
<name>jclouds.rackspace.user</name>
<value>${jclouds.rackspace.user}</value>
</property>
<property>
<name>jclouds.rackspace.key</name>
<value>${jclouds.rackspace.key}</value>
</property>
<property>
<name>jclouds.emcsaas.uid</name>
<value>${jclouds.emcsaas.uid}</value>
</property>
<property>
<name>jclouds.emcsaas.key</name>
<value>${jclouds.emcsaas.key}</value>
</property>
<property>
<name>jclouds.aws.accesskeyid</name>
<value>${jclouds.aws.accesskeyid}</value>
</property>
<property>
<name>jclouds.aws.secretaccesskey</name>
<value>${jclouds.aws.secretaccesskey}</value>
</property>
<property>
<name>appengine.home</name>
<value>${appengine.home}</value>
</property>
<property>
<name>devappserver.address</name>
<value>${devappserver.address}</value>
</property>
<property>
<name>devappserver.port</name>
<value>${devappserver.port}</value>
</property>
<property>
<name>jclouds.tweetstore.container</name>
<value>${jclouds.tweetstore.container}</value>
</property>
<property>
<name>warfile</name>
<value>${project.build.directory}/${project.artifactId}</value>
</property>
</systemProperties>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
====================================================================
-->
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>${appengine.applicationid}</application>
<version>1</version>
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties" />
</system-properties>
</appengine-web-app>

View File

@ -0,0 +1,68 @@
#
#
# 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.
# ====================================================================
#
# Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
#
# ====================================================================
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF 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.
# ====================================================================
#
# A default java.util.logging configuration.
# (All App Engine logging is through java.util.logging by default).
#
# To use this configuration, copy it into your application's WEB-INF
# folder and add the following to your appengine-web.xml:
#
# <system-properties>
# <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
# </system-properties>
#
# Set the default logging level for all loggers to WARNING
.level = INFO
# Set the default logging level for ORM, specifically, to WARNING
org.jclouds.level=INFO
DataNucleus.JDO.level=WARNING
DataNucleus.Persistence.level=WARNING
DataNucleus.Cache.level=WARNING
DataNucleus.MetaData.level=WARNING
DataNucleus.General.level=WARNING
DataNucleus.Utility.level=WARNING
DataNucleus.Transaction.level=WARNING
DataNucleus.Datastore.level=WARNING
DataNucleus.ClassLoading.level=WARNING
DataNucleus.Plugin.level=WARNING
DataNucleus.ValueGeneration.level=WARNING
DataNucleus.Enhancer.level=WARNING
DataNucleus.SchemaTool.level=WARNING

View File

@ -0,0 +1,125 @@
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 javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
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.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 create resources for servlets.
*
* @author Andrew Phillips
* @see SpringServletConfig
*/
@Configuration
public class SpringAppConfig implements 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).
*/
private final Properties props = new Properties();
Map<String, BlobStoreContext<?, ?>> providerTypeToBlobStoreMap;
TwitterClient twitterClient;
String container;
@SuppressWarnings("unchecked")
@PostConstruct
public void initialize() {
// 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));
}
}
@PreDestroy
public void destroy() {
for (BlobStoreContext<?, ?> context : providerTypeToBlobStoreMap.values()) {
context.close();
}
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.context.ResourceLoaderAware#setResourceLoader(org
* .springframework.core.io.ResourceLoader)
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
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);
}
}
}

View File

@ -0,0 +1,66 @@
package org.jclouds.demo.tweetstore.config;
import java.util.Map;
import javax.inject.Inject;
import org.jclouds.demo.tweetstore.controller.AddTweetsController;
import org.jclouds.demo.tweetstore.controller.StoreTweetsController;
import org.jclouds.demo.tweetstore.functions.ServiceToStoredTweetStatuses;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.SimpleServletHandlerAdapter;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import com.google.common.collect.Maps;
/**
* Creates servlets,(using resources from the {@link SpringAppConfig}) and mappings.
*
* @author Andrew Phillips
*/
@Configuration
public class SpringServletConfig {
@Inject
private SpringAppConfig appConfig;
@Bean
public StoreTweetsController storeTweetsController() {
return new StoreTweetsController(appConfig.providerTypeToBlobStoreMap,
appConfig.container, appConfig.twitterClient);
}
@Bean
public AddTweetsController addTweetsController() {
return new AddTweetsController(appConfig.providerTypeToBlobStoreMap,
serviceToStoredTweetStatuses());
}
@Bean
ServiceToStoredTweetStatuses serviceToStoredTweetStatuses() {
return new ServiceToStoredTweetStatuses(appConfig.providerTypeToBlobStoreMap,
appConfig.container);
}
@Bean
public HandlerMapping handlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
Map<String, Object> urlMap = Maps.newHashMapWithExpectedSize(2);
urlMap.put("/store/*", storeTweetsController());
urlMap.put("/tweets/*", addTweetsController());
mapping.setUrlMap(urlMap);
/*
* "/store" and "/tweets" are part of the servlet mapping and thus stripped
* by the mapping if using default settings.
*/
mapping.setAlwaysUseFullPath(true);
return mapping;
}
@Bean
public HandlerAdapter servletHandlerAdapter() {
return new SimpleServletHandlerAdapter();
}
}

View File

@ -0,0 +1,96 @@
/**
*
* 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.controller;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.demo.tweetstore.domain.StoredTweetStatus;
import org.jclouds.demo.tweetstore.functions.ServiceToStoredTweetStatuses;
import org.jclouds.logging.Logger;
import com.google.appengine.repackaged.com.google.common.collect.Lists;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
/**
* Shows an example of how to use @{link BlobStoreContext} injected with Guice.
*
* @author Adrian Cole
*/
@Singleton
public class AddTweetsController extends HttpServlet implements
Function<Set<String>, List<StoredTweetStatus>> {
/** The serialVersionUID */
private static final long serialVersionUID = 3888348023150822683L;
private final Map<String, BlobStoreContext<?, ?>> contexts;
private final ServiceToStoredTweetStatuses blobStoreContextToContainerResult;
@Resource
protected Logger logger = Logger.NULL;
@Inject
public AddTweetsController(Map<String, BlobStoreContext<?, ?>> contexts,
ServiceToStoredTweetStatuses blobStoreContextToContainerResult) {
this.contexts = contexts;
this.blobStoreContextToContainerResult = blobStoreContextToContainerResult;
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
addMyTweetsToRequest(request);
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/tweets.jsp");
dispatcher.forward(request, response);
} catch (Exception e) {
logger.error(e, "Error listing containers");
throw new ServletException(e);
}
}
void addMyTweetsToRequest(HttpServletRequest request) throws InterruptedException,
ExecutionException, TimeoutException {
request.setAttribute("tweets", apply(contexts.keySet()));
}
public List<StoredTweetStatus> apply(Set<String> in) {
List<StoredTweetStatus> statuses = Lists.newArrayList();
for (Iterable<StoredTweetStatus> list : Iterables.transform(in,
blobStoreContextToContainerResult)) {
Iterables.addAll(statuses, list);
}
return statuses;
}
}

View File

@ -0,0 +1,129 @@
/**
*
* 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.controller;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.IOException;
import java.util.Map;
import java.util.SortedSet;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType;
import org.jclouds.blobstore.BlobMap;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
import org.jclouds.logging.Logger;
import org.jclouds.twitter.TwitterClient;
import org.jclouds.twitter.domain.Status;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
/**
* Grab tweets related to me and store them into blobstores
*
* @author Adrian Cole
*/
@Singleton
public class StoreTweetsController extends HttpServlet {
private static final class StatusToBlob implements Function<Status, Blob> {
private final BlobMap map;
private StatusToBlob(BlobMap map) {
this.map = map;
}
public Blob apply(Status from) {
Blob to = map.newBlob();
to.getMetadata().setContentType(MediaType.TEXT_PLAIN);
to.getMetadata().setName(from.getId() + "");
to.setPayload(from.getText());
to.getMetadata().getUserMetadata().put(TweetStoreConstants.SENDER_NAME,
from.getUser().getScreenName());
return to;
}
}
/** The serialVersionUID */
private static final long serialVersionUID = 7215420527854203714L;
private final Map<String, BlobStoreContext<?, ?>> contexts;
private final TwitterClient client;
private final String container;
@Resource
protected Logger logger = Logger.NULL;
@Inject
public StoreTweetsController(Map<String, BlobStoreContext<?, ?>> contexts,
@Named(TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER) final String container,
TwitterClient client) {
this.container = container;
this.contexts = contexts;
this.client = client;
}
@VisibleForTesting
void addMyTweets(String contextName, SortedSet<Status> allAboutMe) {
BlobStoreContext<?, ?> context = checkNotNull(contexts.get(contextName), "no context for "
+ contextName + " in " + contexts.keySet());
BlobMap map = context.createBlobMap(container);
for (Status status : allAboutMe) {
try {
map.put(status.getId() + "", new StatusToBlob(map).apply(status));
} catch (Exception e) {
logger.error(e, "Error storing tweet %s on map %s/%s", status.getId(), context,
container);
}
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (request.getHeader("X-AppEngine-QueueName") != null
&& request.getHeader("X-AppEngine-QueueName").equals("twitter")) {
try {
String contextName = checkNotNull(request.getHeader("context"),
"missing header context");
logger.info("retrieving tweets");
addMyTweets(contextName, client.getMyMentions());
logger.debug("done storing tweets");
response.setContentType(MediaType.TEXT_PLAIN);
response.getWriter().println("Done!");
} catch (Exception e) {
logger.error(e, "Error storing tweets");
throw new ServletException(e);
}
} else {
response.sendError(401);
}
}
}

View File

@ -0,0 +1,149 @@
/**
*
* 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.domain;
import java.io.Serializable;
/**
*
* @author Adrian Cole
*/
public class StoredTweetStatus implements Comparable<StoredTweetStatus>, Serializable {
/** The serialVersionUID */
private static final long serialVersionUID = -3257496189689220018L;
private final String service;
private final String host;
private final String container;
private final String id;
private final String from;
private final String tweet;
private final String status;
@Override
public String toString() {
return "StoredTweetStatus [container=" + container + ", from=" + from + ", host=" + host
+ ", id=" + id + ", service=" + service + ", status=" + status + ", tweet=" + tweet
+ "]";
}
public StoredTweetStatus(String service, String host, String container, String id, String from,
String tweet, String status) {
this.service = service;
this.host = host;
this.container = container;
this.id = id;
this.from = from;
this.tweet = tweet;
this.status = status;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((container == null) ? 0 : container.hashCode());
result = prime * result + ((from == null) ? 0 : from.hashCode());
result = prime * result + ((host == null) ? 0 : host.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((service == null) ? 0 : service.hashCode());
result = prime * result + ((tweet == null) ? 0 : tweet.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
StoredTweetStatus other = (StoredTweetStatus) obj;
if (container == null) {
if (other.container != null)
return false;
} else if (!container.equals(other.container))
return false;
if (from == null) {
if (other.from != null)
return false;
} else if (!from.equals(other.from))
return false;
if (host == null) {
if (other.host != null)
return false;
} else if (!host.equals(other.host))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (service == null) {
if (other.service != null)
return false;
} else if (!service.equals(other.service))
return false;
if (tweet == null) {
if (other.tweet != null)
return false;
} else if (!tweet.equals(other.tweet))
return false;
return true;
}
public String getService() {
return service;
}
public String getHost() {
return host;
}
public String getContainer() {
return container;
}
public String getFrom() {
return from;
}
public String getTweet() {
return tweet;
}
public String getStatus() {
return status;
}
public int compareTo(StoredTweetStatus o) {
if (id == null)
return -1;
return (int) ((this == o) ? 0 : id.compareTo(o.id));
}
public String getId() {
return id;
}
}

View File

@ -0,0 +1,72 @@
/**
*
* 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.functions;
import java.io.InputStream;
import javax.annotation.Resource;
import org.jclouds.blobstore.BlobMap;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.demo.tweetstore.domain.StoredTweetStatus;
import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
import org.jclouds.logging.Logger;
import org.jclouds.util.Utils;
import com.google.common.base.Function;
/**
*
* @author Adrian Cole
*/
public class KeyToStoredTweetStatus implements Function<String, StoredTweetStatus> {
private final String host;
private final BlobMap map;
private final String service;
private final String container;
@Resource
protected Logger logger = Logger.NULL;
KeyToStoredTweetStatus(BlobMap map, String service, String host, String container) {
this.host = host;
this.map = map;
this.service = service;
this.container = container;
}
public StoredTweetStatus apply(String id) {
String status;
String from;
String tweet;
try {
long start = System.currentTimeMillis();
Blob blob = map.get(id);
status = ((System.currentTimeMillis() - start) + "ms");
from = blob.getMetadata().getUserMetadata().get(TweetStoreConstants.SENDER_NAME);
tweet = Utils.toStringAndClose((InputStream) blob.getContent());
} catch (Exception e) {
logger.error(e, "Error listing container %s//%s/$s", service, container, id);
status = (e.getMessage());
tweet = "";
from = "";
}
return new StoredTweetStatus(service, host, container, id, from, tweet, status);
}
}

View File

@ -0,0 +1,71 @@
/**
*
* 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.functions;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.blobstore.BlobMap;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.demo.tweetstore.domain.StoredTweetStatus;
import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
import org.jclouds.logging.Logger;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
@Singleton
public class ServiceToStoredTweetStatuses implements Function<String, Iterable<StoredTweetStatus>> {
private final Map<String, BlobStoreContext<?,?>> contexts;
private final String container;
@Inject
public ServiceToStoredTweetStatuses(Map<String, BlobStoreContext<?,?>> contexts,
@Named(TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER) String container) {
this.contexts = contexts;
this.container = container;
}
@Resource
protected Logger logger = Logger.NULL;
public Iterable<StoredTweetStatus> apply(String service) {
BlobStoreContext<?, ?> context = contexts.get(service);
String host = context.getEndPoint().getHost();
try {
BlobMap blobMap = context.createBlobMap(container);
Set<String> blobs = blobMap.keySet();
return Iterables.transform(blobs, new KeyToStoredTweetStatus(blobMap, service, host,
container));
} catch (Exception e) {
StoredTweetStatus result = new StoredTweetStatus(service, host, container, null, null,
null, e.getMessage());
logger.error(e, "Error listing service %s", service);
return Collections.singletonList(result);
}
}
}

View File

@ -0,0 +1,29 @@
/**
*
* 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.reference;
/**
* Configuration properties and constants used in TweetStore connections.
*
* @author Adrian Cole
*/
public interface TweetStoreConstants {
public static final String PROPERTY_TWEETSTORE_CONTAINER = "jclouds.tweetstore.container";
public static final String SENDER_NAME = "jclouds.tweetstore.sendername";
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- the usual <context:annotation-config/> can't be used because the
CommonAnnotationBeanPostProcessor caused a security exception in GAE when it
tries to load javax.annotation.Resource -->
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean class="org.springframework.context.annotation.ConfigurationClassPostProcessor" />
<bean class="org.jclouds.demo.tweetstore.config.SpringServletConfig" />
</beans>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
====================================================================
-->
<queue-entries>
<queue>
<name>twitter</name>
<rate>1/m</rate>
</queue>
</queue-entries>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
====================================================================
-->
<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"
version="2.5">
<display-name>jclouds-demo-gae-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 -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- cannot use the AnnotationConfigWebApplicationContext as it causes a security
exception in GAE by trying to load javax.annotation.Resource -->
</servlet>
<!-- should be kept in sync with the mappings defined in SpringServletConfig -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/store/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/tweets/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

View File

@ -0,0 +1,31 @@
<%--
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.
====================================================================
--%>
<html>
<head>
<title>jclouds: anyweight cloudware for java</title>
</head>
<body>
<h2>Welcome!</h2>
Click
<a href="/tweets/get">here</a>
to see tweets about jclouds.
</body>
</html>

View File

@ -0,0 +1,108 @@
<%--
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.
====================================================================
--%>
<%@ page buffer="20kb"%>
<%@ taglib uri="http://displaytag.sf.net" prefix="display"%>
<html>
<head>
<title>jclouds: anyweight cloudware for java</title>
<style type="text/css">
<!--
table.staticheader {
text-decoration: none;
border: 1px solid #CCC;
width: 98%;
}
table.staticheader th {
padding: 3px 3px 3px 3px !important;
text-align:center;
}
table.staticheader td {
padding: 3px 3px 3px 3px !important;
}
table.staticheader thead tr {
position: relative;
height: 10px;
background-color: #D7E5F3;
}
table.staticheader tbody {
height:800px;
overflow-x:hidden;
overflow-y: auto;
overflow:scroll;
}
table.staticheader tbody tr {
height: auto;
white-space: nowrap;
}
table.staticheader tbody tr.odd {
background-color: #eee
}
table.staticheader tbody tr.tableRowEven,tr.even {
background-color: #ddd
}
table.staticheader tbody tr td:last-child {
padding-right: 20px;
}
table.staticheader tbody td {
padding: 2px 4px 2px 4px !important;
}
div.TableContainer {
height: 800px;
overflow-x:hidden;
overflow-y:auto;
}
-->
</style>
</head>
<body>
<h2>Tweets in Clouds</h2>
<table width="100%" border="0">
<tr>
<td>
<div class="TableContainer">
<display:table name="tweets" defaultsort="1" cellpadding="5" cellspacing="1" class="staticheader">
<display:column property="id" title="Tweet ID" />
<display:column property="from" title="Who Said it" />
<display:column property="tweet" title="Tweet" />
<display:column property="service" title="Cloud" />
<display:column property="host" title="Host" />
<display:column property="status" title="Status" />
</display:table>
</div>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,80 @@
/**
*
* 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.controller;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.jclouds.blobstore.AsyncBlobStore;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.integration.StubBlobStoreContextBuilder;
import org.jclouds.demo.tweetstore.domain.StoredTweetStatus;
import org.jclouds.demo.tweetstore.functions.ServiceToStoredTweetStatuses;
import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
import org.testng.annotations.Test;
import org.testng.collections.Maps;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
/**
* Tests behavior of {@code AddTweetsController}
*
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "tweetstore.AddTweetsControllerTest")
public class AddTweetsControllerTest {
Map<String, BlobStoreContext<?, ?>> createServices(String container)
throws InterruptedException, ExecutionException {
Map<String, BlobStoreContext<?, ?>> services = Maps.newHashMap();
for (String name : new String[] { "1", "2" }) {
BlobStoreContext<AsyncBlobStore, BlobStore> context = new StubBlobStoreContextBuilder()
.buildContext();
context.getAsyncBlobStore().createContainer(container).get();
Blob blob = context.getAsyncBlobStore().newBlob();
blob.getMetadata().setName("1");
blob.getMetadata().getUserMetadata().put(TweetStoreConstants.SENDER_NAME, "frank");
blob.setPayload("I love beans!");
context.getAsyncBlobStore().putBlob(container, blob).get();
services.put(name, context);
}
return services;
}
public void testStoreTweets() throws IOException, InterruptedException, ExecutionException {
String container = "container";
Map<String, BlobStoreContext<?, ?>> contexts = createServices(container);
ServiceToStoredTweetStatuses function = new ServiceToStoredTweetStatuses(contexts, container);
AddTweetsController controller = new AddTweetsController(contexts, function);
List<StoredTweetStatus> list = controller.apply(ImmutableSet.of("1", "2"));
assertEquals(list.size(), 2);
assertEquals(list, ImmutableList.of(new StoredTweetStatus("1", "localhost", container, "1",
"frank", "I love beans!", null), new StoredTweetStatus("2", "localhost", container,
"1", "frank", "I love beans!", null)));
}
}

View File

@ -0,0 +1,112 @@
/**
*
* 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.controller;
import static org.easymock.classextension.EasyMock.createMock;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.SortedSet;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import org.jclouds.blobstore.BlobMap;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.integration.StubBlobStoreContextBuilder;
import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
import org.jclouds.twitter.TwitterClient;
import org.jclouds.twitter.domain.Status;
import org.jclouds.twitter.domain.User;
import org.jclouds.util.Utils;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
/**
* Tests behavior of {@code StoreTweetsController}
*
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "tweetstore.StoreTweetsControllerTest")
public class StoreTweetsControllerTest {
TwitterClient createTwitterClient() {
return createMock(TwitterClient.class);
}
Map<String, BlobStoreContext<?, ?>> createBlobStores() throws InterruptedException,
ExecutionException {
Map<String, BlobStoreContext<?, ?>> contexts = ImmutableMap
.<String, BlobStoreContext<?, ?>> of("test1", new StubBlobStoreContextBuilder()
.buildContext(), "test2", new StubBlobStoreContextBuilder().buildContext());
for (BlobStoreContext<?, ?> blobstore : contexts.values()) {
blobstore.getAsyncBlobStore().createContainer("favo").get();
}
return contexts;
}
public void testStoreTweets() throws IOException, InterruptedException, ExecutionException {
Map<String, BlobStoreContext<?, ?>> stores = createBlobStores();
StoreTweetsController function = new StoreTweetsController(stores, "favo",
createTwitterClient());
SortedSet<Status> allAboutMe = Sets.newTreeSet();
User frank = new User();
frank.setScreenName("frank");
Status frankStatus = new Status();
frankStatus.setId(1);
frankStatus.setUser(frank);
frankStatus.setText("I love beans!");
User jimmy = new User();
jimmy.setScreenName("jimmy");
Status jimmyStatus = new Status();
jimmyStatus.setId(2);
jimmyStatus.setUser(jimmy);
jimmyStatus.setText("cloud is king");
allAboutMe.add(frankStatus);
allAboutMe.add(jimmyStatus);
function.addMyTweets("test1", allAboutMe);
function.addMyTweets("test2", allAboutMe);
for (Entry<String, BlobStoreContext<?, ?>> entry : stores.entrySet()) {
BlobMap map = entry.getValue().createBlobMap("favo");
Blob frankBlob = map.get("1");
assertEquals(frankBlob.getMetadata().getName(), "1");
assertEquals(frankBlob.getMetadata().getUserMetadata()
.get(TweetStoreConstants.SENDER_NAME), "frank");
assertEquals(frankBlob.getMetadata().getContentType(), "text/plain");
assertEquals(Utils.toStringAndClose((InputStream) frankBlob.getContent()), "I love beans!");
Blob jimmyBlob = map.get("2");
assertEquals(jimmyBlob.getMetadata().getName(), "2");
assertEquals(jimmyBlob.getMetadata().getUserMetadata()
.get(TweetStoreConstants.SENDER_NAME), "jimmy");
assertEquals(jimmyBlob.getMetadata().getContentType(), "text/plain");
assertEquals(Utils.toStringAndClose((InputStream) jimmyBlob.getContent()), "cloud is king");
}
}
}

View File

@ -0,0 +1,71 @@
/**
*
* 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.functions;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import org.jclouds.blobstore.AsyncBlobStore;
import org.jclouds.blobstore.BlobMap;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.integration.StubBlobStoreContextBuilder;
import org.jclouds.demo.tweetstore.domain.StoredTweetStatus;
import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
import org.testng.annotations.Test;
/**
* Tests behavior of {@code KeyToStoredTweetStatus}
*
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "tweetstore.KeyToStoredTweetStatusTest")
public class KeyToStoredTweetStatusTest {
BlobMap createMap() throws InterruptedException, ExecutionException {
BlobStoreContext<AsyncBlobStore, BlobStore> context = new StubBlobStoreContextBuilder()
.buildContext();
context.getBlobStore().createContainer("test1");
return context.createBlobMap("test1");
}
public void testStoreTweets() throws IOException, InterruptedException, ExecutionException {
BlobMap map = createMap();
Blob blob = map.newBlob();
blob.getMetadata().setName("1");
blob.getMetadata().getUserMetadata().put(TweetStoreConstants.SENDER_NAME, "frank");
blob.setPayload("I love beans!");
map.put("1", blob);
String host = "localhost";
String service = "stub";
String container = "tweetstore";
KeyToStoredTweetStatus function = new KeyToStoredTweetStatus(map, service, host, container);
StoredTweetStatus result = function.apply("1");
StoredTweetStatus expected = new StoredTweetStatus(service, host, container, "1", "frank",
"I love beans!", null);
assertEquals(result, expected);
}
}

View File

@ -0,0 +1,77 @@
/**
*
* 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.functions;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.jclouds.blobstore.AsyncBlobStore;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.integration.StubBlobStoreContextBuilder;
import org.jclouds.demo.tweetstore.domain.StoredTweetStatus;
import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
import org.testng.annotations.Test;
import org.testng.collections.Maps;
import com.google.common.collect.Iterables;
/**
* Tests behavior of {@code ServiceToStoredTweetStatuses}
*
* @author Adrian Cole
*/
@Test(groups = "unit", testName = "tweetstore.ServiceToStoredTweetStatuses")
public class ServiceToStoredTweetStatusesTest {
Map<String, BlobStoreContext<?, ?>> createServices(String container)
throws InterruptedException, ExecutionException {
Map<String, BlobStoreContext<?, ?>> services = Maps.newHashMap();
for (String name : new String[] { "1", "2" }) {
BlobStoreContext<AsyncBlobStore, BlobStore> context = new StubBlobStoreContextBuilder()
.buildContext();
context.getAsyncBlobStore().createContainer(container).get();
Blob blob = context.getAsyncBlobStore().newBlob();
blob.getMetadata().setName("1");
blob.getMetadata().getUserMetadata().put(TweetStoreConstants.SENDER_NAME, "frank");
blob.setPayload("I love beans!");
context.getAsyncBlobStore().putBlob(container, blob).get();
services.put(name, context);
}
return services;
}
public void testStoreTweets() throws IOException, InterruptedException, ExecutionException {
String container = "container";
Map<String, BlobStoreContext<?, ?>> contexts = createServices(container);
ServiceToStoredTweetStatuses function = new ServiceToStoredTweetStatuses(contexts, container);
assertEquals(Iterables.getLast(function.apply("1")), new StoredTweetStatus("1", "localhost",
container, "1", "frank", "I love beans!", null));
assertEquals(Iterables.getLast(function.apply("2")), new StoredTweetStatus("2", "localhost",
container, "1", "frank", "I love beans!", null));
}
}

View File

@ -0,0 +1,65 @@
/**
*
* 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.integration;
import com.google.appengine.tools.KickStart;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.File;
import java.util.Properties;
/**
* Basic functionality to start a local google app engine instance.
*
* @author Adrian Cole
*/
public class GoogleDevServer {
Thread server;
public void writePropertiesAndStartServer(final String address,
final String port, final String warfile, Properties props)
throws IOException, InterruptedException {
String filename = String.format(
"%1$s/WEB-INF/jclouds.properties", warfile);
System.err.println("file: " + filename);
props.store(new FileOutputStream(filename), "test");
assert new File(filename).exists();
this.server = new Thread(new Runnable() {
public void run() {
KickStart
.main(new String[]{
"com.google.appengine.tools.development.DevAppServerMain",
"--disable_update_check", "-a", address, "-p",
port, warfile});
}
});
server.start();
Thread.sleep(10 * 1000);
}
@SuppressWarnings("deprecation")
public void stop() throws Exception {
server.stop();
}
}

View File

@ -0,0 +1,203 @@
/**
*
* 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.integration;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_AWS_ACCESSKEYID;
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_AWS_SECRETACCESSKEY;
import static org.jclouds.azure.storage.reference.AzureStorageConstants.PROPERTY_AZURESTORAGE_ACCOUNT;
import static org.jclouds.azure.storage.reference.AzureStorageConstants.PROPERTY_AZURESTORAGE_KEY;
import static org.jclouds.blobstore.reference.BlobStoreConstants.PROPERTY_BLOBSTORE_CONTEXTBUILDERS;
import static org.jclouds.demo.tweetstore.reference.TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER;
import static org.jclouds.rackspace.reference.RackspaceConstants.PROPERTY_RACKSPACE_KEY;
import static org.jclouds.rackspace.reference.RackspaceConstants.PROPERTY_RACKSPACE_USER;
import static org.jclouds.twitter.reference.TwitterConstants.PROPERTY_TWITTER_PASSWORD;
import static org.jclouds.twitter.reference.TwitterConstants.PROPERTY_TWITTER_USER;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.jclouds.aws.s3.S3PropertiesBuilder;
import org.jclouds.aws.s3.blobstore.S3BlobStoreContextBuilder;
import org.jclouds.aws.s3.blobstore.S3BlobStoreContextFactory;
import org.jclouds.azure.storage.blob.AzureBlobPropertiesBuilder;
import org.jclouds.azure.storage.blob.blobstore.AzureBlobStoreContextBuilder;
import org.jclouds.azure.storage.blob.blobstore.AzureBlobStoreContextFactory;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.rackspace.cloudfiles.CloudFilesPropertiesBuilder;
import org.jclouds.rackspace.cloudfiles.blobstore.CloudFilesBlobStoreContextBuilder;
import org.jclouds.rackspace.cloudfiles.blobstore.CloudFilesBlobStoreContextFactory;
import org.jclouds.twitter.TwitterPropertiesBuilder;
import org.jclouds.util.Utils;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableSet;
/**
* Starts up the Google App Engine for Java Development environment and deploys
* an application which tests accesses twitter and blobstores.
*
* @author Adrian Cole
*/
@Test(groups = "live", sequential = true, testName = "functionalTests")
public class TweetStoreLiveTest {
GoogleDevServer server;
private URL url;
private ImmutableSet<BlobStoreContext<? extends Object, ? extends Object>> contexts;
private String container;
@BeforeTest
@Parameters( { "warfile", "devappserver.address", "devappserver.port" })
public void startDevAppServer(final String warfile, final String address,
final String port) throws Exception {
url = new URL(String.format("http://%s:%s", address, port));
Properties props = new Properties();
props.setProperty(PROPERTY_TWEETSTORE_CONTAINER,
checkNotNull(System.getProperty(PROPERTY_TWEETSTORE_CONTAINER), PROPERTY_TWEETSTORE_CONTAINER));
props.setProperty(PROPERTY_BLOBSTORE_CONTEXTBUILDERS,
// WATCH THIS.. when adding a new context, you must update the string
String.format("%s,%s,%s", S3BlobStoreContextBuilder.class.getName(),
CloudFilesBlobStoreContextBuilder.class.getName(),
AzureBlobStoreContextBuilder.class.getName()));
props = new TwitterPropertiesBuilder(props).withCredentials(
checkNotNull(System.getProperty(PROPERTY_TWITTER_USER), PROPERTY_TWITTER_USER),
System.getProperty(PROPERTY_TWITTER_PASSWORD, PROPERTY_TWITTER_PASSWORD))
.build();
props = new S3PropertiesBuilder(props).withCredentials(
checkNotNull(System.getProperty(PROPERTY_AWS_ACCESSKEYID), PROPERTY_AWS_ACCESSKEYID),
System.getProperty(PROPERTY_AWS_SECRETACCESSKEY, PROPERTY_AWS_SECRETACCESSKEY))
.build();
props = new CloudFilesPropertiesBuilder(props).withCredentials(
checkNotNull(System.getProperty(PROPERTY_RACKSPACE_USER), PROPERTY_RACKSPACE_USER),
System.getProperty(PROPERTY_RACKSPACE_KEY, PROPERTY_RACKSPACE_KEY))
.build();
props = new AzureBlobPropertiesBuilder(props).withCredentials(
checkNotNull(System.getProperty(PROPERTY_AZURESTORAGE_ACCOUNT), PROPERTY_AZURESTORAGE_ACCOUNT),
System.getProperty(PROPERTY_AZURESTORAGE_KEY, PROPERTY_AZURESTORAGE_KEY))
.build();
server = new GoogleDevServer();
server.writePropertiesAndStartServer(address, port, warfile, props);
}
@BeforeClass
void clearAndCreateContainers() throws InterruptedException,
ExecutionException, TimeoutException {
container = checkNotNull(System.getProperty(PROPERTY_TWEETSTORE_CONTAINER));
BlobStoreContext<?, ?> s3Context = S3BlobStoreContextFactory.createContext(
checkNotNull(System.getProperty(PROPERTY_AWS_ACCESSKEYID), PROPERTY_AWS_ACCESSKEYID),
System.getProperty(PROPERTY_AWS_SECRETACCESSKEY, PROPERTY_AWS_SECRETACCESSKEY));
BlobStoreContext<?, ?> cfContext = CloudFilesBlobStoreContextFactory.createContext(
checkNotNull(System.getProperty(PROPERTY_RACKSPACE_USER), PROPERTY_RACKSPACE_USER),
System.getProperty(PROPERTY_RACKSPACE_KEY, PROPERTY_RACKSPACE_KEY));
BlobStoreContext<?, ?> azContext = AzureBlobStoreContextFactory.createContext(
checkNotNull(System.getProperty(PROPERTY_AZURESTORAGE_ACCOUNT), PROPERTY_AZURESTORAGE_ACCOUNT),
System.getProperty(PROPERTY_AZURESTORAGE_KEY, PROPERTY_AZURESTORAGE_KEY));
this.contexts = ImmutableSet.of(s3Context, cfContext, azContext);
boolean deleted = false;
for (BlobStoreContext<?, ?> context : contexts) {
if (context.getBlobStore().containerExists(container)) {
System.err.printf("deleting container %s at %s%n", container,
context.getEndPoint());
context.getBlobStore().deleteContainer(container);
deleted = true;
}
}
if (deleted) {
System.err.println("sleeping 30 seconds to allow containers to clear");
Thread.sleep(30000);
}
for (BlobStoreContext<?, ?> context : contexts) {
System.err.printf("creating container %s at %s%n", container,
context.getEndPoint());
context.getBlobStore().createContainer(container);
}
if (deleted) {
System.err.println("sleeping 5 seconds to allow containers to create");
Thread.sleep(30000);
}
}
@Test
public void shouldPass() throws InterruptedException, IOException {
InputStream i = url.openStream();
String string = Utils.toStringAndClose(i);
assert string.indexOf("Welcome") >= 0 : string;
}
@Test(dependsOnMethods = "shouldPass", expectedExceptions = IOException.class)
public void shouldFail() throws InterruptedException, IOException {
new URL(url, "/store/do").openStream();
}
@Test(dependsOnMethods = "shouldFail")
public void testPrimeContainers() throws IOException, InterruptedException {
URL gurl = new URL(url, "/store/do");
for (String context : new String[] { "S3", "Azure", "CloudFiles" }) {
System.out.println("storing at context: " + context);
HttpURLConnection connection = (HttpURLConnection) gurl
.openConnection();
connection.addRequestProperty("X-AppEngine-QueueName", "twitter");
connection.addRequestProperty("context", context);
InputStream i = connection.getInputStream();
String string = Utils.toStringAndClose(i);
assert string.indexOf("Done!") >= 0 : string;
connection.disconnect();
}
System.err.println("sleeping 10 seconds to allow for eventual consistency delay");
Thread.sleep(10000);
for (BlobStoreContext<?, ?> context : contexts) {
assert context.createInputStreamMap(container).size() > 0 : context.getEndPoint();
}
}
@Test(invocationCount = 5, dependsOnMethods = "testPrimeContainers")
public void testSerial() throws InterruptedException, IOException {
URL gurl = new URL(url, "/tweets/get");
InputStream i = gurl.openStream();
String string = Utils.toStringAndClose(i);
assert string.indexOf("Tweets in Clouds") >= 0 : string;
}
@Test(invocationCount = 10, dependsOnMethods = "testPrimeContainers", threadPoolSize = 3)
public void testParallel() throws InterruptedException, IOException {
URL gurl = new URL(url, "/tweets/get");
InputStream i = gurl.openStream();
String string = Utils.toStringAndClose(i);
assert string.indexOf("Tweets in Clouds") >= 0 : string;
}
}

View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
====================================================================
-->
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<!--
For more configuration infromation and examples see the Apache
Log4j website: http://logging.apache.org/log4j/
-->
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"
debug="false">
<!-- A time/date based rolling appender -->
<appender name="WIREFILE" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="target/test-data/jclouds-wire.log" />
<param name="Append" value="true" />
<!-- Rollover at midnight each day -->
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<param name="Threshold" value="TRACE" />
<layout class="org.apache.log4j.PatternLayout">
<!-- The default pattern: Date Priority [Category] Message\n -->
<param name="ConversionPattern" value="%d %-5p [%c] (%t) %m%n" />
<!--
The full pattern: Date MS Priority [Category]
(Thread:NDC) Message\n <param name="ConversionPattern"
value="%d %-5r %-5p [%c] (%t:%x) %m%n"/>
-->
</layout>
</appender>
<!-- A time/date based rolling appender -->
<appender name="FILE" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="target/test-data/jclouds.log" />
<param name="Append" value="true" />
<!-- Rollover at midnight each day -->
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<param name="Threshold" value="TRACE" />
<layout class="org.apache.log4j.PatternLayout">
<!-- The default pattern: Date Priority [Category] Message\n -->
<param name="ConversionPattern" value="%d %-5p [%c] (%t) %m%n" />
<!--
The full pattern: Date MS Priority [Category]
(Thread:NDC) Message\n <param name="ConversionPattern"
value="%d %-5r %-5p [%c] (%t:%x) %m%n"/>
-->
</layout>
</appender>
<appender name="ASYNC" class="org.apache.log4j.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
<appender name="ASYNCWIRE" class="org.apache.log4j.AsyncAppender">
<appender-ref ref="WIREFILE" />
</appender>
<!-- ================ -->
<!-- Limit categories -->
<!-- ================ -->
<category name="org.jclouds">
<priority value="DEBUG" />
<appender-ref ref="ASYNC" />
</category>
<category name="jclouds.http.headers">
<priority value="DEBUG" />
<appender-ref ref="ASYNCWIRE" />
</category>
<category name="jclouds.http.wire">
<priority value="DEBUG" />
<appender-ref ref="ASYNCWIRE" />
</category>
<!--
<category name="jclouds.http.wire"> <priority value="DEBUG" />
<appender-ref ref="ASYNCWIRE" /> </category> <category
name="jclouds.signature"> <priority value="DEBUG" />
<appender-ref ref="ASYNCWIRE" /> </category>
-->
<!-- ======================= -->
<!-- Setup the Root category -->
<!-- ======================= -->
<root>
<priority value="WARN" />
</root>
</log4j:configuration>