com.modusoperandi.dragonfly.widgets.table.TableWidgetPage.java Source code

Java tutorial

Introduction

Here is the source code for com.modusoperandi.dragonfly.widgets.table.TableWidgetPage.java

Source

/**
 * Copyright (c) 2016 Modus Operandi, Inc.
 *
 * This is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details. A copy of the GNU Lesser General Public License is distributed along
 * with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package com.modusoperandi.dragonfly.widgets.table;

import com.carrotsearch.hppc.ObjectLookupContainer;
import com.google.common.collect.UnmodifiableIterator;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AbstractAjaxTimerBehavior;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.request.IRequestParameters;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.handler.TextRequestHandler;
import org.apache.wicket.util.time.Duration;
import org.apache.wicket.util.time.Time;
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
import org.elasticsearch.action.count.CountResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortOrder;

/**
 * The Class TableWidgetPage - renders a table display
 */
public class TableWidgetPage extends WebPage {

    private static final Logger LOGGER = Logger.getLogger(TableWidgetPage.class.getName());

    public class TablePopulateAjaxBehavior extends AbstractDefaultAjaxBehavior {

        private static final long serialVersionUID = 1L;

        @Override
        public void renderHead(final Component component, final IHeaderResponse response) {

            super.renderHead(component, response);
            response.render(OnDomReadyHeaderItem.forScript(jqgridEmptyJs()));
        }

        private void setSorting(final String sidx, final String sord) {

            sortField = sidx;

            if (sord.equals("desc")) {
                sortOrder = "Desc";
            } else {
                sortOrder = "Asc";
            }
        }

        @Override
        protected void respond(final AjaxRequestTarget target) {

            // Parse the query
            final IRequestParameters parameters = getRequest().getRequestParameters();
            pageSize = Integer.parseInt(parameters.getParameterValue("rows").toString("10"));
            page = Integer.parseInt(parameters.getParameterValue("page").toString("10"));
            setSorting(parameters.getParameterValue("sidx").toString(""),
                    parameters.getParameterValue("sord").toString(""));

            // Make query
            final SearchResponse results = esMakeQuery();

            // Build JSON response
            final StringBuilder jsonResponse = new StringBuilder();

            jsonResponse.append("{\n");
            jsonResponse.append("   \"currentpage\":\"");
            jsonResponse.append(parameters.getParameterValue("page"));
            jsonResponse.append("\",\n");
            jsonResponse.append("   \"totalpages\":\"");
            jsonResponse.append(Long.toString((results.getHits().getTotalHits() / pageSize) + 1));
            jsonResponse.append("\",\n");
            jsonResponse.append("   \"totalrecords\":\"");
            jsonResponse.append(Long.toString(results.getHits().getTotalHits()));
            jsonResponse.append("\",\n");
            jsonResponse.append("   \"rows\":[\n");

            long rowId = 0;
            boolean firstRow = true;
            for (final SearchHit hit : results.getHits()) {
                if (!firstRow) {
                    jsonResponse.append(",\n");
                }
                firstRow = false;

                jsonResponse.append("      {\n");
                jsonResponse.append("         \"id\":\"");
                jsonResponse.append(Long.toString(rowId++));
                jsonResponse.append("\",\n");

                boolean firstField = true;
                for (final Map.Entry<String, Object> field : hit.getSource().entrySet()) {
                    if (!firstField) {
                        jsonResponse.append(",\n");
                    }
                    firstField = false;

                    jsonResponse.append("\"");
                    jsonResponse.append(field.getKey());
                    jsonResponse.append("\" : ");

                    jsonResponse.append("\"");
                    jsonResponse.append(StringEscapeUtils
                            .escapeJson(StringEscapeUtils.escapeXml11(field.getValue().toString())));
                    jsonResponse.append("\"");
                }

                jsonResponse.append("      }\n");
            }

            jsonResponse.append("   ]\n");
            jsonResponse.append("}");

            final TextRequestHandler textRequestHandler = new TextRequestHandler("application/json", "UTF-8",
                    jsonResponse.toString());
            RequestCycle.get().scheduleRequestHandlerAfterCurrent(textRequestHandler);
        }
    }

    private transient static Client esClient = null;
    private static final int ONE_DAY_IN_SECONDS = 86400;
    private static final long serialVersionUID = 1L;

    /**
     * Callback for when the page is destroyed. Closes the Elasticsearch client.
     */
    public static void onDestroy() {

        if (esClient != null) {
            esClient.close();
            esClient = null;
        }
    }

