io.neba.core.resourcemodels.metadata.ModelMetadataConsolePlugin.java Source code

Java tutorial

Introduction

Here is the source code for io.neba.core.resourcemodels.metadata.ModelMetadataConsolePlugin.java

Source

/**
 * Copyright 2013 the original author or authors.
 * 
 * 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 io.neba.core.resourcemodels.metadata;

import org.apache.commons.collections.Predicate;
import org.apache.felix.webconsole.AbstractWebConsolePlugin;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONObject;
import org.springframework.stereotype.Service;

import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;

import static java.lang.Math.round;
import static org.apache.commons.collections.CollectionUtils.find;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.startsWith;
import static org.apache.commons.lang.StringUtils.substringAfter;

/**
 * Provides a RESTFul JSON API for {@link io.neba.api.annotations.ResourceModel} metadata,
 * i.e. the metadata collected at both registration and runtime. The metadata - in particular the
 * {@link ResourceModelStatistics} - is visualized by this console plugin on the client-side using D3.js.
 *
 * @author Olaf Otto
 */
@Service
public class ModelMetadataConsolePlugin extends AbstractWebConsolePlugin {
    public static final String LABEL = "modelmetadata";
    private static final long serialVersionUID = -8676958166611686979L;
    private static final String STATISTICS_API_PATH = "/api/statistics";
    private static final String RESET_API_PATH = "/api/reset";

    @Inject
    private ResourceModelMetaDataRegistrar modelMetaDataRegistrar;

    public String getCategory() {
        return "NEBA";
    }

    @Override
    public String getLabel() {
        return LABEL;
    }

    @Override
    public String getTitle() {
        return "Model metadata";
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        String suffix = substringAfter(req.getRequestURI(), req.getServletPath() + "/" + getLabel());
        if (!isBlank(suffix) && suffix.startsWith(STATISTICS_API_PATH)) {
            getModelMetadata(suffix.substring(STATISTICS_API_PATH.length()), res);
            return;
        }
        if (!isBlank(suffix) && suffix.startsWith(RESET_API_PATH)) {
            resetStatistics(res);
            return;
        }
        super.doGet(req, res);
    }

    private void resetStatistics(HttpServletResponse res) {
        for (ResourceModelMetaData metaData : this.modelMetaDataRegistrar.get()) {
            metaData.getStatistics().reset();
        }
        prepareJsonResponse(res);
        try {
            res.getWriter().write("{\"success\": true}");
        } catch (IOException e) {
            throw new RuntimeException("Unable to write success message after statistics cleared.", e);
        }
    }

    private void getModelMetadata(String typePath, HttpServletResponse res) {
        if (typePath.isEmpty()) {
            provideStatisticsOfAllModels(res);
        } else {
            String typeName = typePath.substring(1);
            provideStatisticsOfModel(typeName, res);
        }
    }

    private void provideStatisticsOfModel(final String typeName, HttpServletResponse res) {
        ResourceModelMetaData metaData = (ResourceModelMetaData) find(this.modelMetaDataRegistrar.get(),
                new Predicate() {
                    @Override
                    public boolean evaluate(Object object) {
                        return ((ResourceModelMetaData) object).getTypeName().equals(typeName);
                    }
                });

        if (metaData != null) {
            ResourceModelStatistics statistics = metaData.getStatistics();

            Map<String, Object> data = new LinkedHashMap<String, Object>();
            data.put("age", statistics.getSince());
            data.put("mappableFields", metaData.getMappableFields().length);
            data.put("instantiations", statistics.getInstantiations());
            data.put("mappings", statistics.getNumberOfMappings());
            data.put("averageMappingDuration", statistics.getAverageMappingDuration());
            data.put("maximumMappingDuration", statistics.getMaximumMappingDuration());
            data.put("minimumMappingDuration", statistics.getMinimumMappingDuration());
            data.put("mappingDurationMedian", statistics.getMappingDurationMedian());

            int[] mappingDurationFrequencies = statistics.getMappingDurationFrequencies();
            int[] intervalBoundaries = statistics.getMappingDurationIntervalBoundaries();

            try {
                JSONObject json = new JSONObject(data);
                JSONObject durationFrequencies = new JSONObject();

                int leftBoundary = 0;
                for (int i = 0; i < mappingDurationFrequencies.length; ++i) {
                    durationFrequencies.put("[" + leftBoundary + ", " + intervalBoundaries[i] + ")",
                            mappingDurationFrequencies[i]);
                    leftBoundary = intervalBoundaries[i];
                }

                json.put("mappingDurationFrequencies", durationFrequencies);

                prepareJsonResponse(res);
                json.write(res.getWriter());
            } catch (Exception e) {
                throw new RuntimeException("Unable to write the resource model JSON data.", e);
            }
        }
    }

