Commits

Anonymous committed 09cbe0a

Initial commit from 1.6.4 final source code.

Comments (0)

Files changed (507)

+Copyright (c) 2009-2012 - 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 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 OWNER 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.
+
+# Atlassian Universal Plugin Manager
+
+This repository contains the final version (1.6.4) of the [Atlassian Universal Plugin Manager][1] (UPM) to be released as open source under a [modified BSD license][2]. Versions 2.0 and later are closed-source projects.
+
+# Why release this code?
+
+We've referred to UPM in several contexts for examples of state-of-the-art plugin design, including:
+
+* RESTful web resource design
+* Building a dynamic UI with JavaScript
+* Advanced OSGi use cases
+* Cross-product support
+* Functional testing
+
+We believe that these examples are still of value and deserve to be more widely emulated.
+
+# May I use this code in my own plugin?
+
+Yes. This version of UPM is licensed under the [BSD license][2], which means you are free to copy, modify, or ship any part of it for your own needs as long as you follow the terms of the license. Feel free to fork this repository or to clone it locally for your own needs. 
+
+# Where can I get support for this code?
+
+This code is not supported by Atlassian or any of its partners or clients. No issues can be filed here, and pull requests will be ignored. If you have issues with the bundled UPM plugin in your Atlassian product, please visit [support.atlassian.com][3].
+
+If you wish to discuss this code with other developers, we encourage you to visit [answers.atlassian.com][4]. 
+
+[1]: http://confluence.atlassian.com/display/UPM/Universal+Plugin+Manager+Documentation
+[2]: http://www.opensource.org/licenses/BSD-3-Clause
+[3]: http://support.atlassian.com
+[4]: http://answers.atlassian.com
+<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/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.atlassian.upm</groupId>
+        <artifactId>atlassian-universal-plugin-manager</artifactId>
+        <version>1.6.4</version>
+    </parent>
+    <name>Universal Plugin Manager - API</name>
+    <artifactId>upm-api</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.atlassian.sal</groupId>
+            <artifactId>sal-api</artifactId>
+        </dependency>
+    </dependencies>
+</project>

api/src/main/java/com/atlassian/upm/api/log/AuditLogEntry.java

+package com.atlassian.upm.api.log;
+
+import java.util.Date;
+
+import com.atlassian.sal.api.message.I18nResolver;
+
+/**
+ * Represents a single entry in the UPM's Audit Log. Entries can represent any kind of plugin event:
+ * plugin installation, plugin uninstallation, plugin enablement, and more.
+ *
+ * @since 1.6
+ */
+public interface AuditLogEntry extends Comparable<AuditLogEntry>
+{
+    /**
+     * Returns the log entry's title.
+     * @param i18nResolver the I18nResolver used to translate the title
+     * @return the log entry's title.
+     */
+    String getTitle(I18nResolver i18nResolver);
+
+    /**
+     * Returns the log entry's message content
+     * @param i18nResolver the I18nResolver used to translate the title
+     * @return the log entry's message content
+     */
+    String getMessage(I18nResolver i18nResolver);
+
+    /**
+     * Returns the date at which this log entry was created
+     * @return the date at which this log entry was created
+     */
+    Date getDate();
+
+    /**
+     * Returns the username of the user that was logged in and created this event
+     * @return the username of the user that was logged in and created this event
+     */
+    String getUsername();
+
+    /**
+     * Returns the i18n key of the event message
+     * @return the i18n key of the event message
+     */
+    String getI18nKey();
+
+    /**
+     * Returns the entry type of the event
+     * @return the entry type of the event
+     * @since 1.6
+     */
+    EntryType getEntryType();
+}

api/src/main/java/com/atlassian/upm/api/log/EntryType.java

