You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by fa...@apache.org on 2013/04/08 17:19:09 UTC

svn commit: r1465662 [23/26] - in /qpid/trunk/qpid/tools/src/java: ./ bin/ bin/qpid-web/ bin/qpid-web/authentication/ bin/qpid-web/web/ bin/qpid-web/web/itablet/ bin/qpid-web/web/itablet/css/ bin/qpid-web/web/itablet/images/ bin/qpid-web/web/itablet/im...

Added: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/QpidServer.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/QpidServer.java?rev=1465662&view=auto
==============================================================================
--- qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/QpidServer.java (added)
+++ qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/QpidServer.java Mon Apr  8 15:19:04 2013
@@ -0,0 +1,654 @@
+/*
+ *
+ * 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.apache.qpid.restapi;
+
+// Simple Logging Facade 4 Java
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// Misc Imports
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.ObjectId;
+import org.apache.qpid.qmf2.common.QmfData;
+import org.apache.qpid.qmf2.common.QmfException;
+import org.apache.qpid.qmf2.console.Agent;
+import org.apache.qpid.qmf2.console.Console;
+import org.apache.qpid.qmf2.console.MethodResult;
+import org.apache.qpid.qmf2.console.QmfConsoleData;
+
+import static java.net.HttpURLConnection.HTTP_OK;
+import static java.net.HttpURLConnection.HTTP_BAD_METHOD;
+import static java.net.HttpURLConnection.HTTP_CREATED;
+import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
+import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
+import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
+
+
+/**
+ * This class implements the REST API proper, it is an implementation of the Server interface which allows us to
+ * abstract the "business logic" from the Web Server implementation technology (HttpServer/Servlet etc.).
+ * <p>
+ * The REST API is as follows:
+ * <pre>
+ * PUT: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;
+ *      HTTP body: {"url":&lt;url&gt;,"connectionOptions":&lt;connectionOptions&gt;[,"disableEvents":true;]}
+ *      &lt;url&gt;: A string containing an AMQP connection URL as used in the qpid::messaging API.
+ *      &lt;connectionOptions&gt;: A JSON string containing connectionOptions in the form specified in the 
+ *      qpid::messaging API.
+ *
+ *      This method creates a Qpid Connection Object with the name &lt;name&gt; using the specified url and options.
+ *
+ *      The optional disableEvents property is used to start up a QMF Connection which can only
+ *      do synchronous calls such as getObjects() and can't receive Agent updates or QMF2 Events.
+ *
+ * DELETE: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;
+ *
+ *      This method deletes the Qpid Connection Object with the name &lt;name&gt;.
+ *
+ * POST: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/object/&lt;ObjectId&gt;
+ *      HTTP body: {"_method_name":&lt;method&gt;,"_arguments":&lt;inArgs&gt;}
+ *      &lt;method&gt;: A string containing the QMF2 method name e.g. "getLogLevel", "setLogLevel", "create", "delete".
+ *      &lt;inArgs&gt;: A JSON string containing the method arguments e.g. {"level":"debug+:Broker"} for setLogLevel.
+ *      HTTP response: A JSON string containing the response e.g. {"level":"notice+"} for getLogLevel (may be empty).
+ *
+ *      This method invokes the QMF2 method &lt;method&gt; with arguments &lt;inArgs&gt; on the object &lt;ObjectId&gt;
+ *
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection;
+ * 
+ *      This method retrieves (as a JSON string) the complete set of connections currently enabled on the Server.
+ *      The returned JSON string represents a Map keyed by the connection name/handle. The value part is itself a
+ *      Map containing the Connection URL and Connection Options used to create the Qpid Connection. e.g.
+ *      {"8c5116d6-46a1-489b-93d8-fde525e0d76d":{"url":"0.0.0.0:5672","connectionOptions":{}}}
+ *
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;
+ * 
+ *      This method retrieves (as a JSON string) a Map containing the Connection URL and Connection Options used to    
+ *      create the Qpid Connection with the specified &lt;name&gt;. e.g.
+ *      {"url":"0.0.0.0:5672","connectionOptions":{}}
+ *
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/console/objects/&lt;className&gt;
+ * 
+ *      This method retrieves (as a JSON string) the list of QmfConsoleData objects with the specified &lt;className&gt; 
+ *      using the QMF2 Console associated with the Qpid Connection Object with the name &lt;name&gt;.
+ *      This is the REST equivalent of Console.getObjects(className) which searches across all packages and all Agents 
+ *      for the specified className.
+ *
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/console/objects/&lt;packageName&gt;/&lt;className&gt;
+ *      !!not yet implemented!!
+ *      This method retrieves (as a JSON string) the list of QmfConsoleData objects with the specified 
+ *      &lt;packageName&gt; and &lt;className&gt; using the QMF2 Console associated with the Qpid Connection Object
+ *      with the name &lt;name&gt;.
+ *      This is the REST equivalent of Console.getObjects(packageName, className) which searches across all Agents 
+ *      for the specified className in the package packageName.
+ *
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/object/&lt;ObjectId&gt;
+ *      This method retrieves (as a JSON string) the QmfConsoleData object with the specified &lt;ObjectId&gt;
+ *      using the QMF2 Console associated with the Qpid Connection Object with the name &lt;name&gt;.
+ *      This is the REST implementation of getObjects(oid) it is also the equivalent of the QmfConsoleData refresh() 
+ *      method where an object can update its state.
+ *      Note that there's a slight variance on the QMF2 API here as that returns an array/list of QmfConsoledData
+ *      objects for all queries, however as the call with ObjectId will only return one or zero objects this 
+ *      implementation returns the single QmfConsoleData object found or a 404 Not Found response.
+ *
+ *      N.B. that the ManagementAgent on the broker appears not to set the timestamp properties in the response to this
+ *      call, which means that they get set to current time in the QmfConsoleData, this is OK for _update_ts but not
+ *      for _create_ts and _delete_ts. Users of this call should be aware of that in their own code.
+ *
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/console/objects
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/console/classes (synonyms)
+ * 
+ *      This method retrieves (as a JSON string) the list of SchemaClassId for all available Schema for all Agents.
+ *      This is the REST equivalent of Console.getClasses() which searches across all Agents.
+ *
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/console/classes/&lt;agentName&gt;
+ * 
+ *      This method retrieves (as a JSON string) the list of SchemaClassId for all available Schema on the
+ *      Agent named &lt;agentName&gt;.
+ *      This is the REST equivalent of Console.getClasses(agent) which searches across a specified Agent.
+ *
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/console/packages
+ * 
+ *      This method retrieves (as a JSON string) the list of all known Packages for all Agents.
+ *      This is the REST equivalent of Console.getPackages() which searches across all Agents.
+ *
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/console/packages/&lt;agentName&gt;
+ * 
+ *      This method retrieves (as a JSON string) the list of all known Packages on the Agent named &lt;agentName&gt;.
+ *      This is the REST equivalent of Console.getPackages(agent) which searches across a specified Agent.
+ *
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/console/agents
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/console/agent (synonyms)
+ * 
+ *      This method retrieves (as a JSON string) the list of all known Agents.
+ *      This is the REST equivalent of Console.getAgents().
+ *
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/console/agent/&lt;agentName&gt;
+ * 
+ *      This method retrieves (as a JSON string) the Agent named &lt;agentName&gt;.
+ *      This is the REST equivalent of Console.getAgent(agentName).
+ *
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/console/address
+ * 
+ *      This method retrieves (as a JSON string) the AMQP address this Console is listening to.
+ *      This is the REST equivalent of Console.getAddress().
+ *
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/console/workItemCount
+ * 
+ *      This method retrieves (as a plain text string) the count of pending WorkItems that can be retrieved
+ *      from this Console.
+ *      This is the REST equivalent of Console.getWorkItemCount().
+ *
+ * GET: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/console/nextWorkItem
+ * 
+ *      This method retrieves (as a JSON string) the next pending work item from this Console (N.B. this method
+ *      blocks until a WorkItem is available so should only be called asynchronously e.g. via AJAX).
+ *      This is the REST equivalent of Console.getNextWorkitem().
+ * </pre>
+ * @author Fraser Adams
+ */
+public final class QpidServer implements Server
+{
+    private static final Logger _log = LoggerFactory.getLogger(QpidServer.class);
+
+    private ConnectionStore _connections = new ConnectionStore();
+    private String _defaultBroker = null;
+
+    public QpidServer(final String broker)
+    {
+        _defaultBroker = broker;
+    }
+
+    /**
+     * Handle a "/qpid/connection/<connectionName>/console/objects" or 
+     * "/qpid/connection/<connectionName>/console/objects/" request,
+     * in other words a request for information about an object resource specified by the remaining path.
+     * Only the GET method is valid for this resource and it is in effect the REST mapping for Console.getObjects().
+     */
+    private void sendGetObjectsResponse(final HttpTransaction tx, final Console console, final String path) throws IOException
+    {
+        String[] params = path.split("/");
+        if (params.length == 1)
+        { // With one parameter we call getObjects(className)
+            tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getObjects(params[0])));
+        }
+        else if (params.length == 2)
+        { // With two parameters we call getObjects(packageName, className)
+            //System.out.println("params = " + params[0] + ", " + params[1]);
+            tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getObjects(params[0], params[1])));
+        }
+        else if (params.length == 3)
+        { // TODO With three parameters we call getObjects(packageName, className, agent)
+            //System.out.println("params = " + params[0] + ", " + params[1] + ", " + params[2]);
+            tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Too many parameters for objects GET request.");
+        } else {
+            tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Too many parameters for objects GET request.");
+        }
+    }
+
+    /**
+     * Called by the Web Server to allow a Server to handle a GET request.
+     * The HTTP GET URL structure for the REST API is specified above in the overall class documentation.
+     *
+     * @param tx the HttpTransaction containing the request from the client and used to send the response.
+     */
+    public void doGet(final HttpTransaction tx) throws IOException
+    {
+        String path = tx.getRequestURI();
+
+        //System.out.println();
+        //System.out.println("QpidServer doGet " + path);
+        //tx.logRequest();
+
+        if (path.startsWith("/qpid/connection/"))
+        {
+            path = path.substring(17);
+
+            String user = tx.getPrincipal(); // Using the principal lets different users use the default connection.
+            if (path.length() == 0)
+            { // handle "/qpid/connection/" request with unspecified connection (returns list of available connections).
+                tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(_connections.getAll(user)));   
+            }
+            else
+            { // if path.length() > 0 we're dealing with a specified Connection so extract the name and look it up.
+                String connectionName = path;
+                int i = path.indexOf("/");
+                if (i > 0) // Can use > rather than >= as we've already tested for "/qpid/connection/" above.
+                {
+                    connectionName = path.substring(0, i);
+                    path = path.substring(i + 1);
+                }
+                else
+                {
+                    path = "";
+                }
+
+                connectionName = user + "." + connectionName;
+
+                // TODO what if we don't want a default connection.
+                // If necessary we create a new "default" Connection associated with the user. The default connection
+                // attempts to connect to a broker specified in the QpidRestAPI config (or default 0.0.0.0:5672).
+                if (connectionName.equals(user + ".default"))
+                {
+                    ConnectionProxy defaultConnection = _connections.get(connectionName);
+                    if (defaultConnection == null)
+                    {
+                        defaultConnection = _connections.create(connectionName, _defaultBroker, "", false);
+
+                        // Wait a maximum of 1000ms for the underlying Qpid Connection to become available. If we
+                        // don't do this the first call using the default will return 404 Not Found.
+                        defaultConnection.waitForConnection(1000);
+                    }
+                }
+
+                // Find the Connection with the name extracted from the URI.
+                ConnectionProxy connection = _connections.get(connectionName);
+
+                if (connection == null)
+                {
+                    tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found.");
+                }
+                else if (!connection.isConnected())
+                {
+                    tx.sendResponse(HTTP_INTERNAL_ERROR, "text/plain", "500 Broker Disconnected.");
+                }
+                else
+                {
+                    if (path.length() == 0)
+                    { // handle request for information about a specified Console
+                        tx.sendResponse(HTTP_OK, "application/json", connection.toString());
+                    }
+                    else
+                    { // In this block we are dealing with resources associated with a specified connectionName.
+                      // path describes the resources specifically related to "/qpid/connection/<connectionName>"
+                        Console console = connection.getConsole();
+
+                        if (path.startsWith("console/objects/"))
+                        { // Get information about specified objects.
+                            path = path.substring(16);
+                            sendGetObjectsResponse(tx, console, path);
+                        }
+                        else if (path.startsWith("console/objects") && path.length() == 15)
+                        {  // If objects is unspecified treat as a synonym for classes.
+                            tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getClasses()));
+                        }
+                        else if (path.startsWith("console/address/"))
+                        { // Get the Console AMQP Address
+                            tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getAddress()));
+                        }
+                        else if (path.startsWith("console/address") && path.length() == 15)
+                        { // Get the Console AMQP Address
+                            tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getAddress()));
+                        }
+                        else if (path.startsWith("console/workItemCount/"))
+                        { // Returns the count of pending WorkItems that can be retrieved.
+                            tx.sendResponse(HTTP_OK, "text/plain", "" + console.getWorkitemCount());
+                        }
+                        else if (path.startsWith("console/workItemCount") && path.length() == 21)
+                        { // Returns the count of pending WorkItems that can be retrieved.
+                            tx.sendResponse(HTTP_OK, "text/plain", "" + console.getWorkitemCount());
+                        }
+                        else if (path.startsWith("console/nextWorkItem/"))
+                        { // Obtains the next pending work item, or null if none available.
+                            tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getNextWorkitem()));
+                        }
+                        else if (path.startsWith("console/nextWorkItem") && path.length() == 20)
+                        { // Obtains the next pending work item, or null if none available.
+                            tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getNextWorkitem()));
+                        }
+                        else if (path.startsWith("console/agents") && path.length() == 14)
+                        { // Get information about all available Agents.
+                            tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getAgents()));
+                        }
+                        else if (path.startsWith("console/agent/"))
+                        { // Get information about a specified Agent.
+                            Agent agent = console.getAgent(path.substring(14));
+                            if (agent == null)
+                            {
+                                tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found.");
+                            }
+                            else
+                            {
+                                tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(agent));
+                            }
+                        }
+                        else if (path.startsWith("console/agent") && path.length() == 13)
+                        { // If agent is unspecified treat as a synonym for agents.
+                            tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getAgents()));
+                        }
+                        else if (path.startsWith("console/classes/"))
+                        { // Get information about the classes for a specified Agent
+                            path = path.substring(16); // Get Agent name
+
+                            // TODO handle getClasses() for specified Agent
+                            tx.sendResponse(HTTP_NOT_IMPLEMENTED, "text/plain", "501 getClasses() for specified Agent not yet implemented.");
+                        }
+                        else if (path.startsWith("console/classes") && path.length() == 15)
+                        { // Get information about all the classes for all Agents
+                            tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getClasses()));
+                        }
+                        else if (path.startsWith("console/packages/"))
+                        { // Get information about the packages for a specified Agent
+                            path = path.substring(17); // Get Agent name
+
+                            // TODO handle getPackages() for specified Agent.
+                            tx.sendResponse(HTTP_NOT_IMPLEMENTED, "text/plain", "501 getPackages() for specified Agent not yet implemented.");
+                        }
+                        else if (path.startsWith("object/"))
+                        {
+                            /**
+                             * This is the REST implementation of getObjects(oid) it is also the equivalent of
+                             * the QmfConsoleData refresh() method where an object can update its state.
+                             * N.B. that the ManagementAgent on the broker appears not to set the timestamp properties
+                             * in the response to this call, which means that they get set to current time in the
+                             * QmfConsoleData, this is OK for _update_ts but not for _create_ts and _delete_ts
+                             * users of this call should be aware of that in their own code.
+                             */
+                            path = path.substring(7);
+
+                            // The ObjectId has been passed in the URI, create a real ObjectId
+                            ObjectId oid = new ObjectId(path);
+
+                            List<QmfConsoleData> objects = console.getObjects(oid);
+                            if (objects.size() == 0)
+                            {
+                                tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found.");
+                            }
+                            else
+                            {
+                                // Not that in a departure from the QMF2 API this returns the QmfConsoleData object
+                                // rather than a list of size one. Perhaps the APIs should be completely consistent
+                                // but this response seems more convenient.
+                                tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(objects.get(0)));
+                            }
+                        }
+                        else if (path.startsWith("console/packages") && path.length() == 16)
+                        { // Get information about all the packages for all Agents
+                            tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getPackages()));
+                        }
+                        else
+                        {
+                            tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found.");
+                        }
+                    }
+                }
+            }
+        }
+        else if (path.startsWith("/qpid/connection"))
+        { // handle "/qpid/connection" request with unspecified connection (returns list of available connections).
+            String user = tx.getPrincipal(); // Using the principal lets different users use the default connection.
+            tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(_connections.getAll(user)));   
+        }
+        else
+        {
+            tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found.");
+        }
+    }
+
+    /**
+     * Called by the Web Server to allow a Server to handle a POST request.
+     * <pre>
+     * POST: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;/object/&lt;ObjectId&gt;
+     *      HTTP body: {"_method_name":&lt;method&gt;,"_arguments":&lt;inArgs&gt;}
+     *      &lt;method&gt;: A string containing the QMF2 method name e.g. "getLogLevel", "setLogLevel", "create", "delete".
+     *      &lt;inArgs&gt;: A JSON string containing the method arguments e.g. {"level":"debug+:Broker"} for setLogLevel.
+     *      HTTP response: A JSON string containing the response e.g. {"level":"notice+"} for getLogLevel (may be empty).
+     *
+     *      This method invokes the QMF2 method &lt;method&gt; with arguments &lt;inArgs&gt; on the object &lt;ObjectId&gt;
+     * </pre>
+     * @param tx the HttpTransaction containing the request from the client and used to send the response.
+     */
+    @SuppressWarnings("unchecked")
+    public void doPost(final HttpTransaction tx) throws IOException
+    {
+        String path = tx.getRequestURI();
+
+        //System.out.println();
+        //System.out.println("QpidServer doPost " + path);
+        //System.out.println("thread = " + Thread.currentThread().getId());
+
+        if (path.startsWith("/qpid/connection/"))
+        {
+            path = path.substring(17);
+            String user = tx.getPrincipal();
+
+            String connectionName = path;
+            int i = path.indexOf("/");
+            if (i > 0) // Can use > rather than >= as we've already tested for "/qpid/connection/" above.
+            {
+                connectionName = user + "." + path.substring(0, i);
+                path = path.substring(i + 1);
+
+                // Find the Connection with the name extracted from the URI.
+                ConnectionProxy connection = _connections.get(connectionName);
+
+                if (connection == null)
+                {
+                    _log.info("QpidServer.doPost path: {} Connection not found.", tx.getRequestURI());
+                    tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found.");
+                }
+                else if (!connection.isConnected())
+                {
+                    tx.sendResponse(HTTP_INTERNAL_ERROR, "text/plain", "500 Broker Disconnected.");
+                }
+                else
+                { // If we get this far we should have found a Qpid Connection so retrieve the QMF2 Console Object.
+                    Console console = connection.getConsole();
+
+                    if (path.startsWith("object/"))
+                    {
+                        path = path.substring(7);
+
+                        // The ObjectId has been passed in the URI create an ObjectId and retrieve the Agent Name.
+                        ObjectId oid = new ObjectId(path);
+                        String agentName = oid.getAgentName();
+
+                        // The qpidd ManagementAgent doesn't populate AgentName, if it's empty assume it's the broker.
+                        agentName = agentName.equals("") ? "broker" : agentName;
+
+                        Agent agent = console.getAgent(agentName); // Find the Agent we got the QmfData from.
+                        if (agent == null)
+                        {
+                            _log.info("QpidServer.doPost path: {} Agent: {} not found.", tx.getRequestURI(), agentName);
+                            tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found.");
+                        }
+                        else
+                        {   // If we get this far we can create the Object and invoke the method.
+                            // We can create a QmfConsoleData with nothing but an ObjectId and the agent.
+                            QmfConsoleData object = new QmfConsoleData(Collections.EMPTY_MAP, agent);
+                            object.setObjectId(oid);
+
+                            String request = tx.getRequestString();
+                            _log.info("QpidServer.doPost path: {} body: {}", tx.getRequestURI(), request);
+
+                            //System.out.println(request);
+                            String method = "";
+                            try
+                            {
+                                Map<String, Object> reqMap = JSON.toMap(request);
+
+                                method = (String)reqMap.get("_method_name");
+                                Object arguments = reqMap.get("_arguments");
+
+                                Map args = (arguments instanceof Map) ? (Map)arguments : null;
+                                //System.out.println("method: " + method + ", args: " + args);
+
+                                // Parse the args if present into a QmfData (needed by invokeMethod).
+                                QmfData inArgs = (args == null) ? new QmfData() : new QmfData(args);
+
+                                // Invoke the specified method on the QmfConsoleData we've created.
+                                MethodResult results = null;
+
+                                _log.info("invokeMethod: {}", request);
+                                results = object.invokeMethod(method, inArgs);
+                                tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(results));
+                            }
+                            catch (QmfException qmfe)
+                            {
+                                _log.info("QpidServer.doPost() caught Exception {}", qmfe.getMessage());
+                                tx.sendResponse(HTTP_INTERNAL_ERROR, "text/plain", "invokeMethod(" + 
+                                                method + ") -> " + qmfe.getMessage());
+                            }
+                            catch (Exception e)
+                            {
+                                _log.info("QpidServer.doPost() caught Exception {}", e.getMessage());
+                                tx.sendResponse(HTTP_INTERNAL_ERROR, "text/plain", "500 " + e.getMessage());
+                            }
+                        }
+                    }
+                    else
+                    {
+                        tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found.");
+                    }
+                }
+            }
+            else
+            {
+                tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found.");
+            }
+        }
+        else
+        {
+            tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found.");
+        }
+    }
+
+    /**
+     * <pre>
+     * PUT: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;
+     *      HTTP body: {"url":&lt;url&gt;,"connectionOptions":&lt;connectionOptions&gt;[,"disableEvents":true;]}
+     *      &lt;url&gt;: A string containing an AMQP connection URL as used in the qpid::messaging API.
+     *      &lt;connectionOptions&gt;: A JSON string containing connectionOptions in the form specified in the 
+     *      qpid::messaging API.
+     * </pre>
+     * Called by the Web Server to allow a Server to handle a PUT request.
+     * This method creates a Qpid Connection Object with the name &lt;name&gt; using the specified url and options.
+     * <p>
+     * The optional disableEvents property is used to start up a QMF Connection which can only
+     * do synchronous calls such as getObjects() and can't receive Agent updates or QMF2 Events.
+     * <p>
+     * N.B. It is possible for the Qpid broker to be unavailable when this method is called so it is actually a
+     * ConnectionProxy that is created. This method waits for up to 1000ms for the underlying Qpid Connection to
+     * become available and then returns. Clients should be aware that this method successfully returning only
+     * implies that the ConnectionProxy is in place and the underlying Qpid Connection may not be available.
+     * If the broker is down the ConnectionProxy will periodically attempt to reconnect, but whilst it is down
+     * the REST API will return a 500 Broker Disconnected response to any PUT or POST call.
+     *
+     * @param tx the HttpTransaction containing the request from the client and used to send the response.
+     */
+    @SuppressWarnings("unchecked")
+    public void doPut(final HttpTransaction tx) throws IOException
+    {
+        String path = tx.getRequestURI();
+        //tx.logRequest();
+
+        if (path.startsWith("/qpid/connection/"))
+        {
+            path = path.substring(17);
+            String user = tx.getPrincipal();
+            String request = tx.getRequestString();
+            _log.info("QpidServer.doPut path: {} body: {}", tx.getRequestURI(), request);
+
+            String name = user + "." + path;
+
+            try
+            {
+                // The PUT request is a JSON string containing a url String property and a connectionOptions
+                // property which is itself a JSON String.
+                Map<String, String> reqMap = JSON.toMap(request);
+
+                String url = reqMap.get("url");
+                url = url.equals("") ? _defaultBroker : url;
+
+                // Extract the connectionOptions property and check its type is a MAP
+                Object options = reqMap.get("connectionOptions");
+                Map optionsMap = (options instanceof Map) ? (Map)options : null;
+
+                // Turn the connectionOptions Map back into a JSON String. Note that we can't just get
+                // connectionOptions as a String from reqMap as it is sent as JSON and the JSON.toMap()
+                // call on the POST request will fully parse it.
+                String connectionOptions = JSON.fromObject(optionsMap);
+
+                boolean disableEvents = false;
+                String disableEventsString = reqMap.get("disableEvents");
+                if (disableEventsString != null)
+                {
+                    disableEvents = disableEventsString.equalsIgnoreCase("true");
+                }
+
+                ConnectionProxy proxy = _connections.create(name, url, connectionOptions, disableEvents);
+
+                // Wait a maximum of 1000ms for the underlying Qpid Connection to become available then return.
+                proxy.waitForConnection(1000);
+                tx.sendResponse(HTTP_CREATED, "text/plain", "201 Created.");
+            } 
+            catch (Exception e)
+            {
+                _log.info("QpidServer.doPut() caught Exception {}", e.getMessage());
+                tx.sendResponse(HTTP_INTERNAL_ERROR, "text/plain", "500 " + e.getMessage());
+            }
+        }
+        else
+        {
+            tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found.");
+        }
+    }
+
+    /**
+     * Called by the Web Server to allow a Server to handle a DELETE request.
+     *
+     * DELETE: &lt;host&gt;:&lt;port&gt;/qpid/connection/&lt;name&gt;
+     *
+     *      This method deletes the Qpid Connection Object with the name &lt;name&gt;.
+     *
+     * @param tx the HttpTransaction containing the request from the client and used to send the response.
+     */
+    public void doDelete(final HttpTransaction tx) throws IOException
+    {
+        String path = tx.getRequestURI();
+        //tx.logRequest();
+
+        if (path.startsWith("/qpid/connection/"))
+        {
+            path = path.substring(17);
+            String user = tx.getPrincipal();
+            String name = user + "." + path;
+
+            //System.out.println("Deleting " + name);
+            _log.info("QpidServer.doDelete path: {}", tx.getRequestURI());
+
+            _connections.delete(name);
+            tx.sendResponse(HTTP_OK, "text/plain", "200 Deleted.");
+        }
+        else
+        {
+            tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found.");
+        }
+    }
+}
+
+

