Added new Tweetstore demo for CloudBees' RUN@cloud platform

This commit is contained in:
Andrew Phillips 2011-05-29 19:51:41 +01:00
parent 785ad0699d
commit 4d26b5502c
25 changed files with 2396 additions and 0 deletions

View File

@ -37,6 +37,7 @@
<module>getpath</module> <module>getpath</module>
<module>googleappengine</module> <module>googleappengine</module>
<module>perftest</module> <module>perftest</module>
<module>runatcloud-tweetstore</module>
<module>speedtest-azurequeue</module> <module>speedtest-azurequeue</module>
<module>speedtest-sqs</module> <module>speedtest-sqs</module>
<module>simpledb</module> <module>simpledb</module>

View File

@ -0,0 +1,274 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2010 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-runatcloud-tweetstore</artifactId>
<packaging>war</packaging>
<name>jclouds TweetStore for RUN@cloud</name>
<description>jclouds TweetStore for CloudBees' RUN@cloud using Guice for Dependency Injection</description>
<properties>
<bees.appid>tweetstore</bees.appid>
<bees.environment>run</bees.environment>
<bees.address>localhost</bees.address>
<bees.port>8088</bees.port>
<jclouds.tweetstore.blobstores>cloudfiles-us,aws-s3,azureblob</jclouds.tweetstore.blobstores>
<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>org.twitter4j</groupId>
<artifactId>twitter4j-core</artifactId>
<version>[2.1,)</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>org.jclouds.provider</groupId>
<artifactId>aws-s3</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jclouds.provider</groupId>
<artifactId>cloudfiles-us</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jclouds.provider</groupId>
<artifactId>azureblob</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-servlet</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>displaytag</groupId>
<artifactId>displaytag</artifactId>
<version>1.2</version>
<scope>runtime</scope>
<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>
<scope>runtime</scope>
</dependency>
<dependency>
<artifactId>jstl</artifactId>
<groupId>javax.servlet</groupId>
<version>1.1.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<artifactId>standard</artifactId>
<groupId>taglibs</groupId>
<version>1.1.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- RUN@cloud API -->
<dependency>
<groupId>net.stax</groupId>
<artifactId>stax-appserver</artifactId>
<version>1.0.20110131-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>
<pluginRepositories>
<pluginRepository>
<id>bees-plugins-snapshots</id>
<url>http://repository-cloudbees.forge.cloudbees.com/public-snapshot/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<build>
<finalName>${project.artifactId}</finalName>
<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>bees.address</name>
<value>${bees.address}</value>
</property>
<property>
<name>bees.port</name>
<value>${bees.port}</value>
</property>
<property>
<name>bees.environment</name>
<value>${bees.environment}</value>
</property>
<property>
<name>bees.basedir</name>
<value>${project.build.directory}/bees</value>
</property>
<property>
<name>warfile</name>
<value>${project.build.directory}/${project.artifactId}</value>
</property>
</systemProperties>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.cloudbees</groupId>
<artifactId>bees-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
</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>test.twitter.identity</name>
<value>${test.twitter.identity}</value>
</property>
<property>
<name>test.twitter.credential</name>
<value>${test.twitter.credential}</value>
</property>
<property>
<name>test.azureblob.identity</name>
<value>${test.azureblob.identity}</value>
</property>
<property>
<name>test.azureblob.credential</name>
<value>${test.azureblob.credential}</value>
</property>
<property>
<name>test.cloudfiles-us.identity</name>
<value>${test.cloudfiles-us.identity}</value>
</property>
<property>
<name>test.cloudfiles-us.credential</name>
<value>${test.cloudfiles-us.credential}</value>
</property>
<property>
<name>test.aws-s3.identity</name>
<value>${test.aws-s3.identity}</value>
</property>
<property>
<name>test.aws-s3.credential</name>
<value>${test.aws-s3.credential}</value>
</property>
<property>
<name>bees.address</name>
<value>${bees.address}</value>
</property>
<property>
<name>bees.port</name>
<value>${bees.port}</value>
</property>
<property>
<name>bees.environment</name>
<value>${bees.environment}</value>
</property>
<property>
<name>jclouds.tweetstore.blobstores</name>
<value>${jclouds.tweetstore.blobstores}</value>
</property>
<property>
<name>jclouds.tweetstore.container</name>
<value>${jclouds.tweetstore.container}</value>
</property>
<property>
<name>bees.basedir</name>
<value>${project.build.directory}/bees</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,154 @@
/**
*
* Copyright (C) 2011 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.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.jclouds.demo.tweetstore.reference.TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.servlet.ServletContextEvent;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.BlobStoreContextFactory;
import org.jclouds.demo.tweetstore.config.utils.HttpRequestTask;
import org.jclouds.demo.tweetstore.config.utils.HttpRequestTask.Factory;
import org.jclouds.demo.tweetstore.config.utils.TaskQueue;
import org.jclouds.demo.tweetstore.controller.AddTweetsController;
import org.jclouds.demo.tweetstore.controller.StoreTweetsController;
import org.jclouds.http.HttpRequest;
import twitter4j.Twitter;
import twitter4j.TwitterFactory;
import twitter4j.conf.Configuration;
import twitter4j.conf.ConfigurationBuilder;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.io.Closeables;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.servlet.ServletModule;
/**
* Setup Logging and create Injector for use in testing S3.
*
* @author Adrian Cole
*/
public class GuiceServletConfig extends GuiceServletContextListener {
public static final String PROPERTY_BLOBSTORE_CONTEXTS = "blobstore.contexts";
private Map<String, BlobStoreContext> providerTypeToBlobStoreMap;
private Twitter twitterClient;
private String container;
private TaskQueue queue;
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
BlobStoreContextFactory blobStoreContextFactory = new BlobStoreContextFactory();
Properties props = loadJCloudsProperties(servletContextEvent);
Set<Module> modules = ImmutableSet.<Module>of();
// shared across all blobstores and used to retrieve tweets
try {
Configuration twitterConf = new ConfigurationBuilder()
.setUser(props.getProperty("twitter.identity"))
.setPassword(props.getProperty("twitter.credential")).build();
twitterClient = new TwitterFactory(twitterConf).getInstance();
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("properties for twitter not configured properly in "
+ props.toString(), e);
}
// common namespace for storing tweets
container = checkNotNull(props.getProperty(PROPERTY_TWEETSTORE_CONTAINER), PROPERTY_TWEETSTORE_CONTAINER);
// instantiate and store references to all blobstores by provider name
providerTypeToBlobStoreMap = Maps.newHashMap();
for (String hint : Splitter.on(',').split(checkNotNull(props.getProperty(PROPERTY_BLOBSTORE_CONTEXTS), PROPERTY_BLOBSTORE_CONTEXTS))) {
providerTypeToBlobStoreMap.put(hint, blobStoreContextFactory.createContext(hint, modules, props));
}
// get a queue for submitting store tweet requests
queue = TaskQueue.builder().name("twitter").period(MINUTES).build();
Factory taskFactory = HttpRequestTask.factory(props, "twitter");
// submit a job to store tweets for each configured blobstore
for (String name : providerTypeToBlobStoreMap.keySet()) {
queue.add(taskFactory.create(HttpRequest.builder()
.endpoint(URI.create("http://localhost:8080" + servletContextEvent.getServletContext().getContextPath() + "/store/do"))
.headers(ImmutableMultimap.of("context", name, "X-RUN@cloud-QueueName", "twitter"))
.method("GET").build()));
}
super.contextInitialized(servletContextEvent);
}
private Properties loadJCloudsProperties(
ServletContextEvent servletContextEvent) {
InputStream input = servletContextEvent.getServletContext()
.getResourceAsStream("/WEB-INF/jclouds.properties");
Properties props = new Properties();
try {
props.load(input);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
Closeables.closeQuietly(input);
}
return props;
}
@Override
protected Injector getInjector() {
return Guice.createInjector(new ServletModule() {
@Override
protected void configureServlets() {
bind(new TypeLiteral<Map<String, BlobStoreContext>>() {
}).toInstance(providerTypeToBlobStoreMap);
bind(Twitter.class).toInstance(twitterClient);
bindConstant().annotatedWith(
Names.named(PROPERTY_TWEETSTORE_CONTAINER)).to(
container);
serve("/store/*").with(StoreTweetsController.class);
serve("/tweets/*").with(AddTweetsController.class);
}
});
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
for (BlobStoreContext context : providerTypeToBlobStoreMap.values()) {
context.close();
}
queue.destroy();
super.contextDestroyed(servletContextEvent);
}
}