    private void prepareJsonResponse(HttpServletResponse res) {
        res.setCharacterEncoding("UTF-8");
        res.setContentType("application/json; charset=UTF-8");
    }

    private void provideStatisticsOfAllModels(HttpServletResponse res) {
        JSONArray array = new JSONArray();
        for (ResourceModelMetaData metaData : this.modelMetaDataRegistrar.get()) {
            ResourceModelStatistics statistics = metaData.getStatistics();

            Map<String, Object> data = new LinkedHashMap<String, Object>();
            data.put("type", metaData.getTypeName());
            data.put("since", statistics.getSince());
            data.put("mappableFields", metaData.getMappableFields().length);
            data.put("instantiations", statistics.getInstantiations());
            data.put("mappings", statistics.getNumberOfMappings());
            data.put("averageMappingDuration", statistics.getAverageMappingDuration());
            data.put("maximumMappingDuration", statistics.getMaximumMappingDuration());
            data.put("minimumMappingDuration", statistics.getMinimumMappingDuration());
            data.put("mappingDurationMedian", statistics.getMappingDurationMedian());
            array.put(data);
        }
        try {
            prepareJsonResponse(res);
            array.write(res.getWriter());
        } catch (Exception e) {
            throw new RuntimeException("Unable to write the resource model JSON data.", e);
        }
    }

    @Override
    protected void renderContent(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
        writeHeadnavigation(res);
        writeBody(res);
    }

    public URL getResource(String path) {
        URL url = null;
        String internalPath = substringAfter(path, "/" + getLabel());
        if (startsWith(internalPath, "/static/")) {
            url = getClass().getResource("/META-INF/consoleplugin/modelmetadata" + internalPath);
        }
        return url;
    }

    private void writeHeadnavigation(HttpServletResponse response) throws IOException {
        int numberOfModelsWithInstantiations = 0;
        double highestMappingDuration = 0D;
        int highestNumberOfFields = 0;
        String nameOfModelWithHighestMappingDuration = "";
        String nameOfModelWithGreatestNumberOfFields = "";
        for (ResourceModelMetaData metaData : this.modelMetaDataRegistrar.get()) {
            ResourceModelStatistics statistics = metaData.getStatistics();
            if (statistics.getInstantiations() != 0) {
                ++numberOfModelsWithInstantiations;
                double maximumMappingDuration = statistics.getMaximumMappingDuration();
                if (maximumMappingDuration > highestMappingDuration) {
                    highestMappingDuration = maximumMappingDuration;
                    nameOfModelWithHighestMappingDuration = metaData.getTypeName();
                }
                int numberOfMappableFields = metaData.getMappableFields().length;
                if (numberOfMappableFields > highestNumberOfFields) {
                    highestNumberOfFields = numberOfMappableFields;
                    nameOfModelWithGreatestNumberOfFields = metaData.getTypeName();
                }
            }
        }

        String template = readTemplateFile("/META-INF/consoleplugin/modelmetadata/templates/head.html");
        response.getWriter().printf(template, numberOfModelsWithInstantiations, round(highestMappingDuration),
                nameOfModelWithHighestMappingDuration, highestNumberOfFields,
                nameOfModelWithGreatestNumberOfFields);
    }

    private void writeBody(HttpServletResponse response) throws IOException {
        String template = readTemplateFile("/META-INF/consoleplugin/modelmetadata/templates/plots.html");
        response.getWriter().print(template);
    }
}