    /**
     * Retrieves the Elasticsearch index mapping for the specified index
     *
     * @param indexName
     *            the index name
     * @return the index mapping
     */
    private static ImmutableOpenMap<String, MappingMetaData> esGetMappings(final String indexName) {

        final ClusterStateResponse clusterStateResponse = getEsClient().admin().cluster().prepareState().execute()
                .actionGet();

        return clusterStateResponse.getState().getMetaData().index(indexName).getMappings();
    }

    /**
     * Gets the Elasticsearch client.
     *
     * @return the Elasticsearch client
     */
    private static Client getEsClient() {

        // ////////////
        // Remote
        if (esClient == null) {
            try {
                final Settings settings = Settings.settingsBuilder()
                        .put("node.name", "DRAGONFLY_" + Long.toString(Thread.currentThread().getId()))
                        .put("cluster.name", "DRAGONFLY").build();
                esClient = new TransportClient.Builder().settings(settings).build().addTransportAddress(
                        new InetSocketTransportAddress(InetAddress.getByName("localhost"), 9300));
            } catch (UnknownHostException ex) {
                LOGGER.log(Level.SEVERE, null, ex);
            }
        }

        return esClient;
    }

    /**
     * Checks if the specified field is a link based on the Elasticsearch index mapping metadata
     *
     * @param field
     *            the field to check
     * @param meta
     *            the index mapping metadata
     * @return true, if is link
     */
    private static boolean isLink(final String fieldName, final Map<String, Object> meta) {

        String fieldInterface = "";

        if (meta != null) {
            for (final Entry<String, Object> entry : meta.entrySet()) {
                if (entry.getKey().equals("Fields")) {
                    // This is a field descriptor
                    try {
                        final ArrayList<Map<String, Object>> descriptors = (ArrayList<Map<String, Object>>) entry
                                .getValue();
                        for (final Map<String, Object> field : descriptors) {
                            if (field.get("Name").equals(fieldName)) {
                                fieldInterface = (String) field.get("Extended Type");
                            }
                        }
                    } catch (final Exception e) {
                        // The meta data is a JSON object created by a query widget converted to a map of maps.
                        // If the conventions were not followed, parsing will fail in many possible ways.
                        // If parsing fails, this field will be treated as a normal field

                        LOGGER.log(Level.SEVERE, e.getMessage(), e);
                    }
                }
            }
        }

        return fieldInterface.equals("URL");
    }

    private static String jqgridUpdateJs() {

        return "jqgridUpdateJs();";

    }

    private String facetField = null;
    private String facetValue = null;
    private final List<String> fieldList = new LinkedList<>();
    private String filter;
    private final List<String> indexList = new LinkedList<>();
    private String indexName;
    private long lastCount;
    private int page = 1;
    private int pageSize = 10;
    private final TablePopulateAjaxBehavior populateTableBehavior;
    private final DropDownChoice<?> selectFacetField;
    private final DropDownChoice<?> selectFacetValue;
    private final DropDownChoice<?> selectIndex;
    private String sortField = "";
    private String sortOrder;
    private final List<String> valueList = new LinkedList<>();