View File

@ -0,0 +1,132 @@
package org.jclouds.demo.tweetstore.config.utils;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.inject.name.Names.bindProperties;
import static java.lang.String.format;
import java.util.Properties;
import javax.ws.rs.core.UriBuilder;
import org.jclouds.PropertiesBuilder;
import org.jclouds.concurrent.config.ExecutorServiceModule;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpCommandExecutorService;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.config.JavaUrlHttpCommandExecutorServiceModule;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.sun.jersey.api.uri.UriBuilderImpl;
public class HttpRequestTask implements Runnable {
public static Factory factory(Properties props) {
return factory(props, format("%s@%d", Factory.class.getName(), System.currentTimeMillis()));
}
public static Factory factory(Properties props, String originator) {
return new Factory(props, originator);
}
public static class Factory {
private static final String HTTP_REQUEST_ORIGINATOR_HEADER = "X-RUN@cloud-Originator";
protected final HttpCommandExecutorService httpClient;
protected final String originator;
private Factory(final Properties props, String originator) {
this.originator = originator;
httpClient = Guice.createInjector(new ExecutorServiceModule(),
new JavaUrlHttpCommandExecutorServiceModule(),
new AbstractModule() {
@Override
protected void configure() {
// URL connection defaults
Properties toBind = new PropertiesBuilder().build();
toBind.putAll(checkNotNull(props, "properties"));
toBind.putAll(System.getProperties());
bindProperties(binder(), toBind);
bind(UriBuilder.class).to(UriBuilderImpl.class);
}
}).getInstance(HttpCommandExecutorService.class);
}
public HttpRequestTask create(HttpRequest request) {
HttpRequest requestWithSubmitter = request.toBuilder().headers(
copyOfWithEntry(request.getHeaders(),
HTTP_REQUEST_ORIGINATOR_HEADER, originator)).build();
return new HttpRequestTask(httpClient, requestWithSubmitter);
}
private static <K, V> Multimap<K, V> copyOfWithEntry(
Multimap<? extends K, ? extends V> multimap, K k1, V v1) {
return ImmutableMultimap.<K, V>builder().putAll(multimap).put(k1, v1).build();
}
}
private final HttpCommandExecutorService httpClient;
private final HttpRequest request;
private HttpRequestTask(HttpCommandExecutorService httpClient, HttpRequest request) {
this.httpClient = httpClient;
this.request = request;
}
@Override
public void run() {
httpClient.submit(new ImmutableHttpCommand(request));
}
private class ImmutableHttpCommand implements HttpCommand {
private final HttpRequest request;
public ImmutableHttpCommand(HttpRequest request) {
this.request = request;
}
@Override
public void setException(Exception exception) {
}
@Override
public void setCurrentRequest(HttpRequest request) {
}
@Override
public boolean isReplayable() {
return false;
}
@Override
public int incrementRedirectCount() {
return 0;
}
@Override
public int incrementFailureCount() {
return 0;
}
@Override
public int getRedirectCount() {
return 0;
}
@Override
public int getFailureCount() {
return 0;
}
@Override
public Exception getException() {
return null;
}
@Override
public HttpRequest getCurrentRequest() {
return request;
}
}
}

