Java tutorial
/** * 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. } }