All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.mavenplugins.doctest.ReportMojo Maven / Gradle / Ivy

Go to download

A simple Mojo for Restful Webservice integration Testing with self-contained documentation.

There is a newer version: 1.8.1
Show newest version
/**
 * Copyright 2012 the contributors
 *
 *    Licensed 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 com.github.mavenplugins.doctest;

import java.io.File;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.TreeMap;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.sink.SinkEventAttributeSet;
import org.apache.maven.doxia.siterenderer.Renderer;
import org.apache.maven.doxia.siterenderer.RendererException;
import org.apache.maven.doxia.util.HtmlTools;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;

import com.fasterxml.jackson.databind.ObjectMapper;

import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicInteger;

/**
 * This Mojo reports the doctest results.
 * 
 * @goal report
 * @phase site
 */
public class ReportMojo extends AbstractMavenReport {
    
    private static final String LINE_SEPARATOR = System.getProperty("line.separator");
    private static final Pattern JAVADOC_STAR_FINDER = Pattern.compile("^\\s*\\*\\s?", Pattern.MULTILINE);
    private static final Pattern JAVADOC_EMPTYLINE_FINDER = Pattern.compile("^\\s*\\*\\s*$", Pattern.MULTILINE);
    private static final Pattern ANY_METHOD_FINDER = Pattern.compile("public\\s+void\\s+.*\\s*\\((HttpResponse|"
            + HttpResponse.class.getName().replaceAll("\\.", "\\\\.") + ")", Pattern.CASE_INSENSITIVE
            | Pattern.MULTILINE);
    private static final SinkEventAttributeSet TABLE_CELL_STYLE_ATTRIBUTES = new SinkEventAttributeSet(new String[] {
            "style", "width:150px;" });
    private static final String JAVASCRIPT_CODE = "";
    
    /**
     * A container which encapsulates endpoints and contains the corresponding doctests.
     */
    public class DoctestsContainer {
        
        protected Map doctests = new TreeMap();
        protected String name;
        
        public String getName() {
            return name;
        }
        
        public void setName(String name) {
            this.name = name;
        }
        
        public Map getDoctests() {
            return doctests;
        }
        
        public void setDoctests(Map doctests) {
            this.doctests = doctests;
        }
        
    }
    
    /**
     * A container for the doctest data (request, response, javadoc).
     */
    public class DoctestData {
        
        protected RequestResultWrapper request;
        protected ResponseResultWrapper response;
        protected String javaDoc = "";
        protected String name;
        
        public String getName() {
            return name;
        }
        
        public void setName(String name) {
            this.name = name;
        }
        
        public String getJavaDoc() {
            return javaDoc;
        }
        
        public void setJavaDoc(String javaDoc) {
            this.javaDoc = javaDoc;
        }
        
        public RequestResultWrapper getRequest() {
            return request;
        }
        
        public void setRequest(RequestResultWrapper request) {
            this.request = request;
        }
        
        public ResponseResultWrapper getResponse() {
            return response;
        }
        
        public void setResponse(ResponseResultWrapper response) {
            this.response = response;
        }
        
    }
    
    /**
     * Maven Internal: The Doxia Site Renderer.
     * 
     * @component
     * @required
     * @readonly
     */
    private Renderer siteRenderer;
    
    /**
     * Maven Internal: The Project descriptor.
     * 
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    private MavenProject project;
    
    /**
     * The number of characters that can be seen without hitting the "more details" button.
     * 
     * @parameter expression="${project.reporting.doctests.maxPreview}" default-value="128"
     */
    private int maxPreview = 128;
    
    /**
     * the java back-store which has the information where the result are situated.
     */
    protected Preferences prefs = Preferences.userNodeForPackage(DoctestRunner.class);
    /**
     * the report is sorted by endpoint, this map holds them.
     */
    protected Map endpoints = new TreeMap();
    /**
     * the json mapper used to read the doctest results.
     */
    protected ObjectMapper mapper = new ObjectMapper();
    
    public int getMaxPreview() {
        return maxPreview;
    }
    
    public void setMaxPreview(int maxPreview) {
        this.maxPreview = maxPreview;
    }
    
    @Override
    protected String getOutputDirectory() {
        return project.getReporting().getOutputDirectory();
    }
    