View File

@ -0,0 +1,55 @@
package org.jclouds.demo.tweetstore.config.utils;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpRequest;
class ImmutableHttpCommand implements HttpCommand {
private final HttpRequest request;
public ImmutableHttpCommand(HttpRequest request) {
this.request = request;
}
@Override
public void setException(Exception exception) {
}
@Override
public void setCurrentRequest(HttpRequest request) {
}
@Override
public boolean isReplayable() {
return false;
}
@Override
public int incrementRedirectCount() {
return 0;
}
@Override
public int incrementFailureCount() {
return 0;
}
@Override
public int getRedirectCount() {
return 0;
}
@Override
public int getFailureCount() {
return 0;
}
@Override
public Exception getException() {
return null;
}
@Override
public HttpRequest getCurrentRequest() {
return request;
}
}

View File

@ -0,0 +1,58 @@
/*
* @(#)ObjectFields.java 26 May 2011
*
* Copyright © 2010 Andrew Phillips.
*
* ====================================================================
* 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.utils;
import java.lang.reflect.Field;
public class ObjectFields {
public static Object valueOf(String fieldName, Object source) {
return valueOf(fieldName, source, source.getClass());
}
public static Object valueOf(String fieldName, Object source,
Class<?> fieldDeclaringClass) {
try {
return getAccessibleField(fieldName, fieldDeclaringClass).get(source);
} catch (Exception exception) {
throw new IllegalArgumentException(exception);
}
}
private static Field getAccessibleField(String name, Class<?> declaringClass) throws SecurityException, NoSuchFieldException {
Field field = declaringClass.getDeclaredField(name);
field.setAccessible(true);
return field;
}
public static void set(String fieldName, Object target, Object value) {
set(fieldName, target, value, target.getClass());
}
public static void set(String fieldName, Object target, Object value,
Class<?> fieldDeclaringClass) {
try {
getAccessibleField(fieldName, fieldDeclaringClass).set(target, value);
} catch (Exception exception) {
throw new IllegalArgumentException(exception);
}
}
}

View File

@ -0,0 +1,67 @@
package org.jclouds.demo.tweetstore.config.utils;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import com.google.inject.Provider;
public class TaskQueue {
public static Builder builder() {
return new Builder();
}
public static class Builder implements Provider<TaskQueue> {
protected String name = "default";
protected long taskPeriodMillis = TimeUnit.SECONDS.toMillis(1);
public Builder name(String name) {
this.name = checkNotNull(name, "name");
return this;
}
public Builder period(TimeUnit period) {
this.taskPeriodMillis = checkNotNull(period, "period").toMillis(1);
return this;
}
public Builder period(long taskPeriodMillis) {
checkArgument(taskPeriodMillis > 0, "taskPeriodMillis");
this.taskPeriodMillis = taskPeriodMillis;
return this;
}
public TaskQueue build() {
return new TaskQueue(name, taskPeriodMillis);
}
@Override
public TaskQueue get() {
return build();
}
}
private final Timer timer;
private final long taskPeriodMillis;
private TaskQueue(String name, long taskPeriodMillis) {
timer = new Timer(name);
this.taskPeriodMillis = taskPeriodMillis;
}
public void add(final Runnable task) {
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
task.run();
}
}, 0, taskPeriodMillis);
}
public void destroy() {
timer.cancel();
}
}

View File

@ -0,0 +1,96 @@
/**
*
* Copyright (C) 2011 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.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
* 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
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) 2011 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 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.rest.AuthorizationException;
import twitter4j.Status;
import twitter4j.Twitter;
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.blobBuilder().name(from.getId() + "").build();
to.setPayload(from.getText());
to.getPayload().getContentMetadata().setContentType(MediaType.TEXT_PLAIN);
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 Twitter client;
private final String container;
@Resource
protected Logger logger = Logger.NULL;
@Inject
@VisibleForTesting
public StoreTweetsController(Map<String, BlobStoreContext> contexts,
@Named(TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER) String container, Twitter client) {
this.container = container;
this.contexts = contexts;
this.client = client;
}
@VisibleForTesting
public void addMyTweets(String contextName, Iterable<Status> responseList) {
BlobStoreContext context = checkNotNull(contexts.get(contextName), "no context for " + contextName + " in "
+ contexts.keySet());
BlobMap map = context.createBlobMap(container);
for (Status status : responseList) {
Blob blob = null;
try {
blob = new StatusToBlob(map).apply(status);
map.put(status.getId() + "", blob);
} catch (AuthorizationException e) {
throw e;
} catch (Exception e) {
logger.error(e, "Error storing tweet %s (blob[%s]) on map %s/%s", status.getId(), blob, context, container);
}
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (request.getHeader("X-RUN@cloud-Submitter") != null
&& request.getHeader("X-RUN@cloud-Submitter").equals("twitter")) {
try {
String contextName = checkNotNull(request.getHeader("context"), "missing header context");
logger.info("retrieving tweets");
addMyTweets(contextName, client.getMentions());
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) 2011 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,71 @@
/**
*
* Copyright (C) 2011 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.jclouds.util.Strings2.toStringAndClose;
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 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 = toStringAndClose(blob.getPayload().getInput());
} 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) 2011 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.getProviderSpecificContext().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,33 @@
/**
*
* Copyright (C) 2011 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";
/**
* Note that this has to conform to restrictions of all blobstores. for example, azure doesn't
* support periods.
*/
public static final String SENDER_NAME = "sendername";
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<cloudbees-web-app xmlns="http://www.cloudbees.com/xml/webapp/1">
<appid>${bees.appid}</appid>
<context-param>
<param-name>application.environment</param-name>
<param-value>${bees.environment}</param-value>
</context-param>
</cloudbees-web-app>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2011 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-tweetstore</display-name>
<!-- Servlets -->
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.jclouds.demo.tweetstore.config.GuiceServletConfig
</listener-class>
</listener>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