    /**
     * Instantiates a new table widget page.
     */
    public TableWidgetPage() {

        // /////////////////////////////
        // Elasticsearch
        getEsClient();
        sortOrder = "Asc";

        // /////////////////////////////
        // UI
        setVersioned(false);

        final Form<?> queryForm = new Form<>("queryForm");
        add(queryForm);

        // ////////////////////////////
        // Index Name
        populateIndices();

        selectIndex = new DropDownChoice<>("dataSetName", new PropertyModel<String>(this, "indexName"), indexList);
        selectIndex.add(new AjaxFormComponentUpdatingBehavior("focus") {

            private static final long serialVersionUID = 2981822623630720652L;

            @Override
            protected void onUpdate(final AjaxRequestTarget target) {

                if (populateIndices()) {
                    target.add(selectIndex);
                }
            }
        });
        selectIndex.add(new AjaxFormComponentUpdatingBehavior("change") {

            private static final long serialVersionUID = 2981822623630720652L;

            @Override
            protected void onUpdate(final AjaxRequestTarget target) {

                resetFacets();

                target.add(selectFacetField);
                target.add(selectFacetValue);
                target.appendJavaScript(jqgridUpdateJs());
                target.appendJavaScript(jqgridPopulatedJs());
            }
        });
        queryForm.add(selectIndex);

        // ////////////////////////////
        // Filter
        final TextField<String> filterField = new TextField<>("filter", new PropertyModel<String>(this, "filter"));
        filterField.add(new OnChangeAjaxBehavior() {

            private static final long serialVersionUID = 2981822623630720652L;

            @Override
            protected void onUpdate(final AjaxRequestTarget target) {

                target.appendJavaScript(jqgridUpdateJs());
                target.appendJavaScript(jqgridPopulatedJs());
            }
        });

        queryForm.add(filterField);

        // ////////////////////////////
        // Facet
        selectFacetField = new DropDownChoice<>("facetField", new PropertyModel<String>(this, "facetField"),
                fieldList);

        selectFacetField.add(new OnChangeAjaxBehavior() {

            private static final long serialVersionUID = 2981822623630720652L;

            @Override
            protected void onUpdate(final AjaxRequestTarget target) {

                populateFacet(esMakeQuery());

                target.add(selectFacetValue);
                target.appendJavaScript(jqgridUpdateJs());
                target.appendJavaScript(jqgridPopulatedJs());
            }
        });
        queryForm.add(selectFacetField);

        selectFacetValue = new DropDownChoice<>("facetValue", new PropertyModel<String>(this, "facetValue"),
                valueList);

        selectFacetValue.add(new OnChangeAjaxBehavior() {

            private static final long serialVersionUID = 2981822623630720652L;

            @Override
            protected void onUpdate(final AjaxRequestTarget target) {

                target.appendJavaScript(jqgridUpdateJs());
                target.appendJavaScript(jqgridPopulatedJs());
            }
        });
        queryForm.add(selectFacetValue);

        // //////////////////////////////
        // Table
        populateTableBehavior = new TablePopulateAjaxBehavior();
        add(populateTableBehavior);

        // //////////////////////////////
        // Updates
        add(new AbstractAjaxTimerBehavior(Duration.seconds(1)) {

            private static final long serialVersionUID = 1L;

            /**
             * @see org.apache.wicket.ajax.AbstractAjaxTimerBehavior#onTimer(org.apache.wicket.ajax.AjaxRequestTarget)
             */
            @Override
            protected void onTimer(final AjaxRequestTarget target) {

                if (indexName != null) {
                    final IndicesExistsResponse exists = getEsClient().admin().indices()
                            .exists(new IndicesExistsRequest(indexName)).actionGet();

                    if (exists.isExists()) {
                        final CountResponse response = getEsClient().prepareCount(indexName).execute().actionGet();

                        if (response.getCount() != lastCount) {

                            System.out
                                    .println(Long.toString(lastCount) + " : " + Long.toString(response.getCount()));

                            lastCount = response.getCount();

                            target.appendJavaScript(jqgridUpdateJs());
                            target.appendJavaScript(jqgridPopulatedJs());
                        }
                    }
                }
            }
        });
    }

    private SearchResponse esMakeQuery() {

        final SearchRequestBuilder search = getEsClient().prepareSearch(indexName);
        search.setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
        search.setFrom(pageSize * (page - 1));
        search.setSize(pageSize);

        QueryBuilder scriptQuery;

        if ((filter != null) && !filter.trim().equals("")) {
            // scriptQuery = QueryBuilders.matchQuery("_all", filter.trim());
            scriptQuery = QueryBuilders.scriptQuery(new Script(filter));
        } else {
            scriptQuery = QueryBuilders.matchAllQuery();

        }

        if ((sortField != null) && !sortField.trim().equals("")) {
            search.addSort(sortField, getSortOrder());
        }

        QueryBuilder facetQuery = QueryBuilders.matchAllQuery();

        if ((facetField != null) && !facetField.trim().equals("")) {
            search.addAggregation(AggregationBuilders.terms("f").field(facetField).size(100));

            if (facetValue != null) {
                facetQuery = QueryBuilders.matchQuery(facetField,
                        facetValue.substring(0, facetValue.lastIndexOf(" (")));
            }
        }

        search.setQuery(QueryBuilders.boolQuery().must(scriptQuery).must(facetQuery));

        return search.execute().actionGet();
    }

    private SortOrder getSortOrder() {

        if (sortOrder.equals("Asc")) {
            return SortOrder.ASC;
        }

        return SortOrder.DESC;
    }

    private String jqgridEmptyJs() {

        final StringBuilder gridString = new StringBuilder();

        gridString.append("jQuery(\"#resultsTable\").jqGrid({ \n");
        gridString.append("   datatype: 'clientSide', \n");
        gridString.append("   colNames:['No Data'], \n");
        gridString.append("   colModel:[ {name:'nodata', index:'nodata', width:100} ], \n");
        gridString.append("   rowNum: ").append(pageSize).append(", \n");
        gridString.append("   pager: '#resultsPager', \n");
        gridString.append("   autowidth: true,\n");
        gridString.append("   height: '100%',\n");
        gridString.append("}); \n");
        gridString.append("\n");
        gridString.append(
                "jQuery(\"#resultsList\").jqGrid('navGrid','#resultsPager',{edit:false,add:false,del:false});");

        return gridString.toString();
    }