Propchange: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/QpidServer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/QpidServer.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/Server.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/Server.java?rev=1465662&view=auto
==============================================================================
--- qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/Server.java (added)
+++ qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/Server.java Mon Apr  8 15:19:04 2013
@@ -0,0 +1,95 @@
+/*
+ *
+ * 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.apache.qpid.restapi;
+
+import java.io.IOException;
+
+/**
+ * A Server represents a handler that runs within a Web server, which is invoked to process HTTP exchanges.
+ * Servers receive and respond to requests from Web clients that are encapsulated into HttpTransactions.
+ * <p>
+ * The Server and HttpTransaction interfaces are intended to provide abstractions to enable the "business logic" to
+ * be isolated from the actual Web Server implementation choice, so for example a concrete HttpTransaction implementation
+ * could be created by wrapping a com.sun.net.httpserver.HttpExchange, but equally another implementation could wrap
+ * javax.servlet.http.HttpServletRequest and javax.servlet.http.HttpServletResponse so for example an HttpServlet
+ * could delegate to a Server instance passing the HttpTransaction it constructed from the HttpServletRequest and
+ * HttpServletResponse.
+ *
+ * @author Fraser Adams
+ */
+public interface Server
+{
+    /**
+     * Called by the Web Server to allow a Server to handle a GET request.
+     * <p>
+     * The GET method should be safe, that is, without any side effects for which users are held responsible. For   
+     * example, most form queries have no side effects. If a client request is intended to change stored data, the 
+     * request should use some other HTTP method.
+     * <p>
+     * The GET method should also be idempotent, meaning that it can be safely repeated. Sometimes making a method safe 
+     * also makes it idempotent. For example, repeating queries is both safe and idempotent, but buying a product online 
+     * or modifying data is neither safe nor idempotent.
+     *
+     * @param tx the HttpTransaction containing the request from the client and used to send the response.
+     */
+    public void doGet(HttpTransaction tx) throws IOException;
+
+    /**
+     * Called by the Web Server to allow a Server to handle a POST request.
+     * <p>
+     * The HTTP POST method allows the client to send data of unlimited length to the Web server a single time and is 
+     * useful when posting information such as credit card numbers. 
+     * <p>
+     * This method does not need to be either safe or idempotent. Operations requested through POST can have side
+     * effects for which the user can be held accountable, for example, updating stored data or buying items online. 
+     *
+     * @param tx the HttpTransaction containing the request from the client and used to send the response.
+     */
+    public void doPost(HttpTransaction tx) throws IOException;
+
+    /**
+     * Called by the Web Server to allow a Server to handle a PUT request.
+     * <p>
+     * The PUT operation allows a client to place a file on the server and is similar to sending a file by FTP.
+     * <p>
+     * This method does not need to be either safe or idempotent. Operations that doPut performs can have side effects 
+     * for which the user can be held accountable. When using this method, it may be useful to save a copy of the 
+     * affected URL in temporary storage. 
+     *
+     * @param tx the HttpTransaction containing the request from the client and used to send the response.
+     */
+    public void doPut(HttpTransaction tx) throws IOException;
+
+    /**
+     * Called by the Web Server to allow a Server to handle a DELETE request.
+     * <p>
+     * The DELETE operation allows a client to remove a document or Web page from the server.
+     * <p>
+     * This method does not need to be either safe or idempotent. Operations requested through DELETE can have side 
+     * effects for which users can be held accountable. When using this method, it may be useful to save a copy of the 
+     * affected URL in temporary storage.
+     *
+     * @param tx the HttpTransaction containing the request from the client and used to send the response.
+     */
+    public void doDelete(HttpTransaction tx) throws IOException;
+}
+
+

