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: <host>:<port>/qpid/connection/<name>
+ * HTTP body: {"url":<url>,"connectionOptions":<connectionOptions>[,"disableEvents":true;]}
+ * <url>: A string containing an AMQP connection URL as used in the qpid::messaging API.
+ * <connectionOptions>: A JSON string containing connectionOptions in the form specified in the
+ * qpid::messaging API.
+ *
+ * This method creates a Qpid Connection Object with the name <name> 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: <host>:<port>/qpid/connection/<name>
+ *
+ * This method deletes the Qpid Connection Object with the name <name>.
+ *
+ * POST: <host>:<port>/qpid/connection/<name>/object/<ObjectId>
+ * HTTP body: {"_method_name":<method>,"_arguments":<inArgs>}
+ * <method>: A string containing the QMF2 method name e.g. "getLogLevel", "setLogLevel", "create", "delete".
+ * <inArgs>: 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 <method> with arguments <inArgs> on the object <ObjectId>
+ *
+ * GET: <host>:<port>/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: <host>:<port>/qpid/connection/<name>
+ *
+ * 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 <name>. e.g.
+ * {"url":"0.0.0.0:5672","connectionOptions":{}}
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/console/objects/<className>
+ *
+ * This method retrieves (as a JSON string) the list of QmfConsoleData objects with the specified <className>
+ * using the QMF2 Console associated with the Qpid Connection Object with the name <name>.
+ * This is the REST equivalent of Console.getObjects(className) which searches across all packages and all Agents
+ * for the specified className.
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/console/objects/<packageName>/<className>
+ * !!not yet implemented!!
+ * This method retrieves (as a JSON string) the list of QmfConsoleData objects with the specified
+ * <packageName> and <className> using the QMF2 Console associated with the Qpid Connection Object
+ * with the name <name>.
+ * This is the REST equivalent of Console.getObjects(packageName, className) which searches across all Agents
+ * for the specified className in the package packageName.
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/object/<ObjectId>
+ * This method retrieves (as a JSON string) the QmfConsoleData object with the specified <ObjectId>
+ * using the QMF2 Console associated with the Qpid Connection Object with the name <name>.
+ * 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: <host>:<port>/qpid/connection/<name>/console/objects
+ * GET: <host>:<port>/qpid/connection/<name>/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: <host>:<port>/qpid/connection/<name>/console/classes/<agentName>
+ *
+ * This method retrieves (as a JSON string) the list of SchemaClassId for all available Schema on the
+ * Agent named <agentName>.
+ * This is the REST equivalent of Console.getClasses(agent) which searches across a specified Agent.
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/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: <host>:<port>/qpid/connection/<name>/console/packages/<agentName>
+ *
+ * This method retrieves (as a JSON string) the list of all known Packages on the Agent named <agentName>.
+ * This is the REST equivalent of Console.getPackages(agent) which searches across a specified Agent.
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/console/agents
+ * GET: <host>:<port>/qpid/connection/<name>/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: <host>:<port>/qpid/connection/<name>/console/agent/<agentName>
+ *
+ * This method retrieves (as a JSON string) the Agent named <agentName>.
+ * This is the REST equivalent of Console.getAgent(agentName).
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/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: <host>:<port>/qpid/connection/<name>/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: <host>:<port>/qpid/connection/<name>/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: <host>:<port>/qpid/connection/<name>/object/<ObjectId>
+ * HTTP body: {"_method_name":<method>,"_arguments":<inArgs>}
+ * <method>: A string containing the QMF2 method name e.g. "getLogLevel", "setLogLevel", "create", "delete".
+ * <inArgs>: 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 <method> with arguments <inArgs> on the object <ObjectId>
+ * </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: <host>:<port>/qpid/connection/<name>
+ * HTTP body: {"url":<url>,"connectionOptions":<connectionOptions>[,"disableEvents":true;]}
+ * <url>: A string containing an AMQP connection URL as used in the qpid::messaging API.
+ * <connectionOptions>: 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 <name> 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: <host>:<port>/qpid/connection/<name>
+ *
+ * This method deletes the Qpid Connection Object with the name <name>.
+ *
+ * @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