    @Override
    public Renderer getSiteRenderer() {
        return siteRenderer;
    }
    
    @Override
    protected MavenProject getProject() {
        return project;
    }
    
    @Override
    public String getOutputName() {
        return "doctests/index";
    }
    
    @Override
    public String getName(Locale locale) {
        return getBundle(locale).getString("name");
    }
    
    @Override
    public String getDescription(Locale locale) {
        return getBundle(locale).getString("description");
    }
    
    protected ResourceBundle getBundle(Locale locale) {
        return ResourceBundle.getBundle("doctest", locale, this.getClass().getClassLoader());
    }
    
    public void setSiteRenderer(Renderer siteRenderer) {
        this.siteRenderer = siteRenderer;
    }
    
    public void setProject(MavenProject project) {
        this.project = project;
    }
    
    /**
     * Parses and renders the doctest results using {@link #parseDoctestResults(File, String)} and {@link #renderDoctestResults(Locale)}.
     */
    @Override
    protected void executeReport(Locale locale) throws MavenReportException {
        File dir;
        File results = null;
        String doctestResults = "";
        
        try {
            prefs.sync();
            doctestResults = prefs.get(ReportingCollector.RESULT_PATH, "");
            results = new File(doctestResults);
            prefs.removeNode();
        } catch (BackingStoreException exception) {
            getLog().error("error while getting settings", exception);
        }
        
        if (!(dir = new File(project.getReporting().getOutputDirectory() + File.separator + "doctests")).exists()) {
            dir.mkdirs();
        }
        
        if (results != null && results.exists()) {
            parseDoctestResults(results, doctestResults);
            try {
                renderDoctestResults(locale);
            } catch (RendererException exception) {
                getLog().error("error while rendernig doctests.", exception);
            }
        }
    }
    
    /**
     * renders the basic scaffolding via the {@link Sink}. the actual report rendering is done via {@link #renderReport(Sink, Locale)}.
     */
    protected void renderDoctestResults(Locale locale) throws RendererException {
        Sink sink = getSink();
        
        sink.head();
        sink.title();
        sink.text(getBundle(locale).getString("header.title"));
        sink.title_();
        sink.head_();
        
        sink.body();
        sink.rawText(JAVASCRIPT_CODE);
        renderReport(sink, locale);
        sink.body_();
        
        sink.flush();
    }
    
    /**
     * Iterates through all enpoints and renders all doctest method for each endpoint.
     */
    protected void renderReport(Sink sink, Locale locale) {
        AtomicInteger counter = new AtomicInteger();
        String requestLabel = escapeToHtml(getBundle(locale).getString("request.header"));
        String responseLabel = escapeToHtml(getBundle(locale).getString("response.header"));
        String detailLabel = escapeToHtml(getBundle(locale).getString("detail.label"));
        
        sink.section1();
        sink.sectionTitle1();
        sink.text(escapeToHtml(getBundle(locale).getString("toc.title")));
        sink.sectionTitle1_();
        
        sink.list();
        for (Map.Entry endpoint : endpoints.entrySet()) {
            sink.listItem();
            sink.anchor(endpoint.getKey());
            sink.text(endpoint.getKey());
            sink.anchor_();
            sink.listItem_();
        }
        sink.list_();
        
        for (Map.Entry endpoint : endpoints.entrySet()) {
            sink.section2();
            sink.sectionTitle2();
            sink.text(endpoint.getKey());
            sink.sectionTitle2_();
            
            for (Map.Entry doctest : endpoint.getValue().getDoctests().entrySet()) {
                sink.section3();
                sink.sectionTitle3();
                sink.text(doctest.getKey());
                sink.sectionTitle3_();
                
                if (!StringUtils.isEmpty(doctest.getValue().getJavaDoc())) {
                    sink.verbatim(SinkEventAttributeSet.BOXED);
                    sink.rawText(doctest.getValue().getJavaDoc());
                    sink.verbatim_();
                }
                
                sink.table();
                
                sink.tableRow();
                sink.tableCell(TABLE_CELL_STYLE_ATTRIBUTES);
                sink.bold();
                sink.text(requestLabel);
                sink.bold_();
                sink.tableCell_();
                sink.tableCell();
                renderRequestCell(sink, doctest.getValue().getRequest(), counter, detailLabel);
                sink.tableCell_();
                sink.tableRow_();
                
                sink.tableRow();
                sink.tableCell(TABLE_CELL_STYLE_ATTRIBUTES);
                sink.bold();
                sink.text(responseLabel);
                sink.bold_();
                sink.tableCell_();
                sink.tableCell();
                renderResponseCell(sink, doctest.getValue().getResponse(), counter, detailLabel);
                sink.tableCell_();
                sink.tableRow_();
                
                sink.table_();
                sink.section3_();
            }
            sink.section2_();
        }
        sink.section1_();
    }
    