View File

@ -0,0 +1,31 @@
<%--
Copyright (C) 2011 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) 2011 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,76 @@
/**
*
* Copyright (C) 2011 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.BlobStoreContext;
import org.jclouds.blobstore.BlobStoreContextFactory;
import org.jclouds.blobstore.domain.Blob;
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")
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 context = new BlobStoreContextFactory().createContext("transient", "dummy", "dummy");
context.getAsyncBlobStore().createContainerInLocation(null, container).get();
Blob blob = context.getAsyncBlobStore().blobBuilder("1").build();
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,118 @@
/**
*
* Copyright (C) 2011 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.EasyMock.expect;
import static org.easymock.classextension.EasyMock.createMock;
import static org.easymock.classextension.EasyMock.replay;
import static org.easymock.classextension.EasyMock.verify;
import static org.jclouds.util.Strings2.toStringAndClose;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import org.jclouds.blobstore.BlobMap;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.BlobStoreContextFactory;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.demo.tweetstore.reference.TweetStoreConstants;
import org.testng.annotations.Test;
import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.User;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
/**
* Tests behavior of {@code StoreTweetsController}
*
* @author Adrian Cole
*/
@Test(groups = "unit")
public class StoreTweetsControllerTest {
Twitter createTwitter() {
return createMock(Twitter.class);
}
Map<String, BlobStoreContext> createBlobStores() throws InterruptedException, ExecutionException {
Map<String, BlobStoreContext> contexts = ImmutableMap.<String, BlobStoreContext> of("test1",
new BlobStoreContextFactory().createContext("transient", "dummy", "dummy"), "test2",
new BlobStoreContextFactory().createContext("transient", "dummy", "dummy"));
for (BlobStoreContext blobstore : contexts.values()) {
blobstore.getAsyncBlobStore().createContainerInLocation(null, "favo").get();
}
return contexts;
}
public void testStoreTweets() throws IOException, InterruptedException, ExecutionException {
Map<String, BlobStoreContext> stores = createBlobStores();
StoreTweetsController function = new StoreTweetsController(stores, "favo", createTwitter());
User frank = createMock(User.class);
expect(frank.getScreenName()).andReturn("frank").atLeastOnce();
Status frankStatus = createMock(Status.class);
expect(frankStatus.getId()).andReturn(1l).atLeastOnce();
expect(frankStatus.getUser()).andReturn(frank).atLeastOnce();
expect(frankStatus.getText()).andReturn("I love beans!").atLeastOnce();
User jimmy = createMock(User.class);
expect(jimmy.getScreenName()).andReturn("jimmy").atLeastOnce();
Status jimmyStatus = createMock(Status.class);
expect(jimmyStatus.getId()).andReturn(2l).atLeastOnce();
expect(jimmyStatus.getUser()).andReturn(jimmy).atLeastOnce();
expect(jimmyStatus.getText()).andReturn("cloud is king").atLeastOnce();
replay(frank);
replay(frankStatus);
replay(jimmy);
replay(jimmyStatus);
function.addMyTweets("test1", ImmutableList.of(frankStatus, jimmyStatus));
function.addMyTweets("test2", ImmutableList.of(frankStatus, jimmyStatus));
verify(frank);
verify(frankStatus);
verify(jimmy);
verify(jimmyStatus);
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().getContentMetadata().getContentType(), "text/plain");
assertEquals(toStringAndClose(frankBlob.getPayload().getInput()), "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().getContentMetadata().getContentType(), "text/plain");
assertEquals(toStringAndClose(jimmyBlob.getPayload().getInput()), "cloud is king");
}
}
}

