/* * Copyright 2000-2013 Enonic AS * http://www.enonic.com/license */ package com.enonic.cms.core.tools.index; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus; import org.elasticsearch.action.admin.indices.status.IndexStatus; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.collect.Maps; import org.joda.time.Period; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.PeriodFormatter; import org.joda.time.format.PeriodFormatterBuilder; import org.springframework.beans.factory.annotation.Autowired; import com.google.common.collect.Lists; import com.enonic.cms.core.content.ContentSpecification; import com.enonic.cms.core.search.ElasticSearchIndexService; import com.enonic.cms.core.search.IndexType; import com.enonic.cms.core.tools.AbstractToolController; import com.enonic.cms.store.dao.ContentDao; public final class IndexMonitorController extends AbstractToolController { protected static final PeriodFormatter HOURS_MINUTES_MILLIS = new PeriodFormatterBuilder(). appendHours().appendSuffix( " h ", " h " ). appendMinutes().appendSuffix( " m ", " m " ). appendSeconds().appendSuffix( " s ", " s " ). appendMillis().appendSuffix( " ms", " ms" ). toFormatter(); private static final DateTimeFormatter SIMPLE_DATE_FORMAT = DateTimeFormat.forPattern( "yyyy-MM-dd HH:mm" ); private static final String GET_ALL_QUERY = "{\n" + " \"from\" : 0,\n" + " \"size\" : 0,\n" + " \"query\" : {\n" + " \"match_all\" : {\n" + " }\n" + " }\n" + "}\n" + ""; public static final String NOT_APPLICABLE = "N/A"; private ElasticSearchIndexService elasticSearchIndexService; private ReindexContentToolService reindexContentToolService; private ContentDao contentDao; @Override protected void doGet( final HttpServletRequest req, final HttpServletResponse res ) throws Exception { final Map<String, Object> model = Maps.newHashMap(); model.put( "baseUrl", getBaseUrl( req ) ); final String op = req.getParameter( "op" ); if ( !"info".equals( op ) ) { renderView( req, res, model, "indexMonitorPage" ); return; } List<String> errors = Lists.newArrayList(); populateClusterHealth( model, errors ); populateReindexInfo( model, errors ); final ContentSpecification specification = new ContentSpecification(); specification.setIncludeDeleted( false ); final long totalNumberOfContent = contentDao.findCountBySpecification( specification ); model.put( "totalNumberOfContent", totalNumberOfContent ); if ( !errors.isEmpty() ) { model.put( "errors", errors ); } renderView( req, res, model, "indexMonitorPage_info" ); } private void populateClusterHealth( final Map<String, Object> model, final List<String> errors ) { final ClusterHealthResponse clusterHealthResponse = elasticSearchIndexService.getClusterHealth( "cms", false ); populateInfoFromIndexStatus( model, errors ); if ( clusterHealthResponse == null ) { errors.add( "Not able to get clusterHealthResponse" ); model.put( "activeShards", NOT_APPLICABLE ); model.put( "clusterStatus", NOT_APPLICABLE ); model.put( "relocatingShards", NOT_APPLICABLE ); model.put( "activePrimaryShards", NOT_APPLICABLE ); model.put( "numberOfNodes", NOT_APPLICABLE ); model.put( "unassignedShards", NOT_APPLICABLE ); model.put( "indexExists", NOT_APPLICABLE ); model.put( "numberOfContent", NOT_APPLICABLE ); model.put( "numberOfBinaries", NOT_APPLICABLE ); } else { model.put( "activeShards", clusterHealthResponse.getActiveShards() ); final ClusterHealthStatus status = clusterHealthResponse.getStatus(); model.put( "clusterStatus", status.toString() ); model.put( "relocatingShards", clusterHealthResponse.getRelocatingShards() ); model.put( "activePrimaryShards", clusterHealthResponse.getActivePrimaryShards() ); model.put( "numberOfNodes", clusterHealthResponse.getNumberOfNodes() ); model.put( "unassignedShards", clusterHealthResponse.getUnassignedShards() ); final List<String> allValidationFailures = clusterHealthResponse.getAllValidationFailures(); if ( !allValidationFailures.isEmpty() ) { model.put( "validationFailures", allValidationFailures ); } final boolean indexExists = elasticSearchIndexService.indexExists( "cms" ); model.put( "indexExists", indexExists ); model.put( "numberOfContent", getTotalHitsContent( status ) ); model.put( "numberOfBinaries", getTotalHitsBinaries( status ) ); } } private void populateInfoFromIndexStatus( final Map<String, Object> model, final List<String> errors ) { final IndexStatus indexStatus = elasticSearchIndexService.getIndexStatus( "cms" ); if ( indexStatus == null ) { errors.add( "Not able to get indexStatus for index 'cms'" ); model.put( "numberOfDocuments", NOT_APPLICABLE ); model.put( "primaryStorageSize", NOT_APPLICABLE ); model.put( "totalStorageSize", NOT_APPLICABLE ); } else { model.put( "numberOfDocuments", indexStatus.getDocs() != null ? indexStatus.getDocs().getNumDocs() : "-1" ); model.put( "primaryStorageSize", indexStatus.getPrimaryStoreSize() ); model.put( "totalStorageSize", indexStatus.getStoreSize() ); } } private void populateReindexInfo( final Map<String, Object> model, final List<String> errors ) { model.put( "reindexInProgress", reindexContentToolService.isReIndexInProgress() ); model.put( "reindexError", reindexContentToolService.isLastReindexFailed() ); if ( reindexContentToolService.getLastReindexTime() != null ) { model.put( "lastReindexTime", getLastIndexTimeString() ); model.put( "lastReindexTimeUsed", getTimeUsedString() ); } } private String getLastIndexTimeString() { return SIMPLE_DATE_FORMAT.print( reindexContentToolService.getLastReindexTime() ); } private String getTimeUsedString() { final long lastReindexTimeUsed = reindexContentToolService.getLastReindexTimeUsed(); return HOURS_MINUTES_MILLIS.print( new Period( lastReindexTimeUsed ) ).trim(); } private long getTotalHitsBinaries( final ClusterHealthStatus status ) { if ( !status.equals( ClusterHealthStatus.RED ) ) { final SearchResponse response = elasticSearchIndexService.search( "cms", IndexType.Binaries.toString(), GET_ALL_QUERY ); return response.getHits().getTotalHits(); } else { return -1; } } private long getTotalHitsContent( final ClusterHealthStatus status ) { if ( !status.equals( ClusterHealthStatus.RED ) ) { final SearchResponse response = elasticSearchIndexService.search( "cms", IndexType.Content.toString(), GET_ALL_QUERY ); return response.getHits().getTotalHits(); } else { return -1; } } @Autowired public void setElasticSearchIndexService( ElasticSearchIndexService elasticSearchIndexService ) { this.elasticSearchIndexService = elasticSearchIndexService; } @Autowired public void setReindexContentToolService( final ReindexContentToolService reindexContentToolService ) { this.reindexContentToolService = reindexContentToolService; } @Autowired public void setContentDao( final ContentDao contentDao ) { this.contentDao = contentDao; } }