    private String jqgridPopulatedJs() {

        final StringBuilder response = new StringBuilder();

        try {
            final SearchResponse queryResponse = esMakeQuery();

            final ImmutableOpenMap<String, MappingMetaData> typeMappings = esGetMappings(indexName);

            final StringBuilder colNames = new StringBuilder();
            final StringBuilder colModel = new StringBuilder();
            colNames.append("[");
            colModel.append("[");

            boolean firstTime = true;
            final UnmodifiableIterator<MappingMetaData> it = typeMappings.valuesIt();
            while (it.hasNext()) {
                final MappingMetaData mapping = it.next();

                final Map<String, Object> meta = ((Map<String, Object>) mapping.getSourceAsMap().get("_meta"));
                final Map<String, Object> fields = ((Map<String, Object>) mapping.getSourceAsMap()
                        .get("properties"));

                for (final String y : fields.keySet()) {

                    if (!firstTime) {
                        colNames.append(",");
                        colModel.append(",\n");
                    }
                    firstTime = false;

                    colNames.append("'");
                    colNames.append(y);
                    colNames.append("'");

                    fieldList.add(y);

                    colModel.append("{name:'");
                    colModel.append(y);
                    colModel.append("', index:'");
                    colModel.append(y);

                    colModel.append("', formatter: function(cellvalue, options, rowObject) { \n");
                    if (isLink(y, meta)) {
                        colModel.append(
                                "       return '<a style=\"white-space: nowrap;\" href=\"' + cellvalue + '\">");
                        colModel.append(y);
                        colModel.append("</div>';\n");
                    } else {
                        colModel.append(
                                "       return '<div style=\"white-space: nowrap;\">' + cellvalue + '</div>';\n");
                    }
                    colModel.append("   }\n");
                    colModel.append("}");
                }
            }

            colNames.append("]");
            colModel.append("]");

            response.append("populateTableBehaviorUrl = '").append(populateTableBehavior.getCallbackUrl())
                    .append("';");
            response.append("pageSize = ").append(pageSize).append(";");
            response.append("colNames = ").append(colNames.toString()).append(";");
            response.append("colModel = ").append(colModel.toString()).append(";");
            response.append("jqGridPopulated();");

            return response.toString();

        } catch (final Exception e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);

            return jqgridEmptyJs();
        }
    }

    private void populateFacet(final SearchResponse response) {

        facetValue = null;
        valueList.clear();

        if (response.getAggregations() != null) {
            Terms f = response.getAggregations().get("f");
            Collection<Terms.Bucket> buckets = f.getBuckets();

            buckets.stream().forEach((bucket) -> {
                valueList.add(bucket.getKeyAsString() + " (" + Long.toString(bucket.getDocCount()) + ")");
            });
        }
    }

    private boolean populateIndices() {

        ObjectLookupContainer<String> mapAl = getEsClient().admin().cluster().prepareState().execute().actionGet()
                .getState().getMetaData().indices().keys();
        final Object keyAry[] = mapAl.toArray();

        boolean changed = (indexList.size() != keyAry.length);
        int i = 0;
        while ((i < indexList.size()) && (i < keyAry.length) && !changed) {
            changed = !indexList.get(i).equals(keyAry[i]);
            i++;
        }

        if (changed) {
            indexList.clear();
            for (final Object index : keyAry) {
                indexList.add(index.toString());
            }
        }

        return changed;
    }

    private void resetFacets() {

        fieldList.clear();
        facetField = null;
        valueList.clear();
        facetValue = null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.wicket.markup.html.WebPage#onAfterRender()
     */
    @Override
    protected void onAfterRender() {

        super.onAfterRender();

        // Increase the HTTP session timeout to 1 day
        ((HttpServletRequest) RequestCycle.get().getRequest().getContainerRequest()).getSession()
                .setMaxInactiveInterval(ONE_DAY_IN_SECONDS);
    }

    protected @Override void setHeaders(final org.apache.wicket.request.http.WebResponse response) {

        super.setHeaders(response);

        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
        response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
        response.setDateHeader("Expires", Time.START_OF_UNIX_TIME); // Proxies.
    }
}