/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2010-2012 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package org.glassfish.admingui.common.util; import org.w3c.dom.*; import javax.ws.rs.core.Response; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** * <p> This class abstracts the response from the admin console code so that * we can use JSON / REST interchangeably.</p> * * @author jasonlee * @author Ken Paulsen (ken.paulsen@oracle.com) */ public abstract class RestResponse { public abstract int getResponseCode(); public abstract String getResponseBody(); public static RestResponse getRestResponse(Response response) { return new JerseyRestResponse(response); } public boolean isSuccess() { int status = getResponseCode(); return (status >= 200) && (status <= 299); } /** * <p> This method abstracts the physical response to return a consistent * data structure. For many responses, this data structure may look * like:</p> * <p/> * <p> * <code> * Map<String, Object> * { * "responseCode" : Integer // HTTP Response code, ie. 200 * "output" : String // The Raw Response Body * "description" : String // Command Description * // 0 or more messages returned from the command * "messages" : List<Map<String, Object>> * [ * { * "message" : String // Raw Message String * "..." : String // Additional custom attributes * // List of properties for this message * "properties" : List<Map<String, Object>> * [ * { * "name" : String // The Property Name * "value" : String // The Property Value * "properties" : List // Child Properties * }, ... * ] * }, ... * ] * } * </code> * </p> */ public abstract Map<String, Object> getResponse(); public abstract void close(); } class JerseyRestResponse extends RestResponse { protected Response response; private String body = null; public JerseyRestResponse(Response response) { this.response = response; } @Override public String getResponseBody() { if (body == null) { body = response.readEntity(String.class); } return body; } @Override public int getResponseCode() { return response.getStatus(); } /** * <p> This method abstracts the physical response to return a consistent * data structure.</p> */ @Override public Map<String, Object> getResponse() { // Prepare the result object Map<String, Object> result = new HashMap<String, Object>(5); // Add the Response Code result.put("responseCode", getResponseCode()); // Add the Response Body // FIXME: Do not put responseBody into the Map... too big, not needed result.put("responseBody", getResponseBody()); String contentType = response.getHeaderString("Content-type"); if (contentType != null) { String responseBody = getResponseBody(); contentType = contentType.toLowerCase(GuiUtil.guiLocale); if (contentType.startsWith("application/xml")) { InputStream input = null; try { XMLInputFactory inputFactory = XMLInputFactory.newInstance(); inputFactory.setProperty(XMLInputFactory.IS_VALIDATING, false); input = new ByteArrayInputStream(responseBody.trim().getBytes("UTF-8")); XMLStreamReader parser = inputFactory.createXMLStreamReader(input); while (parser.hasNext()) { int event = parser.next(); switch (event) { case XMLStreamConstants.START_ELEMENT: { if ("map".equals(parser.getLocalName())) { result.put("data", processXmlMap(parser)); } break; } default: break; } } } catch (Exception ex) { Logger.getLogger(RestResponse.class.getName()).log(Level.SEVERE, null, ex); throw new RuntimeException(ex); } finally { try { if (input != null){ input.close(); } } catch (IOException ex) { Logger.getLogger(RestResponse.class.getName()).log(Level.SEVERE, null, ex); } } // // If XML... // Document document = MiscUtil.getDocument(getResponseBody()); // Element root = document.getDocumentElement(); // if ("action-report".equalsIgnoreCase(root.getNodeName())) { // // Default XML document type... // // Add the Command Description // result.put("description", root.getAttribute("description")); // result.put("exit-code", root.getAttribute("exit-code")); // // // Add the messages // List<Map<String, Object>> messages = new ArrayList<Map<String, Object>>(2); // result.put("messages", messages); // // // Iterate over each node looking for message-part // NodeList nl = root.getChildNodes(); // int len = nl.getLength(); // Node child; // for (int idx = 0; idx < len; idx++) { // child = nl.item(idx); // if ((child.getNodeType() == Node.ELEMENT_NODE) && (child.getNodeName().equals("message-part"))) { // messages.add(processMessagePart(child)); // } // } // } else { // // Generate a generic Java structure from the XML // result.put("data", getJavaFromXML(root)); // } } else if (contentType.startsWith("application/json")) { // Decode JSON result.put("data", JSONUtil.jsonToJava(responseBody)); } else { // Unsupported Response Format! System.out.println("Unsupported Response Format: '" + contentType + "'!"); } } // Return the populated result data structure return result; } /** * <p> This method will create a Map<String, Object>. It will add all the * attributes of the given root Element to the Map. It will then walk * any child Elements and add the children as a * <code>List<Map<String, Object>></code> for each unique * element name.</p> */ private Map<String, Object> getJavaFromXML(Element element) { // Create a new Map to store the properties and children. Map<String, Object> result = new HashMap<String, Object>(10); // Add all the attributes... NamedNodeMap attributes = element.getAttributes(); int attLen = attributes.getLength(); for (int attIdx = 0; attIdx < attLen; attIdx++) { Node attribute = attributes.item(attIdx); result.put(attribute.getNodeName(), attribute.getNodeValue()); } // Now add any child Elements String childName; Node child; List<Map<String, Object>> childList; NodeList nl = element.getChildNodes(); int len = nl.getLength(); for (int idx = 0; idx < len; idx++) { child = nl.item(idx); if ((child.getNodeType() == Node.ELEMENT_NODE)) { // We found a child Element... childName = child.getNodeName(); if (result.containsKey(childName)) { // Already created, add to it childList = (List<Map<String, Object>>) result.get(childName); } else { // Not created yet, create it childList = new ArrayList<Map<String, Object>>(5); result.put(childName, childList); } // Add the child to the List childList.add(getJavaFromXML((Element) child)); } } // Return the fully populated Map<String, Object> return result; } /** * <p> This method returns a fully populated Map<String, Object> for the * given "message-part" <code>Node</code>.</p> */ private Map<String, Object> processMessagePart(Node messageNode) { // Create a Map to hold all the Message info... Map<String, Object> message = new HashMap<String, Object>(5); // Pull off all the attributes from the message... NamedNodeMap attributes = messageNode.getAttributes(); int attLen = attributes.getLength(); for (int attIdx = 0; attIdx < attLen; attIdx++) { // "message" should be one of them... add them all Node attribute = attributes.item(attIdx); message.put(attribute.getNodeName(), attribute.getNodeValue()); } // Now see if there are any child message-parts or child properties NodeList nl = messageNode.getChildNodes(); int len = nl.getLength(); Node child; boolean hasChildMessages = false; boolean hasProperty = false; List<Map<String, Object>> properties = null; List<Map<String, Object>> messages = null; for (int idx = 0; idx < len; idx++) { child = nl.item(idx); if ((child.getNodeType() == Node.ELEMENT_NODE)) { if (child.getNodeName().equals("message-part")) { // Recursively add this new message-part child if (!hasChildMessages) { // Create a List to hold the messages. messages = new ArrayList<Map<String, Object>>(2); message.put("messages", messages); hasChildMessages = true; } // Add the message messages.add(processMessagePart(child)); } else if (child.getNodeName().equals("property")) { // Add this new property if (!hasProperty) { // Create a List to hold the properties. properties = new ArrayList<Map<String, Object>>(10); message.put("properties", properties); hasProperty = true; } // Add the property properties.add(processProperty(child)); } } } // Return the populated message return message; } /** * <p> This method returns a fully populated Map<String, Object> for the * given "property" <code>Node</code>.</p> */ private Map<String, Object> processProperty(Node propertyNode) { // Create a Map to hold all the Message info... Map<String, Object> property = new HashMap<String, Object>(5); // Pull off all the attributes from the property... NamedNodeMap attributes = propertyNode.getAttributes(); int attLen = attributes.getLength(); for (int attIdx = 0; attIdx < attLen; attIdx++) { // "name" and "value" should be the only 2, but add them all... Node attribute = attributes.item(attIdx); property.put(attribute.getNodeName(), attribute.getNodeValue()); } // Now see if there are any child properties NodeList nl = propertyNode.getChildNodes(); int len = nl.getLength(); Node child; boolean hasProperty = false; List<Map<String, Object>> properties = null; for (int idx = 0; idx < len; idx++) { child = nl.item(idx); if ((child.getNodeType() == Node.ELEMENT_NODE)) { if (child.getNodeName().equals("property")) { // Add this new property if (!hasProperty) { // Create a List to hold the properties. properties = new ArrayList<Map<String, Object>>(10); property.put("properties", properties); hasProperty = true; } // Add the property properties.add(processProperty(child)); } } } // Return the populated property data structure return property; } private static Map processXmlMap(XMLStreamReader parser) throws XMLStreamException { boolean endOfMap = false; Map<String, Object> entry = new HashMap<String, Object>(); String key = null; String element = null; while (!endOfMap) { int event = parser.next(); switch (event) { case XMLStreamConstants.START_ELEMENT: { if ("entry".equals(parser.getLocalName())) { key = parser.getAttributeValue(null, "key"); String value = parser.getAttributeValue(null, "value"); if (value != null) { entry.put(key, value); key = null; } } else if ("map".equals(parser.getLocalName())) { Map value = processXmlMap(parser); entry.put(key, value); } else if ("list".equals(parser.getLocalName())) { List value = processXmlList(parser); entry.put(key, value); } else { element = parser.getLocalName(); } break; } case XMLStreamConstants.END_ELEMENT: { if ("map".equals(parser.getLocalName())) { endOfMap = true; } element = null; break; } default: { String text=parser.getText(); if (element != null) { if ("number".equals(element)) { if (text.contains(".")) { entry.put(key, Double.parseDouble(text)); } else { entry.put(key, Long.parseLong(text)); } } else if ("string".equals(element)) { entry.put(key, text); } element = null; } } } } return entry; } private static List processXmlList(XMLStreamReader parser) throws XMLStreamException { List list = new ArrayList(); boolean endOfList = false; String element = null; while (!endOfList) { int event = parser.next(); switch (event) { case XMLStreamConstants.START_ELEMENT: { if ("map".equals(parser.getLocalName())) { list.add(processXmlMap(parser)); } else { element = parser.getLocalName(); } break; } case XMLStreamConstants.END_ELEMENT: { if ("list".equals(parser.getLocalName())) { endOfList = true; } element = null; break; } default: { String text = parser.getText(); if (element != null) { if ("number".equals(element)) { if (text.contains(".")) { list.add(Double.parseDouble(text)); } else { list.add(Long.parseLong(text)); } } else if ("string".equals(element)) { list.add(text); } element = null; } } } } return list; } @Override public void close() { response.close(); } }