    /**
     * Renders the request cell in the table
     */
    protected void renderRequestCell(Sink sink, RequestResultWrapper wrapper, AtomicInteger counter, String details) {
        StringBuilder builder = new StringBuilder();
        String preview;
        int id = counter.incrementAndGet();
        
        builder.append(wrapper.getRequestLine());
        builder.append("
"); builder.append(""); builder.append(details); builder.append("
"); sink.rawText(builder.toString()); builder.delete(0, builder.length()); preview = wrapper.getEntity(); if (!StringUtils.isEmpty(wrapper.getEntity()) && wrapper.getEntity().length() <= maxPreview) { preview = wrapper.getEntity(); } else if (!StringUtils.isEmpty(wrapper.getEntity())) { preview = wrapper.getEntity().substring(0, maxPreview) + "…"; } if (!StringUtils.isEmpty(wrapper.getEntity())) { sink.verbatim(SinkEventAttributeSet.BOXED); sink.rawText(preview); sink.verbatim_(); } builder.append("
"); builder.append("
"); sink.rawText(builder.toString()); builder.delete(0, builder.length()); if (wrapper.getHeader() != null && wrapper.getHeader().length > 0) { sink.verbatim(SinkEventAttributeSet.BOXED); for (String header : wrapper.getHeader()) { sink.rawText(header); sink.rawText("
"); } sink.verbatim_(); } if (wrapper.getParemeters() != null && wrapper.getParemeters().length > 0) { sink.verbatim(SinkEventAttributeSet.BOXED); for (String parameter : wrapper.getParemeters()) { sink.rawText(parameter); sink.rawText("
"); } sink.verbatim_(); } if (!StringUtils.isEmpty(wrapper.getEntity())) { sink.verbatim(SinkEventAttributeSet.BOXED); sink.rawText(wrapper.getEntity()); sink.verbatim_(); } sink.rawText("
"); } /** * Renders the response cell in the table. */ protected void renderResponseCell(Sink sink, ResponseResultWrapper wrapper, AtomicInteger counter, String details) { StringBuilder builder = new StringBuilder(); String preview; int id = counter.incrementAndGet(); builder.append(wrapper.getStatusLine()); builder.append("
"); builder.append(""); builder.append(details); builder.append("
"); sink.rawText(builder.toString()); builder.delete(0, builder.length()); preview = wrapper.getEntity(); if (!StringUtils.isEmpty(wrapper.getEntity()) && wrapper.getEntity().length() <= maxPreview) { preview = wrapper.getEntity(); } else if (!StringUtils.isEmpty(wrapper.getEntity())) { preview = wrapper.getEntity().substring(0, maxPreview) + "…"; } if (!StringUtils.isEmpty(wrapper.getEntity())) { sink.verbatim(SinkEventAttributeSet.BOXED); sink.rawText(preview); sink.verbatim_(); } builder.append("
"); builder.append("
"); sink.rawText(builder.toString()); builder.delete(0, builder.length()); if (wrapper.getHeader() != null && wrapper.getHeader().length > 0) { sink.verbatim(SinkEventAttributeSet.BOXED); for (String header : wrapper.getHeader()) { sink.rawText(header); sink.rawText("
"); } sink.verbatim_(); } if (wrapper.getParemeters() != null && wrapper.getParemeters().length > 0) { sink.verbatim(SinkEventAttributeSet.BOXED); for (String parameter : wrapper.getParemeters()) { sink.rawText(parameter); sink.rawText("
"); } sink.verbatim_(); } if (!StringUtils.isEmpty(wrapper.getEntity())) { sink.verbatim(SinkEventAttributeSet.BOXED); sink.rawText(wrapper.getEntity()); sink.verbatim_(); } sink.rawText("
"); } /** * Gets the doctest results and transforms them into {@link DoctestsContainer} objects. */ protected void parseDoctestResults(File doctestResultDirectory, String doctestResultDirectoryName) { String tmp; String className; String doctestName; String source; DoctestsContainer endpoint; DoctestData doctest; RequestResultWrapper requestResult; ResponseResultWrapper responseResult; for (File resultFile : doctestResultDirectory.listFiles()) { tmp = resultFile.getAbsolutePath(); if (tmp.endsWith(".request")) { tmp = tmp.substring(0, tmp.lastIndexOf('.')); className = tmp.substring(doctestResultDirectoryName.length(), tmp.indexOf('-', doctestResultDirectoryName.length())); className = className.replaceAll("\\.", "/") + ".java"; doctestName = tmp.substring(tmp.indexOf('-', doctestResultDirectoryName.length()) + 1); try { requestResult = mapper.readValue(new File(tmp + ".request"), RequestResultWrapper.class); responseResult = mapper.readValue(new File(tmp + ".response"), ResponseResultWrapper.class); source = FileUtils .readFileToString(new File(project.getBuild().getTestSourceDirectory(), className)); tmp = tmp.substring(doctestResultDirectoryName.length()).replace('-', '.'); endpoint = endpoints.get(requestResult.getPath()); if (endpoint == null) { endpoint = new DoctestsContainer(); endpoint.setName(requestResult.getPath()); endpoints.put(requestResult.getPath(), endpoint); } requestResult.setEntity(escapeToHtml(requestResult.getEntity())); requestResult.setPath(escapeToHtml(requestResult.getPath())); requestResult.setRequestLine(escapeToHtml(requestResult.getRequestLine())); requestResult.setHeader(escapeToHtml(requestResult.getHeader())); requestResult.setParemeters(escapeToHtml(requestResult.getParemeters())); responseResult.setEntity(escapeToHtml(responseResult.getEntity())); responseResult.setStatusLine(escapeToHtml(responseResult.getStatusLine())); responseResult.setHeader(escapeToHtml(responseResult.getHeader())); responseResult.setParemeters(escapeToHtml(responseResult.getParemeters())); doctest = new DoctestData(); doctest.setJavaDoc(getJavaDoc(source, doctestName)); doctest.setName(tmp); doctest.setRequest(requestResult); doctest.setResponse(responseResult); endpoint.getDoctests().put(tmp, doctest); } catch (IOException exception) { getLog().error("error while reading doctest request", exception); } } } } /** * Gets the javadoc comment situated over a doctest method. */ protected String getJavaDoc(String source, String method) { Pattern methodPattern = Pattern.compile("public\\s+void\\s+" + method + "\\s*\\((HttpResponse|" + HttpResponse.class.getName().replaceAll("\\.", "\\\\.") + ")", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); Matcher matcher = methodPattern.matcher(source); int start, tmp, last, comment; String doc; if (matcher.find()) { start = matcher.start(); last = -1; matcher = ANY_METHOD_FINDER.matcher(source); while (matcher.find() && (tmp = matcher.start()) < start) { last = tmp; } comment = source.lastIndexOf("/**", start); if (comment > 2 && (comment > last || last == -1)) { doc = source.substring(comment, source.indexOf("*/", comment)); doc = doc.substring(3, doc.length() - 2); doc = JAVADOC_EMPTYLINE_FINDER.matcher(doc).replaceAll(LINE_SEPARATOR); doc = JAVADOC_STAR_FINDER.matcher(doc).replaceAll(""); doc = StringUtils.replace(doc, " ", " "); doc = StringUtils.replace(doc, LINE_SEPARATOR, "
"); return doc; } } return ""; } /** * Escapes an array of strings. */ protected String[] escapeToHtml(String[] texts) { for (int i = 0; i < texts.length; i++) { texts[i] = escapeToHtml(texts[i]); } return texts; } /** * Escapes a single string. */ protected String escapeToHtml(String text) { return StringUtils.replace(StringUtils.replace(HtmlTools.escapeHTML(text, false), "&#", "&#"), LINE_SEPARATOR, "
"); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy