From 483c144d380780d58bc18041de4ba1c765853faa Mon Sep 17 00:00:00 2001 From: "adrian.f.cole" Date: Sun, 1 Nov 2009 05:19:01 +0000 Subject: [PATCH] Issue 114: new demo of twitter git-svn-id: http://jclouds.googlecode.com/svn/trunk@2035 3d8758e0-26b5-11de-8745-db77d3ebf521 --- .../java/org/jclouds/blobstore/BlobMap.java | 4 + .../blobstore/internal/BlobMapImpl.java | 4 + demos/gae-tweetstore/README.txt | 59 ++++ demos/gae-tweetstore/pom.xml | 285 ++++++++++++++++++ .../src/main/appengine/appengine-web.xml | 33 ++ .../src/main/appengine/logging.properties | 75 +++++ .../tweetstore/config/GuiceServletConfig.java | 138 +++++++++ .../controller/AddTweetsController.java | 101 +++++++ .../controller/StoreTweetsController.java | 137 +++++++++ .../tweetstore/domain/StoredTweetStatus.java | 154 ++++++++++ .../functions/KeyToStoredTweetStatus.java | 77 +++++ .../ServiceToStoredTweetStatuses.java | 76 +++++ .../reference/TweetStoreConstants.java | 34 +++ .../src/main/webapp/WEB-INF/cron.xml | 9 + .../src/main/webapp/WEB-INF/web.xml | 52 ++++ .../gae-tweetstore/src/main/webapp/index.jsp | 36 +++ .../gae-tweetstore/src/main/webapp/tweets.jsp | 113 +++++++ .../controller/AddTweetsControllerTest.java | 60 ++++ .../controller/StoreTweetsControllerTest.java | 88 ++++++ .../functions/KeyToStoredTweetStatusTest.java | 51 ++++ .../ServiceToStoredTweetStatusesTest.java | 57 ++++ .../integration/GoogleDevServer.java | 70 +++++ .../integration/TweetStoreLiveTest.java | 190 ++++++++++++ demos/pom.xml | 64 ++++ .../org/jclouds/twitter/domain/Status.java | 18 +- .../java/org/jclouds/twitter/domain/User.java | 6 + .../ParseStatusesFromJsonResponseTest.java | 68 ++--- 27 files changed, 2022 insertions(+), 37 deletions(-) create mode 100755 demos/gae-tweetstore/README.txt create mode 100755 demos/gae-tweetstore/pom.xml create mode 100755 demos/gae-tweetstore/src/main/appengine/appengine-web.xml create mode 100644 demos/gae-tweetstore/src/main/appengine/logging.properties create mode 100755 demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java create mode 100755 demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/AddTweetsController.java create mode 100644 demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/StoreTweetsController.java create mode 100755 demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/domain/StoredTweetStatus.java create mode 100644 demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/functions/KeyToStoredTweetStatus.java create mode 100755 demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/functions/ServiceToStoredTweetStatuses.java create mode 100644 demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/reference/TweetStoreConstants.java create mode 100644 demos/gae-tweetstore/src/main/webapp/WEB-INF/cron.xml create mode 100755 demos/gae-tweetstore/src/main/webapp/WEB-INF/web.xml create mode 100755 demos/gae-tweetstore/src/main/webapp/index.jsp create mode 100755 demos/gae-tweetstore/src/main/webapp/tweets.jsp create mode 100644 demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/AddTweetsControllerTest.java create mode 100644 demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/StoreTweetsControllerTest.java create mode 100644 demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/functions/KeyToStoredTweetStatusTest.java create mode 100644 demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/functions/ServiceToStoredTweetStatusesTest.java create mode 100755 demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/integration/GoogleDevServer.java create mode 100755 demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/integration/TweetStoreLiveTest.java create mode 100755 demos/pom.xml diff --git a/blobstore/core/src/main/java/org/jclouds/blobstore/BlobMap.java b/blobstore/core/src/main/java/org/jclouds/blobstore/BlobMap.java index 5ebe42b0b0..1ded2bff62 100644 --- a/blobstore/core/src/main/java/org/jclouds/blobstore/BlobMap.java +++ b/blobstore/core/src/main/java/org/jclouds/blobstore/BlobMap.java @@ -38,7 +38,11 @@ import com.google.inject.ImplementedBy; */ @ImplementedBy(BlobMapImpl.class) public interface BlobMap extends ListableMap { + + Blob newBlob(); + public static interface Factory { BlobMap create(String containerName, ListOptions listOptions); } + } \ No newline at end of file diff --git a/blobstore/core/src/main/java/org/jclouds/blobstore/internal/BlobMapImpl.java b/blobstore/core/src/main/java/org/jclouds/blobstore/internal/BlobMapImpl.java index 7096100475..4bbc6c5d2d 100755 --- a/blobstore/core/src/main/java/org/jclouds/blobstore/internal/BlobMapImpl.java +++ b/blobstore/core/src/main/java/org/jclouds/blobstore/internal/BlobMapImpl.java @@ -218,4 +218,8 @@ public class BlobMapImpl extends BaseBlobMap implements BlobMap { }); } + public Blob newBlob() { + return connection.newBlob(); + } + } diff --git a/demos/gae-tweetstore/README.txt b/demos/gae-tweetstore/README.txt new file mode 100755 index 0000000000..d96d500350 --- /dev/null +++ b/demos/gae-tweetstore/README.txt @@ -0,0 +1,59 @@ +==== + + Copyright (C) 2009 Cloud Conscious, LLC. + + ==================================================================== + 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. + ==================================================================== +==== +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' + + + appengine + + true + + + /path/to/appengine-java-sdk-1.2.5 + yourappid + + + + + keys + + true + + + YOUR_ACCESS_KEY_ID + YOUR_SECRET_KEY + YOUR_USER + YOUR_HEX_KEY + YOUR_ACCOUNT + YOUR_BASE64_ENCODED_KEY + + + + + + jclouds + http://jclouds.googlecode.com/svn/trunk/repo + + diff --git a/demos/gae-tweetstore/pom.xml b/demos/gae-tweetstore/pom.xml new file mode 100755 index 0000000000..c2cbc1a943 --- /dev/null +++ b/demos/gae-tweetstore/pom.xml @@ -0,0 +1,285 @@ + + + + + jclouds-demos-project + org.jclouds + 1.0-SNAPSHOT + ../pom.xml + + 4.0.0 + jclouds-gae-tweetstore + war + JClouds TweetStore for Google App Engine + JClouds TweetStore for Google App Engine + + + + /Users/adriancole/Desktop/appengine-java-sdk-1.2.5 + jclouds-tweetstore + localhost + 8088 + jclouds-tweetstore + + + + + ${project.groupId} + jclouds-blobstore-core + ${project.version} + test-jar + test + + + ${project.groupId} + jclouds-twitter + ${project.version} + + + ${project.groupId} + jclouds-s3 + ${project.version} + + + ${project.groupId} + jclouds-azureblob + ${project.version} + + + ${project.groupId} + jclouds-cloudfiles + ${project.version} + + + ${project.groupId} + jclouds-gae + ${project.version} + + + com.google.code.guice + guice-servlet + 2.1-r1089 + + + displaytag + displaytag + 1.2 + + + org.slf4j + slf4j-log4j12 + + + + + org.slf4j + slf4j-jdk14 + 1.5.6 + + + standard + taglibs + 1.1.2 + jar + runtime + + + jstl + javax.servlet + 1.1.2 + jar + compile + + + org.apache.geronimo.specs + geronimo-el_1.0_spec + 1.0.1 + compile + + + org.apache.geronimo.specs + geronimo-jsp_2.1_spec + 1.0.1 + provided + + + org.apache.geronimo.specs + geronimo-servlet_2.5_spec + 1.2 + provided + + + + com.google.appengine + appengine-tools-api + 1.2.5 + system + ${appengine.home}/lib/appengine-tools-api.jar + + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-war-plugin + + + + src/main/appengine + WEB-INF/ + true + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration + integration-test + + test + + + + + appengine.home + ${appengine.home} + + + devappserver.address + ${devappserver.address} + + + devappserver.port + ${devappserver.port} + + + warfile + ${project.build.directory}/${project.artifactId} + + + + ${appengine.home}/lib/appengine-tools-api.jar + + + + true + ${appengine.home}/bin + ${appengine.home}/lib + ${appengine.home}/config/sdk + + + + + + + + + + + live + + + + org.apache.maven.plugins + maven-surefire-plugin + + + integration + integration-test + + test + + + + + jclouds.twitter.user + ${jclouds.twitter.user} + + + jclouds.twitter.password + ${jclouds.twitter.password} + + + jclouds.azure.storage.account + ${jclouds.azure.storage.account} + + + jclouds.azure.storage.key + ${jclouds.azure.storage.key} + + + jclouds.rackspace.user + ${jclouds.rackspace.user} + + + jclouds.rackspace.key + ${jclouds.rackspace.key} + + + jclouds.aws.accesskeyid + ${jclouds.aws.accesskeyid} + + + jclouds.aws.secretaccesskey + ${jclouds.aws.secretaccesskey} + + + appengine.home + ${appengine.home} + + + devappserver.address + ${devappserver.address} + + + devappserver.port + ${devappserver.port} + + + jclouds.tweetstore.container + ${jclouds.tweetstore.container} + + + warfile + ${project.build.directory}/${project.artifactId} + + + + + + + + + + + diff --git a/demos/gae-tweetstore/src/main/appengine/appengine-web.xml b/demos/gae-tweetstore/src/main/appengine/appengine-web.xml new file mode 100755 index 0000000000..e2054c7b29 --- /dev/null +++ b/demos/gae-tweetstore/src/main/appengine/appengine-web.xml @@ -0,0 +1,33 @@ + + + + ${appengine.applicationid} + 1 + + + + diff --git a/demos/gae-tweetstore/src/main/appengine/logging.properties b/demos/gae-tweetstore/src/main/appengine/logging.properties new file mode 100644 index 0000000000..9a54a9e984 --- /dev/null +++ b/demos/gae-tweetstore/src/main/appengine/logging.properties @@ -0,0 +1,75 @@ +# +# +# Copyright (C) 2009 Cloud Conscious, LLC. +# +# ==================================================================== +# 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. +# ==================================================================== +# +# +# +# Copyright (C) 2009 Global Cloud Specialists, Inc. +# +# ==================================================================== +# 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: +# +# +# +# +# + +# 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 diff --git a/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java b/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java new file mode 100755 index 0000000000..d065134fa3 --- /dev/null +++ b/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/config/GuiceServletConfig.java @@ -0,0 +1,138 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * 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. + * ==================================================================== + */ +package org.jclouds.demo.tweetstore.config; + +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.servlet.ServletContextEvent; + +import org.apache.commons.io.IOUtils; +import org.jclouds.blobstore.BlobStoreContext; +import org.jclouds.blobstore.BlobStoreContextBuilder; +import org.jclouds.demo.tweetstore.controller.AddTweetsController; +import org.jclouds.demo.tweetstore.controller.StoreTweetsController; +import org.jclouds.gae.config.GaeHttpCommandExecutorServiceModule; +import org.jclouds.twitter.TwitterClient; +import org.jclouds.twitter.TwitterContextFactory; + +import com.google.appengine.repackaged.com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.TypeLiteral; +import com.google.inject.servlet.GuiceServletContextListener; +import com.google.inject.servlet.ServletModule; +import com.google.inject.util.Jsr330; + +/** + * Setup Logging and create Injector for use in testing S3. + * + * @author Adrian Cole + */ +public class GuiceServletConfig extends GuiceServletContextListener { + + private Map> contexts; + private TwitterClient client; + private String container; + + @SuppressWarnings("unchecked") + @Override + public void contextInitialized(ServletContextEvent servletContextEvent) { + Properties props = loadJCloudsProperties(servletContextEvent); + container = checkNotNull(props.getProperty(PROPERTY_TWEETSTORE_CONTAINER), + PROPERTY_TWEETSTORE_CONTAINER); + ImmutableList list = ImmutableList. of(checkNotNull( + props.getProperty(PROPERTY_BLOBSTORE_CONTEXTBUILDERS), + PROPERTY_BLOBSTORE_CONTEXTBUILDERS).split(",")); + contexts = Maps.newHashMap(); + client = TwitterContextFactory + .createContext(props, new GaeHttpCommandExecutorServiceModule()).getApi(); + for (String className : list) { + Class> builderClass; + Constructor> constructor; + String name; + BlobStoreContext context; + try { + builderClass = (Class>) 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); + } + contexts.put(name, context); + } + + 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 { + IOUtils.closeQuietly(input); + } + return props; + } + + @Override + protected Injector getInjector() { + return Guice.createInjector(new ServletModule() { + @Override + protected void configureServlets() { + bind(new TypeLiteral>>() { + }).toInstance(GuiceServletConfig.this.contexts); + bind(TwitterClient.class).toInstance(client); + bindConstant().annotatedWith(Jsr330.named(PROPERTY_TWEETSTORE_CONTAINER)).to(container); + serve("/cron/*").with(StoreTweetsController.class); + serve("/tweets/*").with(AddTweetsController.class); + requestInjection(this); + } + } + + ); + } + + @Override + public void contextDestroyed(ServletContextEvent servletContextEvent) { + for (BlobStoreContext context : contexts.values()) { + context.close(); + } + super.contextDestroyed(servletContextEvent); + } +} \ No newline at end of file diff --git a/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/AddTweetsController.java b/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/AddTweetsController.java new file mode 100755 index 0000000000..1293b4fb8c --- /dev/null +++ b/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/AddTweetsController.java @@ -0,0 +1,101 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * 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. + * ==================================================================== + */ +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, List> { + + /** The serialVersionUID */ + private static final long serialVersionUID = 3888348023150822683L; + private final Map> contexts; + private final ServiceToStoredTweetStatuses blobStoreContextToContainerResult; + + @Resource + protected Logger logger = Logger.NULL; + + @Inject + AddTweetsController(Map> 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 apply(Set in) { + List statuses = Lists.newArrayList(); + for (Iterable list : Iterables.transform(in, + blobStoreContextToContainerResult)) { + Iterables.addAll(statuses, list); + } + return statuses; + } +} \ No newline at end of file diff --git a/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/StoreTweetsController.java b/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/StoreTweetsController.java new file mode 100644 index 0000000000..348b2d2069 --- /dev/null +++ b/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/controller/StoreTweetsController.java @@ -0,0 +1,137 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * 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. + * ==================================================================== + */ +package org.jclouds.demo.tweetstore.controller; + +import java.io.IOException; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.concurrent.TimeUnit; + +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; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; + +/** + * 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 { + 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.setData(from.getText()); + to.getMetadata().getUserMetadata().put(TweetStoreConstants.SENDER_NAME, + from.getUser().getScreenName()); + return to; + } + } + + /** The serialVersionUID */ + private static final long serialVersionUID = 7215420527854203714L; + + private final Set maps; + private final TwitterClient client; + + @Resource + protected Logger logger = Logger.NULL; + + @Inject + StoreTweetsController(Map> contexts, + @Named(TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER) final String container, + TwitterClient client) { + this(Sets.newHashSet(Iterables.transform(contexts.values(), + new Function, BlobMap>() { + public BlobMap apply(BlobStoreContext from) { + return from.createBlobMap(container); + } + })), client); + } + + @VisibleForTesting + StoreTweetsController(Set maps, TwitterClient client) { + this.maps = maps; + this.client = client; + } + + @VisibleForTesting + void addMyTweets(SortedSet allAboutMe) { + for (BlobMap map : maps) { + for (Status status : allAboutMe) { + map.put(status.getId() + "", new StatusToBlob(map).apply(status)); + } + } + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + if (request.getHeader("X-AppEngine-Cron") != null + && request.getHeader("X-AppEngine-Cron").equals("true")) { + try { + logger.info("retrieving tweets"); + addMyTweets(client.getMyMentions().get(1, TimeUnit.SECONDS)); + 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); + } + } + +} \ No newline at end of file diff --git a/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/domain/StoredTweetStatus.java b/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/domain/StoredTweetStatus.java new file mode 100755 index 0000000000..e252e97fab --- /dev/null +++ b/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/domain/StoredTweetStatus.java @@ -0,0 +1,154 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * 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. + * ==================================================================== + */ +package org.jclouds.demo.tweetstore.domain; + +import java.io.Serializable; + +/** + * + * @author Adrian Cole + */ +public class StoredTweetStatus implements Comparable, 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; + } + +} diff --git a/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/functions/KeyToStoredTweetStatus.java b/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/functions/KeyToStoredTweetStatus.java new file mode 100644 index 0000000000..ee6ce2ee6b --- /dev/null +++ b/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/functions/KeyToStoredTweetStatus.java @@ -0,0 +1,77 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * 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. + * ==================================================================== + */ +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 { + 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.getData()); + } 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); + } +} diff --git a/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/functions/ServiceToStoredTweetStatuses.java b/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/functions/ServiceToStoredTweetStatuses.java new file mode 100755 index 0000000000..e8806f13aa --- /dev/null +++ b/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/functions/ServiceToStoredTweetStatuses.java @@ -0,0 +1,76 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * 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. + * ==================================================================== + */ +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> { + + private final Map> contexts; + private final String container; + + @Inject + public ServiceToStoredTweetStatuses(Map> contexts, + @Named(TweetStoreConstants.PROPERTY_TWEETSTORE_CONTAINER) String container) { + this.contexts = contexts; + this.container = container; + } + + @Resource + protected Logger logger = Logger.NULL; + + public Iterable apply(String service) { + BlobStoreContext context = contexts.get(service); + String host = context.getEndPoint().getHost(); + try { + BlobMap blobMap = context.createBlobMap(container); + Set 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); + } + + } +} \ No newline at end of file diff --git a/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/reference/TweetStoreConstants.java b/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/reference/TweetStoreConstants.java new file mode 100644 index 0000000000..aff50a0af5 --- /dev/null +++ b/demos/gae-tweetstore/src/main/java/org/jclouds/demo/tweetstore/reference/TweetStoreConstants.java @@ -0,0 +1,34 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * 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. + * ==================================================================== + */ +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"; +} diff --git a/demos/gae-tweetstore/src/main/webapp/WEB-INF/cron.xml b/demos/gae-tweetstore/src/main/webapp/WEB-INF/cron.xml new file mode 100644 index 0000000000..18f866aa34 --- /dev/null +++ b/demos/gae-tweetstore/src/main/webapp/WEB-INF/cron.xml @@ -0,0 +1,9 @@ + + + + /cron/do + store twitter messages into cache stores + + every 2 minutes + + \ No newline at end of file diff --git a/demos/gae-tweetstore/src/main/webapp/WEB-INF/web.xml b/demos/gae-tweetstore/src/main/webapp/WEB-INF/web.xml new file mode 100755 index 0000000000..693b8dafa6 --- /dev/null +++ b/demos/gae-tweetstore/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,52 @@ + + + + + + jclouds-blobstore-example + + + + guiceFilter + com.google.inject.servlet.GuiceFilter + + + + guiceFilter + /* + + + + + org.jclouds.demo.tweetstore.config.GuiceServletConfig + + + + index.jsp + + + diff --git a/demos/gae-tweetstore/src/main/webapp/index.jsp b/demos/gae-tweetstore/src/main/webapp/index.jsp new file mode 100755 index 0000000000..877d38170a --- /dev/null +++ b/demos/gae-tweetstore/src/main/webapp/index.jsp @@ -0,0 +1,36 @@ +<%-- + + + Copyright (C) 2009 Cloud Conscious, LLC. + + ==================================================================== + 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. + ==================================================================== + +--%> + + +jclouds: anyweight cloudware for java + + +

Welcome!

+Click +here +to see tweets about jclouds. + + diff --git a/demos/gae-tweetstore/src/main/webapp/tweets.jsp b/demos/gae-tweetstore/src/main/webapp/tweets.jsp new file mode 100755 index 0000000000..398c33c00b --- /dev/null +++ b/demos/gae-tweetstore/src/main/webapp/tweets.jsp @@ -0,0 +1,113 @@ +<%-- + + + Copyright (C) 2009 Cloud Conscious, LLC. + + ==================================================================== + 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. + ==================================================================== + +--%> +<%@ page buffer="20kb"%> +<%@ taglib uri="http://displaytag.sf.net" prefix="display"%> + + +jclouds: anyweight cloudware for java + + + +

Tweets in Clouds

+ + + + +
+ +
+ + + + + + + + + + +
+
+ + diff --git a/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/AddTweetsControllerTest.java b/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/AddTweetsControllerTest.java new file mode 100644 index 0000000000..d96f1919e2 --- /dev/null +++ b/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/AddTweetsControllerTest.java @@ -0,0 +1,60 @@ +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.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> createServices(String container) throws InterruptedException, + ExecutionException { + Map> services = Maps.newHashMap(); + for (String name : new String[] { "1", "2" }) { + BlobStoreContext context = new StubBlobStoreContextBuilder().buildContext(); + context.getBlobStore().createContainer(container).get(); + Blob blob = context.getBlobStore().newBlob(); + blob.getMetadata().setName("1"); + blob.getMetadata().getUserMetadata().put(TweetStoreConstants.SENDER_NAME, "frank"); + blob.setData("I love beans!"); + context.getBlobStore().putBlob(container, blob).get(); + services.put(name, context); + } + return services; + } + + public void testStoreTweets() throws IOException, InterruptedException, ExecutionException { + String container = "container"; + Map> contexts = createServices(container); + + ServiceToStoredTweetStatuses function = new ServiceToStoredTweetStatuses(contexts, container); + AddTweetsController controller = new AddTweetsController(contexts, function); + List 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))); + + } +} diff --git a/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/StoreTweetsControllerTest.java b/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/StoreTweetsControllerTest.java new file mode 100644 index 0000000000..3e6bfcf3fb --- /dev/null +++ b/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/controller/StoreTweetsControllerTest.java @@ -0,0 +1,88 @@ +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.Set; +import java.util.SortedSet; +import java.util.concurrent.ExecutionException; + +import org.apache.commons.io.IOUtils; +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.reference.TweetStoreConstants; +import org.jclouds.twitter.TwitterClient; +import org.jclouds.twitter.domain.Status; +import org.jclouds.twitter.domain.User; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; +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); + } + + Set createMaps() throws InterruptedException, ExecutionException { + BlobStoreContext context = new StubBlobStoreContextBuilder().buildContext(); + context.getBlobStore().createContainer("test1").get(); + context.getBlobStore().createContainer("test2").get(); + return ImmutableSet.of(context.createBlobMap("test1"), context.createBlobMap("test2")); + } + + public void testStoreTweets() throws IOException, InterruptedException, ExecutionException { + Set maps = createMaps(); + StoreTweetsController function = new StoreTweetsController(maps, createTwitterClient()); + + SortedSet 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(allAboutMe); + + for (BlobMap map : maps) { + 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(IOUtils.toString((InputStream) frankBlob.getData()), "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(IOUtils.toString((InputStream) jimmyBlob.getData()), "cloud is king"); + } + + } +} diff --git a/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/functions/KeyToStoredTweetStatusTest.java b/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/functions/KeyToStoredTweetStatusTest.java new file mode 100644 index 0000000000..b5aa99dd18 --- /dev/null +++ b/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/functions/KeyToStoredTweetStatusTest.java @@ -0,0 +1,51 @@ +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.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 context = new StubBlobStoreContextBuilder().buildContext(); + context.getBlobStore().createContainer("test1").get(); + 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.setData("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); + + } +} diff --git a/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/functions/ServiceToStoredTweetStatusesTest.java b/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/functions/ServiceToStoredTweetStatusesTest.java new file mode 100644 index 0000000000..15613de9bf --- /dev/null +++ b/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/functions/ServiceToStoredTweetStatusesTest.java @@ -0,0 +1,57 @@ +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.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> createServices(String container) throws InterruptedException, + ExecutionException { + Map> services = Maps.newHashMap(); + for (String name : new String[] { "1", "2" }) { + BlobStoreContext context = new StubBlobStoreContextBuilder().buildContext(); + context.getBlobStore().createContainer(container).get(); + Blob blob = context.getBlobStore().newBlob(); + blob.getMetadata().setName("1"); + blob.getMetadata().getUserMetadata().put(TweetStoreConstants.SENDER_NAME, "frank"); + blob.setData("I love beans!"); + context.getBlobStore().putBlob(container, blob).get(); + services.put(name, context); + } + return services; + } + + public void testStoreTweets() throws IOException, InterruptedException, ExecutionException { + String container = "container"; + Map> 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)); + + } +} diff --git a/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/integration/GoogleDevServer.java b/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/integration/GoogleDevServer.java new file mode 100755 index 0000000000..c42bbbb3c5 --- /dev/null +++ b/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/integration/GoogleDevServer.java @@ -0,0 +1,70 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * 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. + * ==================================================================== + */ +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(); + } + +} \ No newline at end of file diff --git a/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/integration/TweetStoreLiveTest.java b/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/integration/TweetStoreLiveTest.java new file mode 100755 index 0000000000..6363dbc467 --- /dev/null +++ b/demos/gae-tweetstore/src/test/java/org/jclouds/demo/tweetstore/integration/TweetStoreLiveTest.java @@ -0,0 +1,190 @@ +/** + * + * Copyright (C) 2009 Cloud Conscious, LLC. + * + * ==================================================================== + * 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. + * ==================================================================== + */ +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.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.apache.commons.io.IOUtils; +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.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> 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))); + props.setProperty(PROPERTY_BLOBSTORE_CONTEXTBUILDERS, 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().exists(container)) { + System.err.printf("deleting container %s at %s%n", container, context.getEndPoint()); + context.getBlobStore().deleteContainer(container).get(30, TimeUnit.SECONDS); + 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).get(30, TimeUnit.SECONDS); + } + } + + @Test + public void shouldPass() throws InterruptedException, IOException { + InputStream i = url.openStream(); + String string = IOUtils.toString(i); + assert string.indexOf("Welcome") >= 0 : string; + } + + @Test(dependsOnMethods = "shouldPass", expectedExceptions = IOException.class) + public void shouldFail() throws InterruptedException, IOException { + new URL(url, "/cron/do").openStream(); + } + + @Test(dependsOnMethods = "shouldFail") + public void testPrimeContainers() throws IOException { + URL gurl = new URL(url, "/cron/do"); + HttpURLConnection connection = (HttpURLConnection) gurl.openConnection(); + connection.addRequestProperty("X-AppEngine-Cron", "true"); + InputStream i = connection.getInputStream(); + String string = IOUtils.toString(i); + assert string.indexOf("Done!") >= 0 : string; + 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 = IOUtils.toString(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 = IOUtils.toString(i); + assert string.indexOf("Tweets in Clouds") >= 0 : string; + } +} diff --git a/demos/pom.xml b/demos/pom.xml new file mode 100755 index 0000000000..689012f945 --- /dev/null +++ b/demos/pom.xml @@ -0,0 +1,64 @@ + + + + + jclouds-project + org.jclouds + 1.0-SNAPSHOT + ../project/pom.xml + + 4.0.0 + jclouds-demos-project + pom + jclouds demos project + + gae-tweetstore + + + + ${project.groupId} + jclouds-core + ${project.version} + test-jar + test + + + log4j + log4j + 1.2.14 + test + + + ${project.groupId} + jclouds-log4j + ${project.version} + test + + + + diff --git a/twitter/core/src/main/java/org/jclouds/twitter/domain/Status.java b/twitter/core/src/main/java/org/jclouds/twitter/domain/Status.java index 566347c66c..ffe9f8bce0 100644 --- a/twitter/core/src/main/java/org/jclouds/twitter/domain/Status.java +++ b/twitter/core/src/main/java/org/jclouds/twitter/domain/Status.java @@ -53,8 +53,8 @@ public class Status implements Comparable { } public Status(DateTime createdAt, boolean favorited, String geo, long id, - String inReplyToScreenName, Integer inReplyToStatusId, Integer inReplyToUserId, String source, - String text, boolean truncated, User user) { + String inReplyToScreenName, Integer inReplyToStatusId, Integer inReplyToUserId, + String source, String text, boolean truncated, User user) { this.createdAt = createdAt; this.favorited = favorited; this.geo = geo; @@ -72,7 +72,9 @@ public class Status implements Comparable { public int hashCode() { final int prime = 31; int result = 1; + result = prime * result + ((createdAt == null) ? 0 : createdAt.hashCode()); result = prime * result + (int) (id ^ (id >>> 32)); + result = prime * result + ((text == null) ? 0 : text.hashCode()); result = prime * result + ((user == null) ? 0 : user.hashCode()); return result; } @@ -86,8 +88,18 @@ public class Status implements Comparable { if (getClass() != obj.getClass()) return false; Status other = (Status) obj; + if (createdAt == null) { + if (other.createdAt != null) + return false; + } else if (!createdAt.equals(other.createdAt)) + return false; if (id != other.id) return false; + if (text == null) { + if (other.text != null) + return false; + } else if (!text.equals(other.text)) + return false; if (user == null) { if (other.user != null) return false; @@ -177,7 +189,7 @@ public class Status implements Comparable { } public int compareTo(Status o) { - return (int) ((this == o) ? 0 : id - id); + return (int) ((this == o) ? 0 : id + "".compareTo(o.id + "")); } public void setGeo(String geo) { diff --git a/twitter/core/src/main/java/org/jclouds/twitter/domain/User.java b/twitter/core/src/main/java/org/jclouds/twitter/domain/User.java index d88f07d274..248c1a1cb9 100644 --- a/twitter/core/src/main/java/org/jclouds/twitter/domain/User.java +++ b/twitter/core/src/main/java/org/jclouds/twitter/domain/User.java @@ -126,6 +126,7 @@ public class User implements Comparable { int result = 1; result = prime * result + (int) (id ^ (id >>> 32)); result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((screenName == null) ? 0 : screenName.hashCode()); return result; } @@ -145,6 +146,11 @@ public class User implements Comparable { return false; } else if (!name.equals(other.name)) return false; + if (screenName == null) { + if (other.screenName != null) + return false; + } else if (!screenName.equals(other.screenName)) + return false; return true; } diff --git a/twitter/core/src/test/java/org/jclouds/twitter/functions/ParseStatusesFromJsonResponseTest.java b/twitter/core/src/test/java/org/jclouds/twitter/functions/ParseStatusesFromJsonResponseTest.java index a4abebba58..ef26754638 100644 --- a/twitter/core/src/test/java/org/jclouds/twitter/functions/ParseStatusesFromJsonResponseTest.java +++ b/twitter/core/src/test/java/org/jclouds/twitter/functions/ParseStatusesFromJsonResponseTest.java @@ -41,6 +41,39 @@ public class ParseStatusesFromJsonResponseTest { SortedSet expects = ImmutableSortedSet .of( + new Status( + dateService.cDateParse("Sat Oct 31 01:45:14 +0000 2009"), + false, + null, + 5303839785l, + null, + null, + null, + "TweetDeck", + "RT @jclouds: come find out about #cloud storage and how to access it from #java in palo alto this Tuesday: http://is.gd/4IFA9", + false, + new User( + dateService.cDateParse("Sat Apr 26 06:13:08 +0000 2008"), + "Jack of All Trades: Dad to anZel and Arden, VMware, vCloud, Security, Compliance, Former Developer", + 0, + 474, + false, + 199, + false, + 14540593, + "Bay Area, CA", + "Jian Zhen", + false, + "C6E2EE", + URI + .create("http://s.twimg.com/a/1256778767/images/themes/theme2/bg.gif"), + false, + URI + .create("http://a3.twimg.com/profile_images/64445411/30b8b19_bigger_normal.jpg"), + "1F98C7", "C6E2EE", "DAECF4", "663B12", false, "zhenjl", + 1981, "Pacific Time (US & Canada)", URI + .create("http://zhen.org"), -28800, false)), + new Status( dateService.cDateParse("Sat Oct 31 09:35:27 +0000 2009"), false, @@ -80,39 +113,7 @@ public class ParseStatusesFromJsonResponseTest { "Pacific Time (US & Canada)", URI .create("http://siliconangle.net/ver2/author/jwatters/"), - -28800, false)), - new Status( - dateService.cDateParse("Sat Oct 31 01:45:14 +0000 2009"), - false, - null, - 5303839785l, - null, - null, - null, - "TweetDeck", - "RT @jclouds: come find out about #cloud storage and how to access it from #java in palo alto this Tuesday: http://is.gd/4IFA9", - false, - new User( - dateService.cDateParse("Sat Apr 26 06:13:08 +0000 2008"), - "Jack of All Trades: Dad to anZel and Arden, VMware, vCloud, Security, Compliance, Former Developer", - 0, - 474, - false, - 199, - false, - 14540593, - "Bay Area, CA", - "Jian Zhen", - false, - "C6E2EE", - URI - .create("http://s.twimg.com/a/1256778767/images/themes/theme2/bg.gif"), - false, - URI - .create("http://a3.twimg.com/profile_images/64445411/30b8b19_bigger_normal.jpg"), - "1F98C7", "C6E2EE", "DAECF4", "663B12", false, "zhenjl", - 1981, "Pacific Time (US & Canada)", URI - .create("http://zhen.org"), -28800, false)) + -28800, false)) ); @@ -121,5 +122,4 @@ public class ParseStatusesFromJsonResponseTest { SortedSet response = parser.apply(is); assertEquals(response, expects); } - }