diff --git a/.gitignore b/.gitignore index 17cecc2..9d93787 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea .classpath .project .settings/ diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..3b00020 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index d863697..9ba29c5 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ $ git clone https://github.com/scratches/spring-boot-legacy $ (cd spring-boot-legacy; mvn install) $ git clone https://github.com/scratches/spring-boot-sample-gae $ cd spring-boot-sample-gae -$ mvn gae:deploy +$ mvn appengine:update ``` -Also runs as a deployed WAR in WTP or regular Tomcat container. The `main()` app (normal Spring Boot launcher) should also work. \ No newline at end of file +Also runs as a deployed jar in a Jetty container. The `main()` app (normal Spring Boot launcher) should also work. \ No newline at end of file diff --git a/gae-demo.iml b/gae-demo.iml new file mode 100644 index 0000000..26c36e3 --- /dev/null +++ b/gae-demo.iml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index f3eaeec..b2ac86b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.demo gae-demo - 0.0.1-SNAPSHOT + 0.0.2-SNAPSHOT war gae @@ -14,35 +14,36 @@ org.springframework.boot spring-boot-starter-parent - 1.1.0.BUILD-SNAPSHOT + 1.1.2.BUILD-SNAPSHOT - - org.springframework.boot - spring-boot-starter-web - - + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-jetty + + + org.springframework.boot spring-boot-starter-actuator - - org.springframework.boot - spring-boot-starter-tomcat - provided - + org.springframework.boot spring-boot-legacy - 1.1.0.BUILD-SNAPSHOT - - - net.kindleit - gae-runtime - ${gae.version} - pom - provided + 1.1.2.BUILD-SNAPSHOT + org.springframework.boot spring-boot-starter-test @@ -79,7 +80,7 @@ UTF-8 1.7 / - 1.8.8 + 1.9.12 ${settings.localRepository}/com/google/appengine/appengine-java-sdk/${gae.version}/appengine-java-sdk-${gae.version} test @@ -90,33 +91,29 @@ org.springframework.boot spring-boot-maven-plugin - - net.kindleit - maven-gae-plugin - 0.9.6 - - - net.kindleit - gae-runtime - ${gae.version} - pom - - - - - maven-release-plugin - - gae:deploy - - - - org.apache.tomcat.maven - tomcat6-maven-plugin - 2.0 - - / - - + + com.google.appengine + appengine-maven-plugin + ${gae.version} + + false + + -Xdebug + -agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n + + + + + + endpoints_get_discovery_doc + + + src/main/webapp/WEB-INF/web.xml + + + + + diff --git a/src/main/java/demo/Application.java b/src/main/java/demo/Application.java index 76be452..09c2ba8 100644 --- a/src/main/java/demo/Application.java +++ b/src/main/java/demo/Application.java @@ -4,27 +4,17 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; @Configuration -@ComponentScan @EnableAutoConfiguration -@RestController -public class Application { +@ComponentScan +public class Application +{ + public static void main(String[] args) { SpringApplication.run(Application.class, args); } - - @RequestMapping("/") - public String home() { - return "Hello World"; - } - @RequestMapping("/version") - public String getVersion() { - return "1.0"; - } } diff --git a/src/main/java/demo/Item.java b/src/main/java/demo/Item.java new file mode 100644 index 0000000..47bdeb4 --- /dev/null +++ b/src/main/java/demo/Item.java @@ -0,0 +1,46 @@ +package demo; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Item { + private static final Logger log = LoggerFactory.getLogger(Item.class); + private final String id = java.util.UUID.randomUUID().toString(); + private final String value; + private long creationTimeMs = 0; + + public Item(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void startClockTicking() { + if (creationTimeMs == 0) //protect against being called twice. + { + creationTimeMs = System.currentTimeMillis(); + log.info("started clock ticking"); + } + } + + public long getCreationTimeMs() { + return creationTimeMs; + } + + + public String getId() { + return id; + } + + @Override + public String toString() { + return "Item{" + + "id='" + id + '\'' + + ", value='" + value + '\'' + + ", creationTimeMs=" + creationTimeMs + + '}'; + } +} + diff --git a/src/main/java/demo/QueueItemListener.java b/src/main/java/demo/QueueItemListener.java new file mode 100644 index 0000000..8b4e9af --- /dev/null +++ b/src/main/java/demo/QueueItemListener.java @@ -0,0 +1,7 @@ +package demo; + + +public interface QueueItemListener +{ + void onTrigger(Item item); +} diff --git a/src/main/java/demo/controller/CronController.java b/src/main/java/demo/controller/CronController.java new file mode 100644 index 0000000..3a81b8a --- /dev/null +++ b/src/main/java/demo/controller/CronController.java @@ -0,0 +1,55 @@ +package demo.controller; + +import demo.service.SimpleItemQueue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; + +/** + * Created by will on 01/10/2014. + */ + +@RestController +public class CronController { + + private static final Logger log = LoggerFactory.getLogger(CronController.class); + + @Autowired + SimpleItemQueue simpleItemQueue; + + + @RequestMapping(value = "/cron/item", + method = RequestMethod.GET, + consumes = "*", + headers = {"content-type=application/X-AppEngine-Cron"}) + @ResponseBody + public ResponseEntity purgeQueue(HttpServletRequest request) + { + + + log.info("== called =="); + + log.debug("userAgent =" + request.getHeader("User-Agent")); + log.debug("host =" + request.getHeader("Host")); + log.debug("X-AppEngine-Cron =" + request.getHeader("X-AppEngine-Cron")); + log.debug("X-AppEngine-QueueName =" + request.getHeader("X-AppEngine-QueueName")); + log.debug("X-AppEngine-TaskName =" + request.getHeader("X-AppEngine-TaskName")); + log.debug("X-AppEngine-TaskRetryCount =" + request.getHeader("X-AppEngine-TaskRetryCount")); + + boolean isCronTask = "true".equals(request.getHeader("X-AppEngine-Cron")); + //String queueName = request.getHeader("X-AppEngine-QueueName"); + + if(isCronTask) + simpleItemQueue.manuallySeekTimeouts(); + else + log.warn("someone tried to invoke cron"); + + return new ResponseEntity(HttpStatus.OK); + } + +} diff --git a/src/main/java/demo/controller/ItemController.java b/src/main/java/demo/controller/ItemController.java new file mode 100644 index 0000000..b02ff53 --- /dev/null +++ b/src/main/java/demo/controller/ItemController.java @@ -0,0 +1,60 @@ +package demo.controller; + +import demo.Item; +import demo.pojo.ItemDisplay; +import demo.service.SimpleItemQueue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * Created by will on 01/10/2014. + */ + +@RestController +public class ItemController { + + private static final Logger log = LoggerFactory.getLogger(ItemController.class); + + @Autowired + SimpleItemQueue simpleItemQueue; + + @RequestMapping("/") + public String home() { + return "Hello World"; + } + + @RequestMapping("/version") + public String getVersion() { + return "1.0"; + } + + + @RequestMapping(value = "/item/add", method = RequestMethod.POST, consumes = "application/json", + produces = "application/json", + headers = {"content-type=application/json"}) + @ResponseBody + public void submitRfq(@RequestBody ItemDisplay itemDisplay) { + log.info("adding " + itemDisplay.value + " to queue"); + + Item item = new Item(itemDisplay.value); + simpleItemQueue.add(item); + } + + @RequestMapping(value = "/item/size", + method = RequestMethod.GET, + consumes = "*", + produces = "application/json", + headers = {"content-type=application/json"}) + @ResponseBody + public String getAll() { + + return String.valueOf(simpleItemQueue.size()); + + } + + +} diff --git a/src/main/java/demo/pojo/ItemDisplay.java b/src/main/java/demo/pojo/ItemDisplay.java new file mode 100644 index 0000000..160a711 --- /dev/null +++ b/src/main/java/demo/pojo/ItemDisplay.java @@ -0,0 +1,8 @@ +package demo.pojo; + +/** + * Created by will on 01/10/2014. + */ +public class ItemDisplay { + public String value; +} diff --git a/src/main/java/demo/queue/IQueueService.java b/src/main/java/demo/queue/IQueueService.java new file mode 100644 index 0000000..fb5ada3 --- /dev/null +++ b/src/main/java/demo/queue/IQueueService.java @@ -0,0 +1,18 @@ +package demo.queue; + +import demo.Item; +import demo.QueueItemListener; + + +public interface IQueueService +{ + void add(Item item); + + int size(); + + Item find(String id); + + void manuallySeekTimeouts(); + + void clear(); +} diff --git a/src/main/java/demo/service/SimpleItemQueue.java b/src/main/java/demo/service/SimpleItemQueue.java new file mode 100644 index 0000000..fc3c712 --- /dev/null +++ b/src/main/java/demo/service/SimpleItemQueue.java @@ -0,0 +1,104 @@ +package demo.service; + + +import demo.Item; +import demo.QueueItemListener; +import demo.queue.IQueueService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.Iterator; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * This is a queue that self purges every 30 seconds. + */ + +@Service +public class SimpleItemQueue implements IQueueService +{ + private static final Logger log = LoggerFactory.getLogger(SimpleItemQueue.class); + + static String MAX_QUEUE_LIFETIME_SECONDS = "30"; + + private final Long timeout = Long.parseLong(MAX_QUEUE_LIFETIME_SECONDS) * 1000L; + private final ConcurrentLinkedQueue fifoQueue = new ConcurrentLinkedQueue<>(); + private QueueItemListener rfqListener = new SelfPurger(); + + + public SimpleItemQueue(){ + log.info("called"); + } + + @Override + public void add(Item rfq) + { + rfq.startClockTicking(); + fifoQueue.add(rfq); + } + + @Override + public int size() + { + return fifoQueue.size(); + } + + + @Override + public Item find(String id) + { + Iterator iterator = fifoQueue.iterator(); + Item found = null; + while(iterator.hasNext()) + { + found = iterator.next(); + if(found.getId().equals(id)) + return found; + } + + return null; + } + + @Override + public void manuallySeekTimeouts() + { + + log.info(" seeking timeout"); + long now = System.currentTimeMillis(); + long elapsedTimeMs; + Iterator iterator = fifoQueue.iterator(); + while(iterator.hasNext()) + { + Item item = iterator.next(); + elapsedTimeMs = now - item.getCreationTimeMs(); + if(elapsedTimeMs >= timeout) + { + log.info("found a timedout id = " + item.getId()); + + rfqListener.onTrigger(item); + } + else + { + log.info("elapsedTimeMs = " + elapsedTimeMs); + } + } + + } + + @Override + public void clear() { + fifoQueue.clear(); + } + + + class SelfPurger implements QueueItemListener + { + + @Override + public void onTrigger(Item rfq) + { + fifoQueue.remove(rfq); + } + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 266ac62..a8afa73 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,4 +1,8 @@ + + + + diff --git a/src/main/webapp/WEB-INF/appengine-web.xml b/src/main/webapp/WEB-INF/appengine-web.xml index 23972a2..8219f6e 100644 --- a/src/main/webapp/WEB-INF/appengine-web.xml +++ b/src/main/webapp/WEB-INF/appengine-web.xml @@ -1,6 +1,6 @@ - dsyerboot - 4 + your_unique_app_here + 3 true \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/cron.xml b/src/main/webapp/WEB-INF/cron.xml new file mode 100644 index 0000000..0cc46b7 --- /dev/null +++ b/src/main/webapp/WEB-INF/cron.xml @@ -0,0 +1,8 @@ + + + + /cron/item/ + polling item Queue + every 1 mins synchronized + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 6e9d6f9..b35ed50 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -12,16 +12,6 @@ org.springframework.boot.legacy.context.web.SpringBootContextLoaderListener - - metricFilter - org.springframework.web.filter.DelegatingFilterProxy - - - - metricFilter - /* - - appServlet org.springframework.web.servlet.DispatcherServlet @@ -37,4 +27,10 @@ / + + defaultHtmlEscape + true + + + diff --git a/src/test/java/demo/AllIntegrationTests.java b/src/test/java/demo/BasicIntegrationTest.java similarity index 90% rename from src/test/java/demo/AllIntegrationTests.java rename to src/test/java/demo/BasicIntegrationTest.java index 294467e..5a4e9c0 100644 --- a/src/test/java/demo/AllIntegrationTests.java +++ b/src/test/java/demo/BasicIntegrationTest.java @@ -25,9 +25,9 @@ @SpringApplicationConfiguration(classes = Application.class) @WebAppConfiguration @IntegrationTest("server.port=0") -public class AllIntegrationTests { +public class BasicIntegrationTest { - private static final Logger log = Logger.getLogger(AllIntegrationTests.class); + private static final Logger log = Logger.getLogger(BasicIntegrationTest.class); @Value("${local.server.port}") private int port; diff --git a/src/test/java/demo/mock/CronSimulator.java b/src/test/java/demo/mock/CronSimulator.java new file mode 100644 index 0000000..2da83c8 --- /dev/null +++ b/src/test/java/demo/mock/CronSimulator.java @@ -0,0 +1,45 @@ +package demo.mock; +/* + * + * This class has a thread inside it - to simulate cron + */ + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import demo.queue.*; + +public class CronSimulator implements Runnable { + private static final Logger log = LoggerFactory.getLogger(CronSimulator.class); + private final ScheduledExecutorService cronSimulator = Executors.newScheduledThreadPool(1); + private ScheduledFuture scannerHandle; + + private final IQueueService listener; + + public CronSimulator(IQueueService listener) { + this.listener = listener; + } + + + public void start() { + log.info("start"); + scannerHandle = cronSimulator.scheduleAtFixedRate(this, 0, 3, TimeUnit.SECONDS); + } + + public void stop() { + log.info("stop"); + scannerHandle.cancel(true); + } + + @Override + public void run() { + listener.manuallySeekTimeouts(); + } + + +} diff --git a/src/test/java/demo/service/SimpleItemQueueTest.java b/src/test/java/demo/service/SimpleItemQueueTest.java new file mode 100644 index 0000000..34b20a1 --- /dev/null +++ b/src/test/java/demo/service/SimpleItemQueueTest.java @@ -0,0 +1,75 @@ +package demo.service; + + +import demo.Application; +import demo.Item; +import demo.mock.CronSimulator; +import org.junit.After; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import javax.annotation.PostConstruct; + +@ActiveProfiles("test") +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = Application.class) +@WebAppConfiguration +@IntegrationTest +public class SimpleItemQueueTest +{ + private static final Logger log = LoggerFactory.getLogger(SimpleItemQueueTest.class); + + @BeforeClass + public static void init() + { + SimpleItemQueue.MAX_QUEUE_LIFETIME_SECONDS = "3"; + } + + @Autowired + private SimpleItemQueue simpleItemQueue; + + private CronSimulator cronSimulator; + + @PostConstruct + public void start() + { + cronSimulator = new CronSimulator(simpleItemQueue); + } + + @After + public void end() + { + simpleItemQueue.clear(); + cronSimulator.stop(); + } + + @Test + public void testCleanExpiredTokens() throws Exception + { + cronSimulator.start(); + + Item one = new Item("one"); + + simpleItemQueue.add(one); + + Assert.assertEquals(1, simpleItemQueue.size()); + + log.info("sleeping"); + Thread.sleep(6000); + log.info("waking"); + + Assert.assertEquals(0, simpleItemQueue.size()); + + + } +} \ No newline at end of file