+package com.atlassian.upm.api.log;
+
+/**
+ * Represents various types of audit log entries.
+ *
+ * @since 1.6
+ */
+public enum EntryType
+{
+    PLUGIN_INSTALL("upm.auditLog.entryType.pluginInstall", "upm.auditLog.install"),
+    PLUGIN_UNINSTALL("upm.auditLog.entryType.pluginUninstall", "upm.auditLog.uninstall"),
+    PLUGIN_ENABLE("upm.auditLog.entryType.pluginEnable", "upm.auditLog.enable"),
+    PLUGIN_DISABLE("upm.auditLog.entryType.pluginDisable", "upm.auditLog.disable"),
+    ENTER_SAFE_MODE("upm.auditLog.entryType.enterSafeMode", "upm.auditLog.safeMode.enter"),
+    EXIT_SAFE_MODE("upm.auditLog.entryType.exitSafeMode", "upm.auditLog.safeMode.exit"),
+    SYSTEM_STARTUP("upm.auditLog.entryType.systemStartup", "upm.auditLog.system.startup"),
+    CANCELLED_CHANGE("upm.auditLog.entryType.cancelledChange", "upm.auditLog.cancelChange"),
+    UNCLASSIFIED_EVENT("upm.auditLog.entryType.unclassified", null);
+
+    private final String i18nName;
+    private final String i18nPrefix;
+
+    private EntryType(String i18nName, String i18nPrefix)
+    {
+        this.i18nName = i18nName;
+        this.i18nPrefix = i18nPrefix;
+    }
+
+    /**
+     * Returns a i18n key for a displayable name.
+     * @return a i18n key for a displayable name.
+     */
+    public String getI18nName()
+    {
+        return i18nName;
+    }
+
+    /**
+     * Returns the i18n key prefix for which all audit log entries of this type will start with.
+     * @return the i18n key prefix for which all audit log entries of this type will start with.
+     */
+    private String getI18nPrefix()
+    {
+        return i18nPrefix;
+    }
+
+    /**
+     * Returns the {@link EntryType} associated with the given i18n key
+     * @param i18n the i18n
+     * @return the {@link EntryType} associated with the given i18n key
+     */
+    public static EntryType valueOfI18n(String i18n)
+    {
+        for (EntryType type : values())
+        {
+            if (type.getI18nPrefix() != null && i18n.startsWith(type.getI18nPrefix()))
+            {
+                return type;
+            }
+        }
+
+        return UNCLASSIFIED_EVENT;
+    }
+}

api/src/main/java/com/atlassian/upm/api/log/PluginLogService.java

+package com.atlassian.upm.api.log;
+
+import java.util.Set;
+
+/**
+ * Provides a log of all events that change the state of the plugin system. An event is stored as a {@link AuditLogEntry}
+ * which consists of a date, user name, and message. The log can be retrieved as a collection of events.
+ * <p/>
+ * Implementations should be thread safe. Multiple threads may assume they can call the interface methods
+ * safely, assuming no additional atomicity requirements are required between calls. Atomicity may be
+ * achieved by synchronizing on the service object and performing successive operations in that block.
+ *
+ * @since 1.6
+ */
+public interface PluginLogService
+{
+    /**
+     * Returns all plugin log entries.
+     *
+     * @return all plugin log entries.
+     */
+    Iterable<AuditLogEntry> getLogEntries();
+
+    /**
+     * Returns {@code maxResults} number of plugin log entries, starting at {@code startIndex}.
+     *
+     * @param maxResults the maximum number of plugin log entries to return, or "all" if null
+     * @param startIndex the starting index of plugin log entries, or 0 if null
+     * @return  {@code maxResults} number of plugin log entries, starting at {@code startIndex}.
+     */
+    Iterable<AuditLogEntry> getLogEntries(Integer maxResults, Integer startIndex);
+
+    /**
+     * Returns all plugin log entries with the specified {@link EntryType}s.
+     *
+     * @param entryTypes the {@link EntryType}s
+     * @return all plugin log entries with the specified {@link EntryType}s.
+     */
+    Iterable<AuditLogEntry> getLogEntries(Set<EntryType> entryTypes);
+
+    /**
+     * Returns {@code maxResults} number of plugin log entries of the specified types, starting at {@code startIndex}.
+     *
+     * @param maxResults the maximum number of plugin log entries to return, or "all" if null
+     * @param startIndex the starting index of plugin log entries, or 0 if null
+     * @param entryTypes the {@link EntryType}s
+     * @return  {@code maxResults} number of plugin log entries, starting at {@code startIndex}.
+     */
+    Iterable<AuditLogEntry> getLogEntries(Integer maxResults, Integer startIndex, Set<EntryType> entryTypes);
+}

atlassian-ide-plugin.xml

+<atlassian-ide-plugin>
+  <project-configuration>
+    <servers>
+      <bamboo>
+        <server-id>f63b7266-8e2a-4b84-99f9-85603946d744</server-id>
+        <name>BDAC</name>
+        <url>https://bamboo.developer.atlassian.com</url>
+        <shared>false</shared>
+        <use-favourites>false</use-favourites>
+        <bamboo2>true</bamboo2>
+        <plans>
+          <plan key="RPM-TRUNKJDK15" grouped="0" />
+          <plan key="RPM-TRUNKJDK16" grouped="0" />
+        </plans>
+      </bamboo>
+      <crucible>
+        <server-id>67947156-020b-4453-9fd5-59c66d440347</server-id>
+        <name>StAC</name>
+        <url>https://studio.atlassian.com/source</url>
+        <shared>false</shared>
+        <isFisheyeInstance>true</isFisheyeInstance>
+      </crucible>
+      <jira>
+        <server-id>a5c87908-6a62-4c0d-93ef-1dd435f96a81</server-id>
+        <name>StAC</name>
+        <url>https://studio.atlassian.com</url>
+        <shared>false</shared>
+      </jira>
+    </servers>
+    <default-crucible-server>67947156-020b-4453-9fd5-59c66d440347</default-crucible-server>
+    <default-fisheye-server>67947156-020b-4453-9fd5-59c66d440347</default-fisheye-server>
+    <default-jira-server>a5c87908-6a62-4c0d-93ef-1dd435f96a81</default-jira-server>
+  </project-configuration>
+</atlassian-ide-plugin>