View File

@ -0,0 +1,67 @@
/**
*
* Copyright (C) 2011 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.BlobMap;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.BlobStoreContextFactory;
import org.jclouds.blobstore.domain.Blob;
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")
public class KeyToStoredTweetStatusTest {
BlobMap createMap() throws InterruptedException, ExecutionException {
BlobStoreContext context = new BlobStoreContextFactory().createContext("transient", "dummy", "dummy");
context.getBlobStore().createContainerInLocation(null, "test1");
return context.createBlobMap("test1");
}
public void testStoreTweets() throws IOException, InterruptedException, ExecutionException {
BlobMap map = createMap();
Blob blob = map.blobBuilder().name("1").build();
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,74 @@
/**
*
* Copyright (C) 2011 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.BlobStoreContext;
import org.jclouds.blobstore.BlobStoreContextFactory;
import org.jclouds.blobstore.domain.Blob;
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")
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 context = new BlobStoreContextFactory().createContext("transient",
"dummy", "dummy");
context.getAsyncBlobStore().createContainerInLocation(null, container).get();
Blob blob = context.getAsyncBlobStore().blobBuilder("1").build();
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,57 @@
/**
*
* Copyright (C) 2011 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 java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import org.apache.commons.cli.ParseException;
/**
* Basic functionality to start a local google app engine instance.
*
* @author Adrian Cole
*/
public class RunAtCloudServer {
protected StaxSdkAppServer2 server;
public void writePropertiesAndStartServer(final String address, final String port,
final String warfile, final String environments,
final String serverBaseDirectory, Properties props) throws IOException, InterruptedException, ParseException, ServletException {
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();
server = StaxSdkAppServer2.createServer(new String[] { "-web", warfile, "-port", port, "-env", environments,
"-dir", serverBaseDirectory }, new String[0], Thread.currentThread().getContextClassLoader());
server.start();
TimeUnit.SECONDS.sleep(30);
}
public void stop() throws Exception {
server.stop();
}
}

