package hudson.model;

import hudson.util.IOException2;
import org.dom4j.CharacterData;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.*;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.stream.StreamResult;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.List;

/**
 * Used to expose remote access API for ".../api/"
 *
 * <p>
 * If the parent object has a <tt>_api.jelly</tt> view, it will be included
 * in the api index page.
 *
 * @author Kohsuke Kawaguchi
 * @see Exported
 */
public class Api extends AbstractModelObject {
    /**
     * Model object to be exposed as XML/JSON/etc.
     */
    public final Object bean;

    public Api(Object bean) {
        this.bean = bean;
    }

    public String getDisplayName() {
        return "API";
    }

    public String getSearchUrl() {
        return "api";
    }

    /**
     * Exposes the bean as XML.
     */
    public void doXml(StaplerRequest req, StaplerResponse rsp, @QueryParameter("xpath") String xpath) throws IOException, ServletException {
        if(xpath==null) {
            // serve the whole thing
            rsp.serveExposedBean(req,bean,Flavor.XML);
            return;
        }

        StringWriter sw = new StringWriter();

        // first write to String
        Model p = MODEL_BUILDER.get(bean.getClass());
        p.writeTo(bean,Flavor.XML.createDataWriter(bean,sw));

        // apply XPath
        Object result;
        try {
            Document dom = new SAXReader().read(new StringReader(sw.toString()));
            List list = dom.selectNodes(xpath);
            if(list.isEmpty()) {
                // XPath didn't match
                rsp.setStatus(HttpServletResponse.SC_NOT_FOUND);
                rsp.getWriter().print("XPath "+xpath+" didn't match");
                return;
            }
            if(list.size()>2) {
                // XPath didn't match
                rsp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                rsp.getWriter().print("XPath "+xpath+" matched "+list.size()+" nodes. Create XPath that only matches one");
                return;
            }

            result = list.get(0);
        } catch (DocumentException e) {
            throw new IOException2(e);
        }

        if(result instanceof CharacterData) {
            rsp.setContentType("text/plain");
            rsp.getWriter().print(((CharacterData)result).getText());
            return;
        }

        if(result instanceof String) {
            rsp.setContentType("text/plain");
            rsp.getWriter().print(result.toString());
            return;
        }

        // otherwise XML
        rsp.setContentType("application/xml;charset=UTF-8");
        new XMLWriter(rsp.getWriter()).write(result);
    }

    /**
     * Generate schema.
     */
    public void doSchema(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        rsp.setContentType("application/xml");
        StreamResult r = new StreamResult(rsp.getOutputStream());
        new SchemaGenerator(new ModelBuilder().get(bean.getClass())).generateSchema(r);
        r.getOutputStream().close();
    }

    /**
     * Exposes the bean as JSON.
     */
    public void doJson(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        rsp.serveExposedBean(req,bean, Flavor.JSON);
    }

    private static final ModelBuilder MODEL_BUILDER = new ModelBuilder();
}

@@ -140,24 +140,84 @@
 tException;%0A
+import org.dom4j.DocumentFactory;%0Aimport org.dom4j.Element;%0A
 import org.d
@@ -231,24 +231,24 @@
 .SAXReader;%0A
-
 import org.d
@@ -1384,16 +1384,59 @@
 ng xpath
+, @QueryParameter(%22wrapper%22) String wrapper
 ) throws
@@ -2277,24 +2277,501 @@
 size()%3E2) %7B%0A
+                if(wrapper!=null) %7B%0A                    Element root = DocumentFactory.getInstance().createElement(wrapper);%0A                    for (Object o : list) %7B%0A                        if(o instanceof String) %7B%0A                            root.addText(o.toString());%0A                        %7D else %7B%0A                            root.add((org.dom4j.Node)o);%0A                        %7D%0A                    %7D%0A                    result = root;%0A                %7D else %7B%0A    
             
@@ -2788,32 +2788,36 @@
 th didn't match%0A
+    
                 
@@ -2885,32 +2885,36 @@
                 
+  
+  
 rsp.getWriter().
@@ -3013,32 +3013,36 @@
                 
+    
 return;%0A        
@@ -3037,35 +3037,61 @@
 rn;%0A            
-%7D%0A%0A
+    %7D%0A            %7D else%0A    
             resu

support a wrapper if multiple nodes need to be returned