fake-pac-plugin/pom.xml

+<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/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <artifactId>atlassian-universal-plugin-manager</artifactId>
+        <groupId>com.atlassian.upm</groupId>
+        <version>1.6.4</version>
+    </parent>
+    <artifactId>fake-pac-plugin</artifactId>
+    <packaging>atlassian-plugin</packaging>
+    <name>Universal Plugin Manager - Fake-PAC Plugin</name>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.atlassian.maven.plugins</groupId>
+                <artifactId>maven-amps-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Import-Package>
+                            javax.ws.rs*;version="1.0",
+                            com.atlassian.plugins.rest.common.*;version="1.0.5",*
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-dependencies</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.outputDirectory}</outputDirectory>
+                            <!-- The IDs must be on a single line: any IDs specified after a line break won't be copied. -->
+                            <includeArtifactIds>atlassian-universal-plugin-manager-plugin,atlassian-universal-plugin-manager-test-plugin-v2-installable,atlassian-universal-plugin-manager-test-plugin-v2-installable-with-config,atlassian-universal-plugin-manager-test-plugin-v2-installable-requires-restart,atlassian-universal-plugin-manager-test-plugin-v2-obr,atlassian-universal-plugin-manager-test-plugin-v2-bundled-upgradable-latest-update,atlassian-universal-plugin-manager-test-plugin-v2-latest-update,atlassian-universal-plugin-manager-test-plugin-v2-broken-upgrade-1.0,atlassian-universal-plugin-manager-test-plugin-v2-broken-upgrade-1.1,atlassian-universal-plugin-manager-test-plugin-v2-upgradeable-requires-restart-1.0,atlassian-universal-plugin-manager-test-plugin-v2-upgradeable-requires-restart-2.0,atlassian-universal-plugin-manager-test-plugin-v2-user-installed-with-modules,atlassian-universal-plugin-manager-test-plugin-v2-unrecognised-module-type,atlassian-universal-plugin-manager-test-plugin-v2-cannot-disable-module,atlassian-universal-plugin-manager-test-plugin-v2-not-enabled-by-default,atlassian-universal-plugin-manager-test-plugin-v1-classpath,atlassian-universal-plugin-manager-test-plugin-v1-installable,atlassian-universal-plugin-manager-test-plugin-v1-installable-upgrade,atlassian-universal-plugin-manager-test-plugin-v2-user-installed-flagged-as-system-required,atlassian-universal-plugin-manager-test-plugin-v99-unsupported,atlassian-universal-plugin-manager-test-osgi-bundle</includeArtifactIds>
+                            <excludeTransitive>true</excludeTransitive>
+                            <stripVersion>true</stripVersion>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>jsr311-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>1.4</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.4</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.atlassian.plugins.rest</groupId>
+            <artifactId>atlassian-rest-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-plugin</artifactId>
+            <version>${project.version}</version>
+            <type>atlassian-plugin</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-installable</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-installable-requires-restart</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-obr</artifactId>
+            <version>${project.version}</version>
+            <type>obr</type>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-installable-with-config</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-bundled-upgradable-latest-update</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-latest-update</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-broken-upgrade-1.0</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-broken-upgrade-1.1</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-upgradeable-requires-restart-1.0</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-upgradeable-requires-restart-2.0</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-user-installed-with-modules</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-user-installed-flagged-as-system-required</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-unrecognised-module-type</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-cannot-disable-module</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v2-not-enabled-by-default</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v1-classpath</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v1-installable</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v1-installable-upgrade</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-plugin-v99-unsupported</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>atlassian-universal-plugin-manager-test-osgi-bundle</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>

fake-pac-plugin/src/main/java/com/atlassian/upm/fakepac/FakePacServices.java