View File

@ -0,0 +1,187 @@
/*
* @(#)StaxSdkAppServer2.java 25 May 2011
*
* Copyright © 2010 Andrew Phillips.
*
* ====================================================================
* 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.Predicates.instanceOf;
import static java.util.Arrays.asList;
import static org.jclouds.demo.tweetstore.config.utils.ObjectFields.set;
import static org.jclouds.demo.tweetstore.config.utils.ObjectFields.valueOf;
import java.io.File;
import java.io.IOException;
import java.util.Timer;
import javax.servlet.ServletException;
import net.stax.appserver.webapp.RequestMonitorValve;
import net.stax.appserver.webapp.WebAppEngine;
import org.apache.catalina.Engine;
import org.apache.commons.cli.ParseException;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.staxnet.appserver.IAppServerConfiguration;
import com.staxnet.appserver.IEngineFactory;
import com.staxnet.appserver.ServerCallbackClient;
import com.staxnet.appserver.StaxAppServerBase;
import com.staxnet.appserver.StaxSdkAppServer;
import com.staxnet.appserver.StaxSdkAppServerCLI;
import com.staxnet.appserver.TomcatServerBase;
import com.staxnet.appserver.WarBasedServerConfiguration;
import com.staxnet.appserver.config.AppServerConfig;
class StaxSdkAppServer2 {
// code more or less exactly from StaxSdkAppServer.java
public static StaxSdkAppServer2 createServer(String[] args, String[] classPaths,
ClassLoader cl) throws ParseException, ServletException {
StaxSdkAppServerCLI cli = StaxSdkAppServerCLI.parse(args);
if (cli.getMissingOptions().length > 0) {
throw new ParseException("Missing required options: " + cli.formatMissingOptions(", "));
}
String[] environments = cli.getEnvironment();
File serverConfig = cli.getServerConfigFile();
File baseDir = new File(cli.getBaseDir());
File webRoot = new File(cli.getWebdir());
File workDir = new File(baseDir, "work");
File staxWebXml = new File(webRoot, "WEB-INF/cloudbees-web.xml");
if (!(staxWebXml.exists()))
staxWebXml = new File(webRoot, "WEB-INF/stax-web.xml");
IAppServerConfiguration config = WarBasedServerConfiguration.load(
serverConfig, webRoot, staxWebXml, environments);
// force the RequestMonitorValve to sleep for only a short period
set("statusInterval", StaxReflect.getAppServerConfig(config), 5);
StaxSdkAppServer server = new StaxSdkAppServer(
baseDir.getAbsolutePath(), workDir.getAbsolutePath(), cl,
classPaths, cli.getPort(), config, cli.getRepositoryPath());
return new StaxSdkAppServer2(server);
}
private final StaxSdkAppServer server;
private Thread serverThread;
private StaxSdkAppServer2(StaxSdkAppServer server) {
this.server = server;
serverThread = new Thread(new Runnable() {
public void run() {
try {
StaxSdkAppServer2.this.server.start();
} catch (ServletException exception) {
System.err.println("exception starting server: " + exception);
}
}
});
}
void start() throws ServletException {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
stop();
}
}));
serverThread.start();
}
void stop() {
server.stop();
serverThread.interrupt();
StaxReflect.getStaxAppQueryTimer(server).cancel();
KillerCallback requestMonitorAssassin = new KillerCallback(StaxReflect.getRequestMonitorTimerCallback(server));
set("callbackClient", StaxReflect.getRequestMonitorTimer(server), requestMonitorAssassin);
requestMonitorAssassin.setToKill();
}
private class KillerCallback extends ServerCallbackClient {
private final ServerCallbackClient delegate;
private volatile boolean killCaller = false;
private KillerCallback(ServerCallbackClient delegate) {
super("", "");
this.delegate = delegate;
}
@Override
public AuthenticationResult getApplicationTicket(String username,
String password) throws IOException {
return delegate.getApplicationTicket(username, password);
}
@Override
public AuthenticationResult renewApplicationTicket(String userAuthTicket)
throws IOException {
return delegate.renewApplicationTicket(userAuthTicket);
}
@Override
public void updateStatus(State state) throws ServletException,
IOException {
if (killCaller) {
throw new ThreadDeath();
}
delegate.updateStatus(state);
}
private void setToKill() {
killCaller = true;
}
}
private static class StaxReflect {
private static WebAppEngine getWebAppEngine(StaxSdkAppServer server) {
return (WebAppEngine) Iterables.find(asList((IEngineFactory[])
valueOf("engineFactories", server, StaxAppServerBase.class)),
instanceOf(WebAppEngine.class));
}
private static Timer getStaxAppQueryTimer(StaxSdkAppServer server) {
return (Timer) valueOf("timer", getWebAppEngine(server));
}
private static AppServerConfig getAppServerConfig(IAppServerConfiguration config) {
return (AppServerConfig) valueOf("appServerConfig", config);
}
private static Engine getLocalEngine(StaxSdkAppServer server) {
return (Engine) Iterables.find(asList((Engine[])
valueOf("engines", valueOf("container", server, TomcatServerBase.class))),
new Predicate<Engine>() {
@Override
public boolean apply(Engine input) {
return input.getName().equals("localEngine");
}
});
}
private static Runnable getRequestMonitorTimer(StaxSdkAppServer server) {
return (Runnable) valueOf("idleTimer", Iterables.find(
asList(getLocalEngine(server).getPipeline().getValves()),
instanceOf(RequestMonitorValve.class)));
}
private static ServerCallbackClient getRequestMonitorTimerCallback(
StaxSdkAppServer server) {
return (ServerCallbackClient) valueOf("callbackClient",
getRequestMonitorTimer(server));
}
}
}

View File

@ -0,0 +1,218 @@
/**
*
* Copyright (C) 2011 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.demo.tweetstore.reference.TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.BlobStoreContextFactory;
import org.jclouds.demo.tweetstore.config.GuiceServletConfig;
import org.jclouds.demo.tweetstore.controller.StoreTweetsController;
import org.jclouds.logging.log4j.config.Log4JLoggingModule;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.util.Strings2;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import twitter4j.ResponseList;
import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.conf.Configuration;
import twitter4j.conf.ConfigurationBuilder;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.inject.Module;
/**
* 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", singleThreaded = true)
public class TweetStoreLiveTest {
RunAtCloudServer server;
private URL url;
private Map<String, BlobStoreContext> contexts;
private String container;
private static final Iterable<String> blobstores =
Splitter.on(',').split(System.getProperty("jclouds.tweetstore.blobstores",
"cloudfiles-us,aws-s3,azureblob"));
private static final Properties props = new Properties();
@BeforeTest
void clearAndCreateContainers() throws InterruptedException, ExecutionException, TimeoutException, IOException,
TwitterException {
container = checkNotNull(System.getProperty(PROPERTY_TWEETSTORE_CONTAINER), PROPERTY_TWEETSTORE_CONTAINER);
props.setProperty(PROPERTY_TWEETSTORE_CONTAINER, container);
props.setProperty(GuiceServletConfig.PROPERTY_BLOBSTORE_CONTEXTS, Joiner.on(',').join(blobstores));
// put all identity/credential pairs into the client
addCredentialsForBlobStores(props);
// example of an ad-hoc client configuration
addConfigurationForTwitter(props);
final BlobStoreContextFactory factory = new BlobStoreContextFactory();
// for testing, capture logs.
final Set<Module> wiring = ImmutableSet.<Module> of(new Log4JLoggingModule());
this.contexts = Maps.newConcurrentMap();
for (String provider : blobstores) {
contexts.put(provider, factory.createContext(provider, wiring, props));
}
Configuration conf = new ConfigurationBuilder()
.setUser(props.getProperty("twitter.identity"))
.setPassword(props.getProperty("twitter.credential")).build();
Twitter client = new TwitterFactory(conf).getInstance();
StoreTweetsController controller = new StoreTweetsController(contexts, container, client);
ResponseList<Status> statuses = client.getMentions();
boolean deleted = false;
for (BlobStoreContext context : contexts.values()) {
try {
if (context.getBlobStore().containerExists(container)) {
System.err.printf("deleting container %s at %s%n", container, context.getProviderSpecificContext()
.getEndpoint());
context.getBlobStore().deleteContainer(container);
deleted = true;
}
} catch (AuthorizationException e) {
throw new AuthorizationException("for context: " + context, e);
}
}
if (deleted) {
System.err.println("sleeping 60 seconds to allow containers to clear");
Thread.sleep(60000);
}
for (BlobStoreContext context : contexts.values()) {
System.err.printf("creating container %s at %s%n", container, context.getProviderSpecificContext()
.getEndpoint());
context.getBlobStore().createContainerInLocation(null, container);
}
if (deleted) {
System.err.println("sleeping 5 seconds to allow containers to create");
Thread.sleep(5000);
}
for (Entry<String, BlobStoreContext> entry : contexts.entrySet()) {
System.err.printf("filling container %s at %s%n", container, entry.getKey());
controller.addMyTweets(entry.getKey(), statuses);
}
}
@BeforeTest(dependsOnMethods = "clearAndCreateContainers")
@Parameters({ "warfile", "bees.address", "bees.port", "bees.environment", "bees.basedir" })
public void startDevAppServer(final String warfile, final String address, final String port,
String environments, String serverBaseDirectory) throws Exception {
url = new URL(String.format("http://%s:%s", address, port));
server = new RunAtCloudServer();
server.writePropertiesAndStartServer(address, port, warfile, environments,
serverBaseDirectory, props);
}
private void addConfigurationForTwitter(Properties props) {
props.setProperty("twitter.identity", checkNotNull(System.getProperty("test.twitter.identity"), "test.twitter.identity"));
props.setProperty("twitter.credential",
checkNotNull(System.getProperty("test.twitter.credential"), "test.twitter.credential"));
}
private void addCredentialsForBlobStores(Properties props) {
for (String provider : blobstores) {
props.setProperty(provider + ".identity",
checkNotNull(System.getProperty("test." + provider + ".identity"), "test." + provider + ".identity"));
props.setProperty(provider + ".credential",
checkNotNull(System.getProperty("test." + provider + ".credential"), "test." + provider + ".credential"));
}
}
@Test
public void shouldPass() throws InterruptedException, IOException {
InputStream i = url.openStream();
String string = Strings2.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 : blobstores) {
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 = Strings2.toStringAndClose(i);
assert string.indexOf("Done!") >= 0 : string;
connection.disconnect();
}
System.err.println("sleeping 20 seconds to allow for eventual consistency delay");
Thread.sleep(20000);
for (BlobStoreContext context : contexts.values()) {
assert context.createInputStreamMap(container).size() > 0 : context.getProviderSpecificContext().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 = Strings2.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 = Strings2.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.headers">
<priority value="DEBUG" />
<appender-ref ref="ASYNCWIRE" />
</category>
<category name="jclouds.wire">
<priority value="DEBUG" />
<appender-ref ref="ASYNCWIRE" />
</category>
<!--
<category name="jclouds.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>