Propchange: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/Server.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/Server.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/Authenticator.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/Authenticator.java?rev=1465662&view=auto
==============================================================================
--- qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/Authenticator.java (added)
+++ qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/Authenticator.java Mon Apr  8 15:19:04 2013
@@ -0,0 +1,151 @@
+/*
+ *
+ * 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.apache.qpid.restapi.httpserver;
+
+// Simple Logging Facade 4 Java
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Properties;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import com.sun.net.httpserver.BasicAuthenticator;
+
+/**
+ * This class implements a simple com.sun.net.httpserver.BasicAuthenticator. Clearly it's not very secure being a
+ * BasicAuthenticator that takes a plain (well Base64 encoded) username/password.
+ * TODO Clearly something more secure needs to be implemented.....
+ *
+ * This class creates a Timer used to schedule regular checks on the account.properties file and if it has changed
+ * it reloads the cache used for looking up the credentials. The poll period is every 10 seconds which should be OK
+ * for checking account updates. The class only updates the cache if the account.properties file has actually changed.
+ * Polling isn't ideal, but for this application it's probably no big deal. With Java 7 there is a Watch Service API
+ * https://blogs.oracle.com/thejavatutorials/entry/watching_a_directory_for_changes that allows asynchronous
+ * notification of changes, but it's an unnecessary dependency on Java 7 for this application.
+ *
+ * @author Fraser Adams
+ */
+public class Authenticator extends BasicAuthenticator
+{
+    private static final int CHECK_PERIOD = 10000; // Check every 10 seconds if the account properties have been changed.
+    private static final String ACCOUNT_FILENAME = "account.properties";
+    private static final Logger _log = LoggerFactory.getLogger(Authenticator.class);
+
+    private File _file;
+    private Properties _accountCache;
+    private long _accountFileLastModified = 0; // Used to check for updates to the account properties.
+
+    /**
+     * Create a Timer used to schedule regular checks on the account.properties file.
+     */
+    private Timer _timer = new Timer(true);
+
+    /**
+     * This private inner class is a fairly trivial TimerTask whose run() method simply calls checkAccountFile()
+     * in the main Authenticator class to check for account changes.
+     */
+    private final class CacheUpdater extends TimerTask
+    {
+        public void run()
+        {
+            checkAccountFile();
+        }
+    }
+
+    /**
+     * Construct the Authenticator. This fires up the CacheUpdater TimerTask to periodically check for changes.
+     * @param realm the authentication realm to use.
+     * @param path the path of the directory holding the account properties file.
+     */
+    public Authenticator(final String realm, final String path)
+    {
+        super(realm);
+
+        String accountPathname = path + "/" + ACCOUNT_FILENAME;
+        _file = new File(accountPathname);
+
+        if (_file.exists())
+        {
+            CacheUpdater updater = new CacheUpdater();
+            _timer.schedule(updater, 0, CHECK_PERIOD);
+        }
+        else
+        {
+            System.out.println("Authentication file " + accountPathname + " is missing.\nCannot continue - exiting.");
+            System.exit(1);
+        }
+    }
+
+    /**
+     * Checks if the account properties file has been updated, if it has it calls loadCache() to reload the cache.
+     */
+    public void checkAccountFile()
+    {
+        long mtime = _file.lastModified();
+        if (mtime != _accountFileLastModified)
+        {
+            _accountFileLastModified = mtime;
+            loadCache();
+        }
+    }
+
+    /**
+     * Load the account properties file into the account cache.
+     */
+    private void loadCache()
+    {
+    	try
+        {
+            Properties properties = new Properties();
+    	    properties.load(new FileInputStream(_file));
+
+            // Set the cache to be the newly loaded one.
+            _accountCache = properties;
+    	}
+        catch (IOException ex)
+        {
+            _log.info("loadCache failed with {}.", ex.getMessage());
+        }
+    }
+
+    @Override
+    public boolean checkCredentials(final String username, final String password)
+    {
+        //System.out.println("username = " + username);
+        //System.out.println("password = " + password);
+
+        // The original version of this forgot to check check for a null username Property. Thanks to 
+        // Bruno Matos for picking that up and supplying the fix below.
+        if (_accountCache.getProperty(username) != null &&
+            _accountCache.getProperty(username).equals(password))
+        {
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+}

Propchange: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/Authenticator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/Authenticator.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/Delegator.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/Delegator.java?rev=1465662&view=auto
==============================================================================
--- qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/Delegator.java (added)
+++ qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/Delegator.java Mon Apr  8 15:19:04 2013
@@ -0,0 +1,89 @@
+/*
+ *
+ * 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.apache.qpid.restapi.httpserver;
+
+import java.io.IOException;
+import static java.net.HttpURLConnection.HTTP_BAD_METHOD;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+import org.apache.qpid.restapi.HttpTransaction;
+import org.apache.qpid.restapi.Server;
+
+/**
+ * Delegator is an implementation of com.sun.net.httpserver.HttpHandler that is used to delegate to Server objects
+ * and thus provide an abstraction between the Web Server implementation (e.g. HttpServer or Servlets) and the
+ * implementation neutral Server and HttpTransaction interfaces.
+ * <p>
+ * In order to replace the HttpServer implementation with a Servlet based implementation all that should be necessary
+ * is a concrete implementation of HttpTransaction that wraps HttpServletRequest and HttpServletResponse and subclasses
+ * of HttpServlet that delegate to the appropriate Server instances from the appropriate context in a similar way
+ * to the Delegator class here.
+ * 
+ * @author Fraser Adams
+ */
+public class Delegator implements HttpHandler
+{
+    private final Server _server;
+
+    /**
+     * Construct a Delegator instance that delegates to the specified Server instance.
+     * @param server the Server instance that this Delegator delegates to.
+     */
+    public Delegator(Server server)
+    {
+        _server = server;
+    }
+
+    /**
+     * Implements the HttpHandler handle interface and delegates to the Server instance.
+     * @param exchange the HttpExchange exchange object passed by the HttpServer. This will be used to construct
+     *        an HttpTransaction instance that will be passed to the Server.
+     */
+    public void handle(final HttpExchange exchange) throws IOException
+    {
+        HttpTransaction tx = new HttpExchangeTransaction(exchange);
+        String method = tx.getMethod();
+        if (method.equals("GET"))
+        {
+            _server.doGet(tx);
+        }
+        else if (method.equals("POST"))
+        {
+            _server.doPost(tx);
+        }
+        else if (method.equals("PUT"))
+        {
+            _server.doPut(tx);
+        }
+        else if (method.equals("DELETE"))
+        {
+            _server.doDelete(tx);
+        }
+        else
+        {
+            tx.sendResponse(HTTP_BAD_METHOD, "text/plain", "405 Bad Method.");
+        }
+    }
+}
+
+

Propchange: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/Delegator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/Delegator.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/HttpExchangeTransaction.java
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/HttpExchangeTransaction.java?rev=1465662&view=auto
==============================================================================
--- qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/HttpExchangeTransaction.java (added)
+++ qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/HttpExchangeTransaction.java Mon Apr  8 15:19:04 2013
@@ -0,0 +1,311 @@
+/*
+ *
+ * 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.apache.qpid.restapi.httpserver;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpPrincipal;
+
+import org.apache.qpid.restapi.HttpTransaction;
+
+/**
+ * This class provides an implementation of the HttpTransaction interface that wraps com.sun.net.httpserver.HttpExchange
+ * in order to provide an implementation neutral facade to the Server classes.
+ *
+ * @author Fraser Adams
+ */
+public final class HttpExchangeTransaction implements HttpTransaction
+{
+    final HttpExchange _exchange;
+
+    /**
+     * Construct an HttpExchangeTransaction from an HttpExchange object.
+     */
+    public HttpExchangeTransaction(final HttpExchange exchange)
+    {
+        _exchange = exchange;
+    }
+
+    /**
+     * Log the HTTP request information (primarily for debugging purposes)
+     */
+    public void logRequest()
+    {
+        System.out.println(_exchange.getRequestMethod() + " " + _exchange.getRequestURI());
+        for (Map.Entry<String, List<String>> header : _exchange.getRequestHeaders().entrySet())
+        {
+            System.out.println(header);
+        }
+        System.out.println("From: " + getRemoteHost() + ":" + getRemotePort());
+    }
+
+    /**
+     * Return the content passed in the request from the client as a Stream.
+     * @return the content passed in the request from the client as a Stream.
+     */
+    public InputStream getRequestStream() throws IOException
+    {
+        return _exchange.getRequestBody();
+    }
+
+    /**
+     * Return the content passed in the request from the client as a String.
+     * @return the content passed in the request from the client as a String.
+     */
+    public String getRequestString() throws IOException
+    {
+        return new String(getRequest());
+    }
+
+
+    /**
+     * Return the content passed in the request from the client as a byte[].
+     * @return the content passed in the request from the client as a byte[].
+     */
+    public byte[] getRequest() throws IOException
+    {
+        InputStream is = _exchange.getRequestBody();
+
+        // Convert InputStream to byte[].
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1024];
+        int len;
+        while ((len = is.read(buffer, 0, 1024)) != -1)
+        {
+            bos.write(buffer, 0, len);
+        }
+        return bos.toByteArray();
+    }
+
+    /**
+     * Send the content passed as a String as an HTTP response back to the client.
+     * @param status the HTTP status code e.g. 200 for OK.
+     * @param mimeType the mimeType of the response content e.g. text/plain, text/xml, image/jpeg etc.
+     * @param content the content of the response passed as a String.
+     */
+    public void sendResponse(final int status, final String mimeType, final String content) throws IOException
+    {
+        if (content == null)
+        { // If response length has the value -1 then no response body is being sent.
+            _exchange.getResponseHeaders().set("Content-Type", mimeType);
+            _exchange.sendResponseHeaders(status, -1);
+            _exchange.close();
+        }
+        else
+        {
+            sendResponse(status, mimeType, content.getBytes());
+        }
+    }
+
+    /**
+     * Send the content passed as a byte[] as an HTTP response back to the client.
+     * @param status the HTTP status code e.g. 200 for OK.
+     * @param mimeType the mimeType of the response content e.g. text/plain, text/xml, image/jpeg etc.
+     * @param content the content of the response passed as a byte[].
+     */
+    public void sendResponse(final int status, final String mimeType, final byte[] content) throws IOException
+    {
+        _exchange.getResponseHeaders().set("Content-Type", mimeType);
+        if (content == null)
+        { // If response length has the value -1 then no response body is being sent. 
+            _exchange.sendResponseHeaders(status, -1);
+            _exchange.close();
+        }
+        else
+        {
+            _exchange.sendResponseHeaders(status, content.length);
+            OutputStream os = _exchange.getResponseBody();
+            os.write(content);
+            os.flush();
+            os.close();
+            _exchange.close();
+        }   
+    }
+
+    /**
+     * Send the content passed as an InputStream as an HTTP response back to the client.
+     * @param status the HTTP status code e.g. 200 for OK.
+     * @param mimeType the mimeType of the response content e.g. text/plain, text/xml, image/jpeg etc.
+     * @param is the content of the response passed as an InputStream.
+     */
+    public void sendResponse(final int status, final String mimeType, final InputStream is) throws IOException
+    {
+        _exchange.getResponseHeaders().set("Content-Type", mimeType);
+        if (is == null)
+        { // If response length has the value -1 then no response body is being sent.
+            _exchange.sendResponseHeaders(status, -1);
+            _exchange.close();
+        }
+        else
+        {
+            _exchange.sendResponseHeaders(status, 0); // For a stream we set to zero to force chunked transfer encoding.
+            OutputStream os = _exchange.getResponseBody();
+
+            byte[] buffer = new byte[8192];
+            while (true)
+            {
+                int read = is.read(buffer, 0, buffer.length);
+                if (read == -1) // Loop until EOF is reached
+                {
+                    break;
+                }
+                os.write(buffer, 0, read);
+            }
+          
+            os.flush();
+            os.close();
+            _exchange.close();
+        }
+    }
+
+    /**
+     * Returns the Internet Protocol (IP) address of the client or last proxy that sent the request.
+     * @return the Internet Protocol (IP) address of the client or last proxy that sent the request.
+     */
+    public String getRemoteAddr()
+    {
+        return _exchange.getRemoteAddress().getAddress().getHostAddress();
+    }
+
+    /**
+     * Returns the fully qualified name of the client or the last proxy that sent the request.
+     * @return the fully qualified name of the client or the last proxy that sent the request.
+     */
+    public String getRemoteHost()
+    {
+        return _exchange.getRemoteAddress().getHostName();
+    }
+
+    /**
+     * Returns the Internet Protocol (IP) source port of the client or last proxy that sent the request.
+     * @return the Internet Protocol (IP) source port of the client or last proxy that sent the request.
+     */
+    public int getRemotePort()
+    {
+        return _exchange.getRemoteAddress().getPort();
+    }
+
+    /**
+     * Returns a String containing the name of the current authenticated user. If the user has not been authenticated, 
+     * the method returns null.
+     * @return a String containing the name of the user making this request; null if the user has not been authenticated.
+     */
+    public String getPrincipal()
+    {
+        HttpPrincipal principal = _exchange.getPrincipal();
+        return principal == null ? null : principal.getUsername();
+    }
+
+    /**
+     * Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT.
+     * @return a String specifying the name of the method with which this request was made.
+     */
+    public String getMethod()
+    {
+        return _exchange.getRequestMethod();
+    }
+
+    /**
+     * Returns the part of this request's URL from the protocol name up to the query string in the first line of
+     * the HTTP request.
+     * @return a String containing the part of the URL from the protocol name up to the query string.
+     */
+    public String getRequestURI()
+    {
+        return _exchange.getRequestURI().getPath();
+    }
+
+    /**
+     * Sets a response header with the given name and value. If the header had already been set, the new value
+     * overwrites the previous one.
+     * @param name a String specifying the header name.
+     * @param value a String specifying the header value. If it contains octet string, it should be encoded according
+     *        to RFC 2047.
+     */
+    public void setHeader(final String name, final String value)
+    {
+        _exchange.getResponseHeaders().set(name, value);
+    }
+
+    /**
+     * Returns the value of the specified request header as a String. If the request did not include a header of the 
+     * specified name, this method returns null. If there are multiple headers with the same name, this method returns 
+     * the first head in the request. The header name is case insensitive. You can use this method with any request 
+     * header.
+     * @param name a String specifying the header name.
+     * @return a String containing the value of the requested header, or null if the request does not have a header of 
+     *         that name.
+     */
+    public String getHeader(final String name)
+    {
+        return _exchange.getRequestHeaders().getFirst(name);
+    }
+
+    /**
+     * Returns the String value of the specified cookie.
+     * @param name a String specifying the cookie name.
+     */
+    public String getCookie(final String name)
+    {
+        Headers headers = _exchange.getRequestHeaders();
+        if (!headers.containsKey("Cookie"))
+        {
+            return null;
+        }
+
+        List<String> values = headers.get("cookie");
+        for (String value : values)
+        {
+            String[] cookies = value.split(";");
+            for (String cookie : cookies)
+            {
+                String[] cdata = cookie.split("=");
+                if (cdata[0].trim().equals(name))
+                {
+                    //return URLDecode(cdata[1]);
+                    return cdata[1];
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds the specified cookie to the response. This method can be called multiple times to set more than one cookie.
+     * @param name a String specifying the cookie name.
+     * @param value a String specifying the cookie value.
+     */
+    public void addCookie(final String name, final String value)
+    {
+        //String data = name + "=" + URLEncode(value) + "; path=/";
+        String data = name + "=" + value + "; path=/";
+        _exchange.getResponseHeaders().add("Set-Cookie", data);
+    }
+}
+
+

Propchange: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/HttpExchangeTransaction.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/httpserver/HttpExchangeTransaction.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/servlet/TODO
URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/servlet/TODO?rev=1465662&view=auto
==============================================================================
--- qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/servlet/TODO (added)
+++ qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/servlet/TODO Mon Apr  8 15:19:04 2013
@@ -0,0 +1,4 @@
+The org.apache.qpid.restapi.HttpTransaction and org.apache.qpid.restapi.Server interfaces are intended to
+provide abstractions to the underlying HTTP Server technology and thus should make it fairly easy to do
+a Servlet based implementation without having to rewrite the core REST API code. A Servlet implementation
+is still on the "TODO" list though.

Propchange: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/servlet/TODO
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: qpid/trunk/qpid/tools/src/java/src/restapi/java/org/apache/qpid/restapi/servlet/TODO
------------------------------------------------------------------------------
    svn:mime-type = text/plain



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org