+package com.atlassian.upm.fakepac;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.StreamingOutput;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import com.atlassian.plugins.rest.common.Status;
+import com.atlassian.plugins.rest.common.security.AnonymousAllowed;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.output.NullOutputStream;
+import org.apache.commons.lang.math.NumberUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED;
+import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM;
+import static javax.ws.rs.core.MediaType.APPLICATION_XML;
+import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
+import static javax.ws.rs.core.Response.Status.NOT_FOUND;
+import static org.apache.commons.io.IOUtils.closeQuietly;
+import static org.apache.commons.io.IOUtils.copy;
+import static org.apache.commons.io.IOUtils.copyLarge;
+import static org.apache.commons.lang.StringUtils.isEmpty;
+
+/**
+ * Serves up static files to imitate PAC.
+ */
+@Path("/")
+@AnonymousAllowed
+public class FakePacServices
+{
+    private static final String BASE_URL = System.getProperty("pac.baseurl") + "/1.0";
+    private final Map<String, Long> pluginFileSizes = new HashMap<String, Long>();
+
+    @GET
+    @Path("pluginversion/find/compatible/{product-key}/{buildNumber}")
+    @Produces(APPLICATION_XML)
+    public String findPluginVersion(@QueryParam("expand") List<String> expand,
+        @QueryParam("q") String query,
+        @DefaultValue("0") @QueryParam("max-results") int maxResults,
+        @DefaultValue("0") @QueryParam("start-index") int startIndex) throws Exception
+    {
+        checkContains(expand, "plugin");
+        return filterContents("/find-plugin-version.xml", query, maxResults, startIndex);
+    }
+
+    @GET
+    @Path("pluginversion/find/popular/{product-key}/{build-number}")
+    @Produces(APPLICATION_XML)
+    public String getPopularPlugins(@QueryParam("expand") List<String> expand,
+        @DefaultValue("0") @QueryParam("max-results") int maxResults,
+        @DefaultValue("0") @QueryParam("start-index") int startIndex) throws Exception
+    {
+        checkContains(expand, "plugin");
+        return filterContents("/find-plugin-version.xml", maxResults, startIndex);
+    }
+
+    @GET
+    @Path("pluginversion/find/supported/{product-key}/{build-number}")
+    @Produces(APPLICATION_XML)
+    public String getSupportedPlugins(@QueryParam("expand") List<String> expand,
+        @DefaultValue("0") @QueryParam("max-results") int maxResults,
+        @DefaultValue("0") @QueryParam("start-index") int startIndex) throws Exception
+    {
+        checkContains(expand, "plugin");
+        return filterContents("/find-plugin-version.xml", maxResults, startIndex);
+    }
+
+    @GET
+    @Path("pluginversion/find/featured/{product-key}/{buildNumber}")
+    @Produces(APPLICATION_XML)
+    public String findFeatured(@QueryParam("expand") List<String> expand,
+        @DefaultValue("0") @QueryParam("max-results") int maxResults,
+        @DefaultValue("0") @QueryParam("start-index") int startIndex) throws Exception
+    {
+        checkContains(expand, "plugin");
+        return filterContents("/find-featured.xml", maxResults, startIndex);
+    }
+
+    @GET
+    @Path("pluginversion/find/compatiblekey/{product-key}/{build-number}/{plugin-key}")
+    @Produces(APPLICATION_XML)
+    public String findCompatibleKey(@QueryParam("expand") List<String> expand,
+        @PathParam("plugin-key") String pluginKey) throws Exception
+    {
+        checkContains(expand, "plugin.icon", "plugin.vendor", "license", "reviewSummary");
+        if ("trigger-500".equals(pluginKey))
+        {
+            throw new WebApplicationException(Response.status(500).entity(Status.error().message("Triggered an internal error because you asked for it.").build()).build());
+        }
+        return filterContents("/find-compatible-" + pluginKey + ".xml");
+    }
+
+    @POST
+    @Path("pluginversion/find/updates/{product-key}/{buildNumber}")
+    @Consumes(APPLICATION_FORM_URLENCODED)
+    @Produces(APPLICATION_XML)
+    public String findUpdates(@QueryParam("expand") List<String> expand) throws Exception
+    {
+        checkContains(expand, "plugin");
+        return filterContents("/find-updates.xml");
+    }
+
+    @GET
+    @Path("product/after/{product-key}/{buildNumber}")
+    @Produces(APPLICATION_XML)
+    public String getProductUpgrades() throws Exception
+    {
+        return filterContents("/product-upgrades.xml");
+    }
+
+    @GET
+    @Path("product/latest/{product-key}")
+    @Produces(APPLICATION_XML)
+    public String getProductLatest(@PathParam("product-key") String productKey) throws Exception
+    {
+        return getContents("/product-versions-" + productKey + ".xml");
+    }
+
+    @POST
+    @Path("pluginversion/find/compatibilitystatus/{product-key}/{currentBuildNumber}/{targetBuildNumber}")
+    @Produces(APPLICATION_XML)
+    public String getProductUpgradePluginCompatibility(@FormParam("expand") List<String> expand) throws Exception
+    {
+        checkContains(expand, "plugin", "productCompatibilities.productCompatibility.maxVersion", "productCompatibilities.productCompatibility.minVersion", "productCompatibilities.productCompatibility.product");
+        return filterContents("/product-upgrade-plugin-compatibility.xml");
+    }
+
+    @GET
+    @Path("plugins/{plugin}")
+    @Produces(APPLICATION_OCTET_STREAM)
+    public Response servePlugin(@PathParam("plugin") String plugin) throws IOException
+    {
+        return Response.ok(newStreamingOutput("/" + plugin)).header(HttpHeaders.CONTENT_LENGTH, sizeOf("/" + plugin)).build();
+    }
+
+    @GET
+    @Path("xmlplugins/{plugin}")
+    @Produces(APPLICATION_XML)
+    public String serveXmlPlugin(@PathParam("plugin") String xmlPlugin) throws IOException
+    {
+        if (xmlPlugin != null && !xmlPlugin.endsWith(".xml"))
+        {
+            xmlPlugin = xmlPlugin.concat(".xml");
+        }
+        return getContents("/" + xmlPlugin);
+    }
+
+    @GET
+    @Path("nonplugin")
+    @Produces(APPLICATION_OCTET_STREAM)
+    public Response serveNonPlugin() throws IOException
+    {
+        return Response.ok(new ByteArrayInputStream("This is not a plugin.".getBytes())).build();
+    }
+
+    private void checkContains(Collection<String> actual, String... expected)
+    {
+        List<String> missing = new ArrayList<String>();
+        for (String e : expected)
+        {
+            if (!actual.contains(e))
+            {
+                missing.add(e);
+            }
+        }
+        if (!missing.isEmpty())
+        {
+            throw new WebApplicationException(Response.
+                status(BAD_REQUEST).
+                entity("For PAC integration to work the following expand parameters are expected but missing: " + missing).
+                build());
+        }
+    }
+
+    private Long sizeOf(String plugin) throws IOException
+    {
+        if (!pluginFileSizes.containsKey(plugin))
+        {
+            pluginFileSizes.put(plugin, calculateFileSize(plugin));
+        }
+        return pluginFileSizes.get(plugin);
+    }
+
+    private Long calculateFileSize(String plugin) throws IOException
+    {
+        InputStream in = null;
+        try
+        {
+            in = getClass().getResourceAsStream("/" + plugin);
+            return copyLarge(in, new NullOutputStream());
+        }
+        finally
+        {
+            closeQuietly(in);
+        }
+    }
+
+    private StreamingOutput newStreamingOutput(final String fileName)
+    {
+        return new StreamingOutput()
+        {
+            public void write(OutputStream output) throws IOException, WebApplicationException
+            {
+                InputStream input = getClass().getResourceAsStream(fileName);
+                if (input == null)
+                {
+                    throw new WebApplicationException(NOT_FOUND);
+                }
+                try
+                {
+                    copy(input, output);
+                }
+                finally
+                {
+                    closeQuietly(input);
+                }
+            }
+        };
+    }
+
+    private String filterContents(String fileName) throws Exception
+    {
+        return filterContents(fileName, 0, 0);
+    }
+
+    private String filterContents(String fileName, int maxResults, int startIndex) throws Exception
+    {
+        return filterContents(fileName, null, maxResults, startIndex);
+    }
+
+    private String filterContents(String fileName, String query, int maxResults, int startIndex) throws Exception
+    {
+        String fileContents = getContents(fileName);
+
+        if ((isEmpty(query) || "alias:Plugin".equals(query)) && maxResults == 0 && startIndex == 0)
+        {
+            return fileContents;
+        }
+
+        Document doc = parseXml(fileContents);
+
+        filterSearchQuery(doc, query);
+        filterPagination(doc, maxResults, startIndex);
+
+        return getXmlString(doc);
+    }
+
+    private String getContents(String fileName) throws IOException
+    {
+        InputStream input = getClass().getResourceAsStream(fileName);
+        if (input == null)
+        {
+            throw new WebApplicationException(NOT_FOUND);
+        }
+        try
+        {
+            String contents = IOUtils.toString(input);
+            return contents.replaceAll("\\$\\{fakepac\\.baseurl\\}", BASE_URL);
+        }
+        finally
+        {
+            closeQuietly(input);
+        }
+    }
+
+    private void filterSearchQuery(Document doc, String query) throws Exception
+    {
+        if (isEmpty(query) || "alias:Plugin".equals(query))
+        {
+            return;
+        }
+
+        Element items = (Element) doc.getFirstChild();
+        int size = NumberUtils.createNumber(items.getAttribute("size")).intValue();
+        int remaining = size;
+
+        for (int i = size - 1; i > -1; i--)
+        {
+            Element item = (Element) items.getElementsByTagName("item").item(i);
+            String title = item.getElementsByTagName("name").item(0).getTextContent();
+
+            if (!title.toLowerCase().matches(".*" + query.toLowerCase() + ".*"))
+            {
+                items.removeChild(item);
+                remaining--;
+            }
+        }
+
+        // set the size attribute of the items tag
+        items.setAttribute("size", String.valueOf(remaining));
+    }
+
+    private void filterPagination(Document doc, int maxResults, int startIndex) throws Exception
+    {
+        if (maxResults == 0 && startIndex == 0)
+        {
+            return;
+        }
+
+        Element items = (Element) doc.getFirstChild();
+        int size = NumberUtils.createNumber(items.getAttribute("size")).intValue();
+        int remaining = size;
+
+        if (startIndex > 0)
+        {
+            for (int i = Math.min(startIndex, size) - 1; i > -1; i--)
+            {
+                Node item = items.getElementsByTagName("item").item(i);
+                items.removeChild(item);
+            }
+            remaining = size - startIndex;
+        }
+
+        if (maxResults > 0 && remaining > maxResults)
+        {
+            for (int i = remaining - 1; i >= maxResults; i--)
+            {
+                Node item = items.getElementsByTagName("item").item(i);
+                items.removeChild(item);
+            }
+            remaining = maxResults;
+        }
+
+        // set the size attribute of the items tag
+        items.setAttribute("size", String.valueOf(remaining));
+    }
+
+    private Document parseXml(String xmlContent) throws Exception
+    {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        return factory.newDocumentBuilder().parse(new InputSource(new StringReader(xmlContent)));
+    }
+
+    private String getXmlString(Document doc) throws TransformerException
+    {
+        StreamResult xmlOutput = new StreamResult(new StringWriter());
+        TransformerFactory tfactory = TransformerFactory.newInstance();
+        Transformer serializer = tfactory.newTransformer();
+        serializer.transform(new DOMSource(doc), xmlOutput);
+
+        return xmlOutput.getWriter().toString();
+    }
+}

