1. Atlassian Tutorials
  2. Untitled project
  3. jira-scheduled-events

Commits

Mary Anthony  committed 2b3d9c0

Moving source code to BB atlasian_tutorials

  • Participants
  • Branches master

Comments (0)

Files changed (18)

File LICENSE

View file
+Copyright (c) 2010, Atlassian Pty Ltd
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the Atlassian Pty Ltd nor the names of its
+      contributors may be used to endorse or promote products derived from
+      this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

File README

View file
+Shows you how to schedule Java tasks in your plugin that run in the background at regular 
+intervals. To this end we will use the cross-product PluginScheduler component from SAL 
+(Shared Access Layer).
+
+
+Full documentation is always available at:
+
+https://developer.atlassian.com/display/DOCS/Plugin+Tutorial+-+Scheduling+Events+via+SAL

File pom.xml

View file
+<?xml version="1.0" encoding="UTF-8"?>
+
+<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>
+    <groupId>com.atlassian.example</groupId>
+    <artifactId>scheduler</artifactId>
+    <version>1.1-SNAPSHOT</version>
+
+    <parent>
+        <groupId>com.atlassian.pom</groupId>
+        <artifactId>atlassian-public-pom</artifactId>
+        <version>20</version>
+    </parent>
+
+    <organization>
+        <name>Example Company</name>
+        <url>http://www.example.com/</url>
+    </organization>
+
+    <name>scheduler</name>
+    <description>This is the com.atlassian.example:scheduler plugin for Atlassian JIRA.</description>
+    <packaging>atlassian-plugin</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.atlassian.jira</groupId>
+            <artifactId>atlassian-jira</artifactId>
+            <version>${jira.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.homeip.yusuke</groupId>
+            <artifactId>twitter4j</artifactId>
+            <version>2.0.10</version>
+        </dependency>
+        <dependency>
+            <groupId>com.atlassian.sal</groupId>
+            <artifactId>sal-api</artifactId>
+            <version>2.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.6</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.8.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.atlassian.jira</groupId>
+            <artifactId>jira-func-tests</artifactId>
+            <version>${jira.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.atlassian.maven.plugins</groupId>
+                <artifactId>maven-jira-plugin</artifactId>
+                <version>3.0.4</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <productVersion>${jira.version}</productVersion>
+                    <productDataVersion>${jira.data.version}</productDataVersion>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.5</source>
+                    <target>1.5</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <scm>
+        <connection>scm:svn:https://svn.atlassian.com/svn/public/contrib/tutorials/scheduled-events/trunk</connection>
+        <developerConnection>scm:svn:https://svn.atlassian.com/svn/public/contrib/tutorials/scheduled-events/trunk</developerConnection>
+        <url>http://svn.atlassian.com/fisheye/browse/public/contrib/tutorials/scheduled-events/trunk</url>
+    </scm>
+
+    <properties>
+        <jira.version>4.0.1</jira.version>
+        <jira.data.version>4.0</jira.data.version>
+    </properties>
+
+</project>

File screenshots/screen1.png

Added
New image

File screenshots/screen2.png

Added
New image

File screenshots/screen3.png

Added
New image

File src/main/java/com/atlassian/example/scheduling/SchedulerAction.java

View file
+package com.atlassian.example.scheduling;
+
+import com.atlassian.jira.web.action.JiraWebActionSupport;
+import twitter4j.Tweet;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author  Erik van Zijst
+ */
+public class SchedulerAction extends JiraWebActionSupport {
+
+    private final TwitterMonitor twitterMonitor;
+    private String query;
+    private long interval;
+
+    public SchedulerAction(TwitterMonitor twitterMonitor) {
+        this.twitterMonitor = twitterMonitor;
+        this.query = twitterMonitor.getQuery();
+        this.interval = twitterMonitor.getInterval();
+    }
+
+    @Override
+    protected String doExecute() throws Exception {
+        return SUCCESS;
+    }
+
+    public String doReschedule() {
+        twitterMonitor.reschedule(query, interval);
+        return getRedirect("TwitterScheduler!default.jspa");
+    }
+
+    public List<Tweet> getTweets() {
+        return twitterMonitor.getTweets();
+    }
+
+    public String getQuery() {
+        return query;
+    }
+
+    public void setQuery(String query) {
+        this.query = query;
+    }
+
+    public long getInterval() {
+        return interval;
+    }
+
+    public void setInterval(long interval) {
+        this.interval = interval;
+    }
+
+    public Date getLastRun() {
+        return twitterMonitor.getLastRun();
+    }
+
+    public Utils getUtils() {
+        return new Utils();
+    }
+}

File src/main/java/com/atlassian/example/scheduling/TwitterMonitor.java

View file
+package com.atlassian.example.scheduling;
+
+import twitter4j.Tweet;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author  Erik van Zijst
+ */
+public interface TwitterMonitor {
+
+    public String getQuery();
+
+    public long getInterval();
+
+    public List<Tweet> getTweets();
+
+    public Date getLastRun();
+
+    public void reschedule(String query, long interval);
+}

File src/main/java/com/atlassian/example/scheduling/TwitterMonitorImpl.java

View file
+package com.atlassian.example.scheduling;
+
+import com.atlassian.sal.api.lifecycle.LifecycleAware;
+import com.atlassian.sal.api.scheduling.PluginScheduler;
+import org.apache.log4j.Logger;
+import twitter4j.Tweet;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This plugin component schedules a Twitter search task through the SAL
+ * {@link com.atlassian.sal.api.scheduling.PluginScheduler}.
+ *
+ * @author  Erik van Zijst
+ */
+public class TwitterMonitorImpl implements TwitterMonitor, LifecycleAware {
+
+    /* package */ static final String KEY = TwitterMonitorImpl.class.getName() + ":instance";
+    private static final String JOB_NAME = TwitterMonitorImpl.class.getName() + ":job";
+
+    private final Logger logger = Logger.getLogger(TwitterMonitorImpl.class);
+    private final PluginScheduler pluginScheduler;  // provided by SAL
+
+    private String query = "Atlassian"; // default Twitter search
+    private long interval = 5000L;      // default job interval (5 sec)
+    private List<Tweet> tweets;         // results of the last search
+    private Date lastRun = null;        // time when the last search returned
+
+    public TwitterMonitorImpl(PluginScheduler pluginScheduler) {
+        this.pluginScheduler = pluginScheduler;
+    }
+
+    // declared by LifecycleAware
+    public void onStart() {
+        reschedule(query, interval);
+    }
+
+    public void reschedule(String query, long interval) {
+        this.query = query;
+        this.interval = interval;
+        
+        pluginScheduler.scheduleJob(
+                JOB_NAME,                   // unique name of the job
+                TwitterQueryTask.class,     // class of the job
+                new HashMap<String,Object>() {{
+                    put(KEY, TwitterMonitorImpl.this);
+                }},                         // data that needs to be passed to the job
+                new Date(),                 // the time the job is to start
+                interval);                  // interval between repeats, in milliseconds
+        logger.info(String.format("Twitter search task scheduled to run every %dms", interval));
+    }
+
+    public String getQuery() {
+        return query;
+    }
+
+    public long getInterval() {
+        return interval;
+    }
+
+    /* package */ void setTweets(List<Tweet> tweets) {
+        this.tweets = tweets;
+    }
+
+    /* package */ void setLastRun(Date lastRun) {
+        this.lastRun = lastRun;
+    }
+
+    public Date getLastRun() {
+        return lastRun;
+    }
+
+    public List<Tweet> getTweets() {
+        return tweets;
+    }
+}

File src/main/java/com/atlassian/example/scheduling/TwitterQueryTask.java

View file
+package com.atlassian.example.scheduling;
+
+import com.atlassian.sal.api.scheduling.PluginJob;
+import org.apache.log4j.Logger;
+import twitter4j.Query;
+import twitter4j.Twitter;
+import twitter4j.TwitterException;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * This is the background task that performs the Twitter search and stores the
+ * result in {@link com.atlassian.example.scheduling.TwitterMonitorImpl}.
+ *
+ * @author  Erik van Zijst
+ */
+public class TwitterQueryTask implements PluginJob {
+
+    private final Logger logger = Logger.getLogger(TwitterQueryTask.class);
+
+    /**
+     * Executes this job.
+     *
+     * @param jobDataMap any data the job needs to execute. Changes to this data will be remembered between executions.
+     */
+    public void execute(Map<String, Object> jobDataMap) {
+
+        final TwitterMonitorImpl monitor = (TwitterMonitorImpl)jobDataMap.get(TwitterMonitorImpl.KEY);
+        assert monitor != null;
+        try {
+            final Twitter twitter = new Twitter();
+            monitor.setTweets(twitter.search(new Query(monitor.getQuery())).getTweets());
+            monitor.setLastRun(new Date());
+        } catch (TwitterException te) {
+            logger.error("Error talking to Twitter: " + te.getMessage(), te);
+        }
+    }
+}

File src/main/java/com/atlassian/example/scheduling/Utils.java

View file
+package com.atlassian.example.scheduling;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ * @author  Erik van Zijst
+ */
+public class Utils {
+
+    public static String htmlEncode(byte[] bytes) {
+        return htmlEncode(new String(bytes));
+    }
+
+    private static void htmlEncode(char c, Writer w) throws IOException {
+        if (c == '&') {
+            w.write("&amp;");
+        } else if (c == '<') {
+            w.write("&lt;");
+        } else if (c == '>') {
+            w.write("&gt;");
+        } else if (c == '"') {
+            w.write("&#34;");
+        } else if (c == '\'') {
+            w.write("&#39;");
+        } else if (((int) c) > 128) {
+            w.write("&#" + ((int) c) + ";");
+        } else {
+            w.write(c);
+        }
+    }
+
+    public static void htmlEncode(CharSequence s, Writer w, int start, int len) throws IOException {
+        if (s == null) {
+            return;
+        }
+
+        for (int i = start; i < start + len; i++) {
+            char c = s.charAt(i);
+            htmlEncode(c, w);
+        }
+    }
+
+    public static void htmlEncode(CharSequence s, Writer w) throws IOException {
+        if (s == null) {
+            return;
+        }
+        htmlEncode(s, w, 0, s.length());
+    }
+
+    public static String htmlEncode(CharSequence s) {
+        StringWriter out = new StringWriter();
+
+        try {
+            htmlEncode(s, out);
+        } catch (IOException e) {
+            // should never happen
+        }
+        return out.toString();
+    }
+
+    public static String htmlEncode(Object o) {
+        if (o == null) {
+            return "";
+        }
+        return Utils.htmlEncode(o.toString());
+    }
+}

File src/main/resources/atlassian-plugin.xml

View file
+<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.artifactId}" plugins-version="2">
+    <plugin-info>
+        <description>${project.description}</description>
+        <version>${project.version}</version>
+        <vendor name="${project.organization.name}" url="${project.organization.url}" />
+    </plugin-info>
+
+    <resource type="i18n" name="i18n" location="com.atlassian.example.scheduling.TwitterSchedulerBundle"/>
+
+    <component-import key="pluginScheduler">
+        <description>SAL Scheduler</description>
+        <interface>com.atlassian.sal.api.scheduling.PluginScheduler</interface>
+    </component-import>
+
+    <component key="schedulerComponent" class="com.atlassian.example.scheduling.TwitterMonitorImpl"
+             system="true" public="true">
+        <description>The plugin component that schedules the Twitter search.</description>
+        <interface>com.atlassian.sal.api.lifecycle.LifecycleAware</interface>
+        <interface>com.atlassian.example.scheduling.TwitterMonitor</interface>
+    </component>
+
+    <web-item key="schedulerActionLink" section="system.admin/system"
+            i18n-name-key="com.atlassian.example.scheduling.adminLink"
+            name="Scheduled Twitter Search"
+            weight="1">
+        <label key="com.atlassian.example.scheduling.adminLink"/>
+        <link linkId="schedulerActionLink">/secure/admin/TwitterScheduler.jspa</link>
+    </web-item>
+
+    <webwork1 key="schedulerAction" name="SAL Scheduler Example">
+        <actions>
+            <action name="com.atlassian.example.scheduling.SchedulerAction"
+                    alias="TwitterScheduler">
+                <view name="success">/templates/scheduler.vm</view>
+                <view name="input">/templates/scheduler.vm</view>
+            </action>
+        </actions>
+    </webwork1>
+</atlassian-plugin>

File src/main/resources/com/atlassian/example/scheduling/TwitterSchedulerBundle.properties

View file
+com.atlassian.example.scheduling.title=SAL Plugin Scheduler Example
+com.atlassian.example.scheduling.adminLink=Scheduled Twitter Search
+com.atlassian.example.scheduling.instructions=This plugin demonstrates the use of the SAL Scheduler \
+  component. The background task it runs is a public search on Twitter. The results of the search \
+  are displayed below. You can change the search string as well as the interval.
+com.atlassian.example.scheduling.queryCell=Search query:
+com.atlassian.example.scheduling.intervalCell=Interval (in milliseconds):
+com.atlassian.example.scheduling.applyButton=Apply
+com.atlassian.example.scheduling.result.header.imageUrl=
+com.atlassian.example.scheduling.result.header.from=Sender
+com.atlassian.example.scheduling.result.header.tweet=Tweet
+com.atlassian.example.scheduling.result.header.date=Time
+com.atlassian.example.scheduling.lastRun=Last search for ran at:

File src/main/resources/com/atlassian/example/scheduling/TwitterSchedulerBundle_en.properties

View file
+com.atlassian.example.scheduling.title=SAL Plugin Scheduler Example
+com.atlassian.example.scheduling.adminLink=Scheduled Twitter Search
+com.atlassian.example.scheduling.instructions=This plugin demonstrates the use of the SAL Scheduler \
+  component. The background task it runs is a public search on Twitter. The results of the search \
+  are displayed below. You can change the search string as well as the interval.
+com.atlassian.example.scheduling.queryCell=Search query:
+com.atlassian.example.scheduling.intervalCell=Interval (in milliseconds):
+com.atlassian.example.scheduling.applyButton=Apply
+com.atlassian.example.scheduling.result.header.imageUrl=
+com.atlassian.example.scheduling.result.header.from=Sender
+com.atlassian.example.scheduling.result.header.tweet=Tweet
+com.atlassian.example.scheduling.result.header.date=Time
+com.atlassian.example.scheduling.lastRun=Last search for ran at:

File src/main/resources/templates/scheduler.vm

View file
+<html>
+    <head>
+		<title>$i18n.getText("com.atlassian.example.scheduling.title")</title>
+        <meta name="decorator" content="atl.admin">
+    </head>
+<body>
+<table width="100%" cellspacing="0" cellpadding="10" border="0">
+    <tbody>
+        <tr>
+            <td>
+                <table class="jiraform maxWidth">
+                    <tbody>
+                        <tr>
+                            <td class="jiraformheader">
+                                <h3 class="formtitle">$i18n.getText("com.atlassian.example.scheduling.title")</h3>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td class="jiraformbody">
+                                <p>
+                                    $i18n.getText("com.atlassian.example.scheduling.instructions")
+                                </p>
+                                <form method="post" action="TwitterScheduler!reschedule.jspa">
+                                    <p>
+                                        <table>
+                                            <tr>
+                                                <td>$i18n.getText("com.atlassian.example.scheduling.queryCell")</td>
+                                                <td><input type="text" name="query" value="$query"></td>
+                                            </tr>
+                                            <tr>
+                                                <td>$i18n.getText("com.atlassian.example.scheduling.intervalCell")</td>
+                                                <td><input type="text" name="interval" value="$interval"></td>
+                                            </tr>
+                                            <tr>
+                                                <td colspan="2"><input type="submit" value="$i18n.getText("com.atlassian.example.scheduling.applyButton")"></td>
+                                            </tr>
+                                        </table>
+                                    </p>
+                                </form>
+                            </td>
+                        </tr>
+                    </tbody>
+                </table>
+                <p>
+                </p>
+            </td>
+        </tr>
+        <tr>
+            <td>
+                <table class="jiraform maxWidth">
+                    <thead class="jiraformheader">
+                        <tr>
+                            <th colspan="2">$i18n.getText("com.atlassian.example.scheduling.result.header.from")</th>
+                            <th>$i18n.getText("com.atlassian.example.scheduling.result.header.tweet")</th>
+                            <th>$i18n.getText("com.atlassian.example.scheduling.result.header.date")</th>
+                        </tr>
+                    </thead>
+                    <tbody id="tweets">
+                        #foreach ( $tweet in $tweets )
+                        <tr>
+                            <td><img src="$tweet.profileImageUrl" width="48" height="48"></td>
+                            <td>$utils.htmlEncode($tweet.fromUser)</td>
+                            <td>$tweet.text</td>    ## twitter4j seems to produce html encoded strings:
+                            <td>$tweet.createdAt</td>
+                        </tr>
+                        #end
+                    </tbody>
+                </table>
+                <div style="text-align: center;">$i18n.getText("com.atlassian.example.scheduling.lastRun") <b>$lastRun</b></div>
+            </td>
+        </tr>
+    </tbody>
+</table>
+</body>
+</html>

File src/test/java/com/atlassian/example/scheduling/SchedulerActionTest.java

View file
+package com.atlassian.example.scheduling;
+
+import org.junit.Test;
+
+import static org.mockito.Mockito.*;
+
+/**
+ * @author  Erik van Zijst
+ */
+public class SchedulerActionTest {
+
+    @Test
+    public void testReadonlyAction() throws Exception {
+
+        final TwitterMonitor twitterMonitor = mock(TwitterMonitor.class);
+
+        SchedulerAction action = new SchedulerAction(twitterMonitor);
+        action.setInterval(1000L);
+        action.setQuery("query");
+        action.doExecute();
+        verify(twitterMonitor, never()).reschedule("query", 1000L);
+    }
+
+    @Test
+    public void testRescheduleAction() {
+
+        final TwitterMonitor twitterMonitor = mock(TwitterMonitor.class);
+
+        SchedulerAction action = new SchedulerAction(twitterMonitor);
+        action.setInterval(1000L);
+        action.setQuery("query");
+        action.doReschedule();
+        verify(twitterMonitor, times(1)).reschedule("query", 1000L);
+    }
+}

File src/test/resources/TEST_RESOURCES_README

View file
+Create any of the test resources you might need in this directory.
+
+Please remove this file before releasing your plugin.

File src/test/xml/TEST_XML_RESOURCES_README

View file
+Create all XML test resources here - these might be needed for populating JIRA instance at the integration-test phase with test data.
+
+Please remove this file before releasing your plugin.