fake-pac-plugin/src/main/java/com/atlassian/upm/fakepac/WebApplicationExceptionMapper.java

+package com.atlassian.upm.fakepac;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+@Provider
+public class WebApplicationExceptionMapper implements ExceptionMapper<WebApplicationException>
+{
+    public Response toResponse(WebApplicationException exception)
+    {
+        Response r = exception.getResponse();
+        if (r.getStatus() >= 500 && r.getEntity() == null)
+        {
+            // Write out the exception to a string
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            exception.printStackTrace(pw);
+            pw.flush();
+
+            r = Response.status(r.getStatus()).entity(sw.toString()).
+                type("text/plain").build();
+        }
+        return r;
+    }
+}

fake-pac-plugin/src/main/resources/atlassian-plugin.xml

+<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>
+
+    <rest key="rest" path="/fakepac" version="1.0" />
+</atlassian-plugin>

fake-pac-plugin/src/main/resources/find-compatible-com.atlassian.upm.atlassian-universal-plugin-manager-plugin.xml

+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<items size="1">
+    <item xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:type="pluginVersion"
+          expand="plugin,state,releaseFocus,license,reviewSummary,productCompatibilities,screenshots,categories,contributors,requirements"
+          id="666">
+        <link rel="self"
+              href="https://plugins.atlassian.com/server/1.0/pluginversion/666" />
+        <lastModified>2010-02-20T04:33:41.989-06:00</lastModified>
+        <plugin expand="icon,vendor,latestVersion,reviewSummary,versions"
+                id="9601">
+            <link rel="self"
+                  href="https://plugins.atlassian.com/server/1.0/plugin/9601" />
+            <lastModified>2010-03-05T10:06:12.403-06:00</lastModified>
+            <name>Installable Test OBR Plugin</name>
+            <pluginKey>com.atlassian.upm.atlassian-universal-plugin-manager-test-plugin-v2-obr</pluginKey>
+            <projectKey>AWSE</projectKey>
+            <status>PUBLISHED</status>
+            <deployable>false</deployable>
+            <icon expand="plugin" id="9618">
+                <link rel="self"
+                      href="https://plugins.atlassian.com/server/1.0/pluginicon/9618" />
+                <lastModified>2010-01-18T09:41:01.432-06:00
+                </lastModified>
+                <location>https://plugins.atlassian.com/server/1.0/pluginicon/fetch/9618</location>
+                <width>270</width>
+                <height>90</height>
+                <imageType>png</imageType>
+                <plugin
+                        expand="icon,vendor,latestVersion,reviewSummary,versions"
+                        id="9601">
+                    <link rel="self"
+                          href="https://plugins.atlassian.com/server/1.0/plugin/9601" />
+                </plugin>
+            </icon>
+            <vendor expand="supportOrganisations,vendorRelationships,plugins"
+                    id="7755">
+                <link rel="self"
+                      href="https://plugins.atlassian.com/server/1.0/vendor/7755" />
+            </vendor>
+            <latestVersion
+                    expand="plugin,state,releaseFocus,license,reviewSummary,productCompatibilities,screenshots,categories,contributors,requirements"
+                    id="19126">
+                <link rel="self"
+                      href="https://plugins.atlassian.com/server/1.0/pluginversion/19126" />
+                <pluginSystemVersion>TWO</pluginSystemVersion>
+            </latestVersion>
+            <reviewSummary expand="reviewSummary" />
+            <versions size="2" expand="version" />
+            <historicalKeys />
+        </plugin>
+        <status>PUBLISHED</status>
+        <supportType>VENDOR</supportType>
+        <version>1.2</version>
+        <buildNumber>6</buildNumber>
+        <pluginSystemVersion>TWO</pluginSystemVersion>
+        <summary>Installable plugin in OBR format for testing UPM.</summary>
+        <description>Doesn't really do anything</description>
+        <jiraUrl>https://example.com/browse/AWSE
+        </jiraUrl>
+        <confluenceUrl></confluenceUrl>
+        <fisheyeUrl></fisheyeUrl>
+        <bambooUrl></bambooUrl>
+        <crucibleUrl></crucibleUrl>
+        <forumsUrl></forumsUrl>
+        <javaDocsUrl></javaDocsUrl>
+        <documentationUrl>https://example.com/wiki/display/AWSE
+        </documentationUrl>
+        <generateEvaluationLicenseUrl></generateEvaluationLicenseUrl>
+        <releaseDate>2010-02-20T00:00:00-06:00</releaseDate>
+        <releaseSummary>Bug fixes for 1.1
+        </releaseSummary>
+        <releaseNotes>Now more zesty than ever!
+        </releaseNotes>
+        <releaseNotesUrl>https://example.com/wiki/display/AWSE
+        </releaseNotesUrl>
+        <state id="4">
+            <link rel="self"
+                  href="https://plugins.atlassian.com/server/1.0/category/4" />
+        </state>
+        <releaseFocus id="11">
+            <link rel="self"
+                  href="https://plugins.atlassian.com/server/1.0/category/11" />
+        </releaseFocus>
+        <license id="17">
+            <link rel="self"
+                  href="https://plugins.atlassian.com/server/1.0/category/17" />
+            <lastModified>2010-02-09T19:33:20.318-06:00</lastModified>
+            <name>Commercial</name>
+            <type>LICENSETYPE</type>
+            <active>true</active>
+            <sortOrder>1</sortOrder>
+            <key>license.commercial</key>
+            <value></value>
+        </license>
+        <reviewSummary>
+            <total>0</total>
+        </reviewSummary>
+        <licenseUrl></licenseUrl>
+        <binaryUrl>${fakepac.baseurl}/plugins/atlassian-universal-plugin-manager-test-plugin-v2-obr.obr
+        </binaryUrl>
+        <sourceUrl></sourceUrl>
+        <donateUrl></donateUrl>
+        <purchaseUrl>http://example.com/purchase</purchaseUrl>
+        <learnMoreUrl>http://example.com/wiki/display/AWSE
+        </learnMoreUrl>
+        <productCompatibilities size="1"
+                                expand="productCompatibility" />
+        <screenshots size="2" expand="screenshot" />
+        <categories size="3" expand="category" />
+        <contributors size="2" expand="contributor" />
+        <requirements size="0" expand="requirement" />
+    </item>
+    <item xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pluginVersion"
+          expand="plugin,state,releaseFocus,license,reviewSummary,productCompatibilities,screenshots,categories,contributors,requirements"
+          id="335281">
+	    <link rel="self" href="http://plugins.atlassian.com/server/1.0/pluginversion/335281"/>
+	    <lastModified>2011-08-01T20:39:27.219-05:00</lastModified>
+	    <plugin expand="icon,tinyicon,banner,vendor,latestVersion,reviewSummary,versions" id="23915">
+	        <link rel="self" href="http://plugins.atlassian.com/server/1.0/plugin/23915"/>
+	        <lastModified>2011-08-01T20:39:27.219-05:00</lastModified>
+	        <name>Atlassian Universal Plugin Manager Plugin</name>
+	        <pluginKey>com.atlassian.upm.atlassian-universal-plugin-manager-plugin</pluginKey>
+	        <projectKey>UPM</projectKey>
+	        <status>PUBLISHED</status>
+	        <deployable>true</deployable>
+	        <icon expand="plugin" id="23931">
+	            <link rel="self" href="http://plugins.atlassian.com/server/1.0/pluginicon/23931"/>
+	        </icon>
+	        <vendor expand="supportOrganisations,vendorRelationships,plugins" id="85">
+	            <link rel="self" href="http://plugins.atlassian.com/server/1.0/vendor/85"/>
+	        </vendor>
+	        <latestVersion expand="plugin,state,releaseFocus,license,reviewSummary,productCompatibilities,screenshots,categories,contributors,requirements" id="335281">
+	            <link rel="self" href="http://plugins.atlassian.com/server/1.0/pluginversion/335281"/>
+	        </latestVersion>
+	        <reviewSummary expand="reviewSummary"/>
+	        <versions size="15" expand="version"/>
+	        <creationDate>2010-06-09T00:00:00-05:00</creationDate>
+	        <historicalKeys/>
+	    </plugin>
+        <status>PUBLISHED</status>
+        <supportType>VENDOR</supportType>
+        <version>1.6-SNAPSHOT</version>
+        <buildNumber>124290</buildNumber>
+        <pluginSystemVersion>TWO</pluginSystemVersion>
+        <summary>The UPM allows you to see and manage the plugins that you have installed
+in your application, and it allows you to discover, download and install
+new plugins from the Atlassian Plugin Exchange.</summary>
+        <description>&lt;p&gt;The UPM allows you to see and manage the plugins that you have
+  installed in your application, and it allows you to discover, download
+  and install new plugins from the Atlassian Plugin Exchange.&lt;/p&gt;
+&lt;p&gt;&amp;nbsp;&lt;/p&gt;
+&lt;p&gt;
+  &lt;strong&gt;If you are installing the Universal Plugin Manager for   JIRA
+    4.2 please ensure you use the    &lt;a
+      href=&quot;../../plugin/details/23915?versionId=34567&quot;&gt;latest
+      compatible version - 1.1.4&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;</description>
+        <jiraUrl>http://studio.atlassian.com/browse/UPM/</jiraUrl>
+        <confluenceUrl>http://studio.atlassian.com/wiki/display/UPM/</confluenceUrl>
+        <fisheyeUrl>https://studio.atlassian.com/source/browse/UPM/</fisheyeUrl>
+        <bambooUrl>http://studio.atlassian.com/builds/browse/UPM/</bambooUrl>
+        <crucibleUrl>http://studio.atlassian.com/reviews/browse/UPM/</crucibleUrl>
+        <forumsUrl></forumsUrl>
+        <javaDocsUrl></javaDocsUrl>
+        <documentationUrl>http://confluence.atlassian.com/display/UPM/</documentationUrl>
+        <generateEvaluationLicenseUrl></generateEvaluationLicenseUrl>
+        <eulaUrl></eulaUrl>
+        <releaseDate>2011-06-13T00:00:00-05:00</releaseDate>
+        <releaseSummary>&amp;quot;offline&amp;quot; mode for when the plugin exchange server is
+unreachable; various bug fixes</releaseSummary>
+        <releaseNotesUrl>http://confluence.atlassian.com/display/UPM/Universal+Plugin+Manager+1.6+Release+Notes</releaseNotesUrl>
+        <youtubeId></youtubeId>
+        <state id="4">
+            <link rel="self" href="http://plugins.atlassian.com/server/1.0/category/4"/>
+            <beta>false</beta>
+        </state>
+        <releaseFocus id="11">
+            <link rel="self" href="http://plugins.atlassian.com/server/1.0/category/11"/>
+            <beta>false</beta>
+        </releaseFocus>
+        <license id="18">
+            <link rel="self" href="http://plugins.atlassian.com/server/1.0/category/18"/>
+            <beta>false</beta>
+        </license>
+        <reviewSummary expand="reviewSummary"/>
+        <licenseUrl>http://www.apache.org/licenses/LICENSE-2.0.html</licenseUrl>
+        <binaryUrl>${fakepac.baseurl}/plugins/atlassian-universal-plugin-manager-plugin.jar</binaryUrl>
+        <sourceUrl>https://studio.atlassian.com/svn/UPM/tags/atlassian-universal-plugin-manager-1.6-SNAPSHOT/</sourceUrl>
+        <donateUrl></donateUrl>
+        <purchaseUrl></purchaseUrl>
+        <creationDate>2011-06-13T19:52:13.370-05:00</creationDate>
+        <productCompatibilities size="5" expand="productCompatibility"/>
+        <screenshots size="6" expand="screenshot"/>
+        <categories size="6" expand="category"/>
+        <contributors size="0" expand="contributor"/>
+        <requirements size="0" expand="requirement"/>
+    </item>
+</items>

fake-pac-plugin/src/main/resources/find-compatible-com.atlassian.upm.atlassian-universal-plugin-manager-test-plugin-v2-obr.xml

+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<items size="1">
+    <item xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:type="pluginVersion"
+          expand="plugin,state,releaseFocus,license,reviewSummary,productCompatibilities,screenshots,categories,contributors,requirements"
+          id="666">
+        <link rel="self"
+              href="https://plugins.atlassian.com/server/1.0/pluginversion/666" />
+        <lastModified>2010-02-20T04:33:41.989-06:00</lastModified>
+        <plugin expand="icon,vendor,latestVersion,reviewSummary,versions"
+                id="9601">
+            <link rel="self"
+                  href="https://plugins.atlassian.com/server/1.0/plugin/9601" />
+            <lastModified>2010-03-05T10:06:12.403-06:00</lastModified>
+            <name>Installable Test OBR Plugin</name>
+            <pluginKey>com.atlassian.upm.atlassian-universal-plugin-manager-test-plugin-v2-obr</pluginKey>
+            <projectKey>AWSE</projectKey>
+            <status>PUBLISHED</status>
+            <deployable>true</deployable>