package org.jboss.elasticsearch.river.jira; import java.io.IOException; import java.net.MalformedURLException; import java.text.ParseException; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetResponse; 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.cluster.node.DiscoveryNode; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.SettingsException; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.index.query.FilterBuilders; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.river.AbstractRiverComponent; import org.elasticsearch.river.River; import org.elasticsearch.river.RiverName; import org.elasticsearch.river.RiverSettings; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.sort.SortOrder; import org.jboss.elasticsearch.tools.content.StructuredContentPreprocessorFactory; import static org.elasticsearch.client.Requests.indexRequest; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; /** * JIRA River implementation class. * * @author Vlastimil Elias (velias at redhat dot com) */ public class JiraRiver extends AbstractRiverComponent implements River, IESIntegration, IJiraRiverMgm { public static final String DEFAULT_JQL_TEMPLATE = "project='%s'%s%s ORDER BY updated ASC"; /** * Map of running river instances. Used for management operations dispatching. See {@link #getRunningInstance(String)} */ protected static Map<String, IJiraRiverMgm> riverInstances = new HashMap<String, IJiraRiverMgm>(); /** * Name of datetime property where permanent indexing stop date is stored * * @see #storeDatetimeValue(String, String, Date, BulkRequestBuilder) * @see #readDatetimeValue(String, String) */ protected static final String PERMSTOREPROP_RIVER_STOPPED_PERMANENTLY = "river_stopped_permanently"; /** * How often is project list refreshed from JIRA instance [ms]. */ protected static final long JIRA_PROJECTS_REFRESH_TIME = 30 * 60 * 1000; public static final String INDEX_ISSUE_TYPE_NAME_DEFAULT = "jira_issue"; public static final String INDEX_ACTIVITY_TYPE_NAME_DEFAULT = "jira_river_indexupdate"; /** * ElasticSearch client to be used for indexing */ protected Client client; /** * Configured JIRA client to access data from JIRA */ protected IJIRAClient jiraClient; /** * Configured JIRA issue index structure builder to be used. */ protected IJIRAIssueIndexStructureBuilder jiraIssueIndexStructureBuilder; /** * Config - maximal number of parallel JIRA indexing threads */ protected int maxIndexingThreads; /** * Config - index update period [ms] */ protected long indexUpdatePeriod; /** * Config - index full update period [ms] */ protected long indexFullUpdatePeriod = -1; /** * Config - cron expression to schedule automatic full update from remote system. */ protected CronExpression indexFullUpdateCronExpression; /** * Config - name of ElasticSearch index used to store issues from this river */ protected String indexName; /** * Config - name of ElasticSearch type used to store issues from this river in index */ protected String typeName; /** * Config - Base URL of JIRA instance to index by this river */ protected String jiraUrlBase = null; /** * Config - name of ElasticSearch index used to store river activity records - null means no activity stored */ protected String activityLogIndexName; /** * Config - name of ElasticSearch type used to store river activity records in index */ protected String activityLogTypeName; /** * Thread running {@link JIRAProjectIndexerCoordinator} is stored here. */ protected Thread coordinatorThread; /** * USed {@link JIRAProjectIndexerCoordinator} instance is stored here. */ protected IJIRAProjectIndexerCoordinator coordinatorInstance; /** * Flag set to true if this river is stopped from ElasticSearch server. */ protected volatile boolean closed = true; /** * List of indexing excluded JIRA project keys loaded from river configuration * * @see #getAllIndexedProjectsKeys() */ protected List<String> projectKeysExcluded = null; /** * List of all JIRA project keys to be indexed. Loaded from river configuration, or from remote JIRA (excludes * removed) * * @see #getAllIndexedProjectsKeys() */ protected List<String> allIndexedProjectsKeys = null; /** * Next time when {@link #allIndexedProjectsKeys} need to be refreshed from remote JIRA instance. * * @see #getAllIndexedProjectsKeys() */ protected long allIndexedProjectsKeysNextRefresh = 0; /** * Last project indexing info store. Key in map is project key. */ protected Map<String, ProjectIndexingInfo> lastProjectIndexingInfo = new HashMap<String, ProjectIndexingInfo>(); /** * Date of last restart of this river. */ protected Date lastRestartDate; /** * Timestamp of permanent stop of this river. */ protected Date permanentStopDate; /** * Public constructor used by ElasticSearch. * * @param riverName * @param settings * @param client * @throws MalformedURLException */ @Inject public JiraRiver(RiverName riverName, RiverSettings settings, Client client) throws MalformedURLException { super(riverName, settings); this.client = client; configure(settings.settings()); } /** * Configure jira river. * * @param settings used for configuration. */ @SuppressWarnings({ "unchecked" }) protected void configure(Map<String, Object> settings) { if (!closed) throw new IllegalStateException("Jira River must be stopped to configure it!"); String jiraUser = null; String jiraJqlTimezone = TimeZone.getDefault().getDisplayName(); if (settings.containsKey("jira")) { Map<String, Object> jiraSettings = (Map<String, Object>) settings.get("jira"); jiraUrlBase = XContentMapValues.nodeStringValue(jiraSettings.get("urlBase"), null); if (Utils.isEmpty(jiraUrlBase)) { throw new SettingsException("jira/urlBase element of configuration structure not found or empty"); } Integer timeout = new Long(Utils.parseTimeValue(jiraSettings, "timeout", 5, TimeUnit.SECONDS)).intValue(); jiraUser = XContentMapValues.nodeStringValue(jiraSettings.get("username"), "Anonymous access"); jiraClient = new JIRA5RestClient(this, jiraUrlBase, XContentMapValues.nodeStringValue( jiraSettings.get("username"), null), XContentMapValues.nodeStringValue(jiraSettings.get("pwd"), null), timeout, XContentMapValues.nodeStringValue(jiraSettings.get("restApiVersion"), null)); jiraClient.setListJIRAIssuesMax(XContentMapValues.nodeIntegerValue(jiraSettings.get("maxIssuesPerRequest"), 50)); if (jiraSettings.get("jqlTimeZone") != null) { TimeZone tz = TimeZone.getTimeZone(XContentMapValues.nodeStringValue(jiraSettings.get("jqlTimeZone"), null)); jiraJqlTimezone = tz.getDisplayName(); jiraClient.setJQLDateFormatTimezone(tz); } jiraClient.setJqlTemplate(XContentMapValues.nodeStringValue(jiraSettings.get("jqlTemplate"), DEFAULT_JQL_TEMPLATE)); maxIndexingThreads = XContentMapValues.nodeIntegerValue(jiraSettings.get("maxIndexingThreads"), 1); indexUpdatePeriod = Utils.parseTimeValue(jiraSettings, "indexUpdatePeriod", 5, TimeUnit.MINUTES); indexFullUpdatePeriod = Utils.parseTimeValue(jiraSettings, "indexFullUpdatePeriod", 12, TimeUnit.HOURS); String ifuce = Utils.trimToNull((String) jiraSettings.get("indexFullUpdateCronExpression")); if (ifuce != null) { try { this.indexFullUpdateCronExpression = new CronExpression(ifuce); } catch (ParseException e) { throw new SettingsException("Cron expression in indexFullUpdateCronExpression is invalid: " + e.getMessage()); } } if (jiraSettings.containsKey("projectKeysIndexed")) { allIndexedProjectsKeys = Utils.parseCsvString(XContentMapValues.nodeStringValue( jiraSettings.get("projectKeysIndexed"), null)); if (allIndexedProjectsKeys != null) { // stop loading from JIRA allIndexedProjectsKeysNextRefresh = Long.MAX_VALUE; } } if (jiraSettings.containsKey("projectKeysExcluded")) { projectKeysExcluded = Utils.parseCsvString(XContentMapValues.nodeStringValue( jiraSettings.get("projectKeysExcluded"), null)); } } else { throw new SettingsException("'jira' element of river configuration structure not found"); } Map<String, Object> indexSettings = null; if (settings.containsKey("index")) { indexSettings = (Map<String, Object>) settings.get("index"); indexName = XContentMapValues.nodeStringValue(indexSettings.get("index"), riverName.name()); typeName = XContentMapValues.nodeStringValue(indexSettings.get("type"), INDEX_ISSUE_TYPE_NAME_DEFAULT); } else { indexName = riverName.name(); typeName = INDEX_ISSUE_TYPE_NAME_DEFAULT; } Map<String, Object> activityLogSettings = null; if (settings.containsKey("activity_log")) { activityLogSettings = (Map<String, Object>) settings.get("activity_log"); activityLogIndexName = Utils .trimToNull(XContentMapValues.nodeStringValue(activityLogSettings.get("index"), null)); if (activityLogIndexName == null) { throw new SettingsException( "'activity_log/index' element of river configuration structure must be defined with some string"); } activityLogTypeName = Utils.trimToNull(XContentMapValues.nodeStringValue(activityLogSettings.get("type"), INDEX_ACTIVITY_TYPE_NAME_DEFAULT)); } jiraIssueIndexStructureBuilder = new JIRA5RestIssueIndexStructureBuilder(this, indexName, typeName, jiraUrlBase, indexSettings); preparePreprocessors(indexSettings, jiraIssueIndexStructureBuilder); jiraClient.setIndexStructureBuilder(jiraIssueIndexStructureBuilder); logger .info( "Configured JIRA River '{}' for JIRA API base URL '{}', jira user '{}', JQL timezone '{}'. Search index name '{}', document type for issues '{}'.", riverName.getName(), jiraClient.getJiraAPIUrlBase(), jiraUser, jiraJqlTimezone, indexName, typeName); if (activityLogIndexName != null) { logger.info( "Activity log for JIRA River '{}' is enabled. Search index name '{}', document type for index updates '{}'.", riverName.getName(), activityLogIndexName, activityLogTypeName); } } @SuppressWarnings("unchecked") private void preparePreprocessors(Map<String, Object> indexSettings, IJIRAIssueIndexStructureBuilder indexStructureBuilder) { if (indexSettings != null) { List<Map<String, Object>> preproclist = (List<Map<String, Object>>) indexSettings.get("preprocessors"); if (preproclist != null && preproclist.size() > 0) { for (Map<String, Object> ppc : preproclist) { try { indexStructureBuilder.addIssueDataPreprocessor(StructuredContentPreprocessorFactory.createPreprocessor(ppc, client)); } catch (IllegalArgumentException e) { throw new SettingsException(e.getMessage(), e); } } } } } /** * Constructor for unit tests, nothing is initialized/configured in river. * * @param riverName * @param settings */ protected JiraRiver(RiverName riverName, RiverSettings settings) { super(riverName, settings); } @Override public synchronized void start() { if (!closed) throw new IllegalStateException("Can't start already running river"); logger.info("starting JIRA River"); synchronized (riverInstances) { addRunningInstance(this); } refreshSearchIndex(getRiverIndexName()); try { if ((permanentStopDate = readDatetimeValue(null, PERMSTOREPROP_RIVER_STOPPED_PERMANENTLY)) != null) { logger .info("JIRA River indexing process not started because stopped permanently, you can restart it over management REST API"); return; } } catch (IOException e) { // OK, we will start river } logger.info("starting JIRA River indexing process"); closed = false; lastRestartDate = new Date(); coordinatorInstance = new JIRAProjectIndexerCoordinator(jiraClient, this, jiraIssueIndexStructureBuilder, indexUpdatePeriod, maxIndexingThreads, indexFullUpdatePeriod, indexFullUpdateCronExpression); coordinatorThread = acquireIndexingThread("jira_river_coordinator", coordinatorInstance); coordinatorThread.start(); } @Override public synchronized void close() { logger.info("closing JIRA River on this node"); closed = true; if (coordinatorThread != null) { coordinatorThread.interrupt(); } // free instances created in #start() coordinatorThread = null; coordinatorInstance = null; synchronized (riverInstances) { riverInstances.remove(riverName().getName()); } } /** * Stop jira river, but leave instance existing in {@link #riverInstances} so it can be found over management REST * calls and/or reconfigured and started later again. Note that standard ES river {@link #close()} method * implementation removes river instance from {@link #riverInstances}. * * @param permanent set to true if info about river stopped can be persisted */ @Override public synchronized void stop(boolean permanent) { logger.info("stopping JIRA River indexing process"); closed = true; if (coordinatorThread != null) { coordinatorThread.interrupt(); } // free instances created in #start() coordinatorThread = null; coordinatorInstance = null; if (permanent) { try { permanentStopDate = new Date(); storeDatetimeValue(null, PERMSTOREPROP_RIVER_STOPPED_PERMANENTLY, permanentStopDate, null); refreshSearchIndex(getRiverIndexName()); logger.info("JIRA River indexing process stopped permanently, you can restart it over management REST API"); } catch (IOException e) { logger.warn("Permanent stopped value storing failed {}", e.getMessage()); } } } /** * Reconfigure jira river. Must be stopped! */ public synchronized void reconfigure() { if (!closed) throw new IllegalStateException("Jira River must be stopped to reconfigure it!"); logger.info("reconfiguring JIRA River"); String riverIndexName = getRiverIndexName(); refreshSearchIndex(riverIndexName); GetResponse resp = client.prepareGet(riverIndexName, riverName().name(), "_meta").execute().actionGet(); if (resp.isExists()) { if (logger.isDebugEnabled()) { logger.debug("Configuration document: {}", resp.getSourceAsString()); } Map<String, Object> newset = resp.getSource(); configure(newset); } else { throw new IllegalStateException("Configuration document not found to reconfigure jira river " + riverName().name()); } } /** * Restart jira river. Configuration of river is updated. */ @Override public synchronized void restart() { logger.info("restarting JIRA River"); boolean cleanPermanent = true; if (!closed) { cleanPermanent = false; stop(false); // wait a while to allow currently running indexers to finish?? try { Thread.sleep(2000); } catch (InterruptedException e) { return; } } else { logger.debug("stopped already"); } reconfigure(); if (cleanPermanent) { deleteDatetimeValue(null, PERMSTOREPROP_RIVER_STOPPED_PERMANENTLY); } start(); logger.info("JIRA River restarted"); } @Override public boolean isClosed() { return closed; } @Override public String forceFullReindex(String jiraProjectKey) throws Exception { if (coordinatorInstance == null) return null; List<String> pkeys = getAllIndexedProjectsKeys(); if (Utils.isEmpty(jiraProjectKey)) { if (pkeys != null) { for (String k : pkeys) { coordinatorInstance.forceFullReindex(k); } return Utils.createCsvString(pkeys); } else { return ""; } } else { if (pkeys != null && pkeys.contains(jiraProjectKey)) { coordinatorInstance.forceFullReindex(jiraProjectKey); return jiraProjectKey; } else { return null; } } } @Override public String forceIncrementalReindex(String jiraProjectKey) throws Exception { if (coordinatorInstance == null) return null; List<String> pkeys = getAllIndexedProjectsKeys(); if (Utils.isEmpty(jiraProjectKey)) { if (pkeys != null) { for (String k : pkeys) { coordinatorInstance.forceIncrementalReindex(k); } return Utils.createCsvString(pkeys); } else { return ""; } } else { if (pkeys != null && pkeys.contains(jiraProjectKey)) { coordinatorInstance.forceIncrementalReindex(jiraProjectKey); return jiraProjectKey; } else { return null; } } } /** * Get info about current operation of this river. Used for REST management operations handling. * * @return String with JSON formatted info. * @throws Exception */ @Override public String getRiverOperationInfo(DiscoveryNode esNode, Date currentDate) throws Exception { XContentBuilder builder = jsonBuilder().prettyPrint(); builder.startObject(); builder.field("river_name", riverName().getName()); builder.field("info_date", currentDate); builder.startObject("indexing"); builder.field("state", closed ? "stopped" : "running"); if (!closed) builder.field("last_restart", lastRestartDate); else if (permanentStopDate != null) builder.field("stopped_permanently", permanentStopDate); builder.endObject(); if (esNode != null) { builder.startObject("node"); builder.field("id", esNode.getId()); builder.field("name", esNode.getName()); builder.endObject(); } if (coordinatorInstance != null) { List<ProjectIndexingInfo> currProjectIndexingInfo = coordinatorInstance.getCurrentProjectIndexingInfo(); if (currProjectIndexingInfo != null) { builder.startArray("current_indexing"); for (ProjectIndexingInfo pi : currProjectIndexingInfo) { pi.buildDocument(builder, null, true, false); } builder.endArray(); } } List<String> pkeys = getAllIndexedProjectsKeys(); if (pkeys != null) { builder.startArray("indexed_jira_projects"); for (String projectKey : pkeys) { builder.startObject(); builder.field("project_key", projectKey); ProjectIndexingInfo lastIndexing = getLastProjectIndexingInfo(projectKey); if (lastIndexing != null) { builder.field("last_indexing"); lastIndexing.buildDocument(builder, null, false, true); } builder.endObject(); } builder.endArray(); } builder.endObject(); return builder.string(); } /** * @param projectKey to get info for * @return project indexing info or null if not found. */ protected ProjectIndexingInfo getLastProjectIndexingInfo(String projectKey) { ProjectIndexingInfo lastIndexing = lastProjectIndexingInfo.get(projectKey); if (lastIndexing == null && activityLogIndexName != null) { try { refreshSearchIndex(activityLogIndexName); SearchResponse sr = client .prepareSearch(activityLogIndexName) .setTypes(activityLogTypeName) .setPostFilter( FilterBuilders.andFilter( FilterBuilders.termFilter(ProjectIndexingInfo.DOCFIELD_PROJECT_KEY, projectKey), FilterBuilders.termFilter(ProjectIndexingInfo.DOCFIELD_RIVER_NAME, riverName().getName()))) .setQuery(QueryBuilders.matchAllQuery()).addSort(ProjectIndexingInfo.DOCFIELD_START_DATE, SortOrder.DESC) .addField("_source").setSize(1).execute().actionGet(); if (sr.getHits().getTotalHits() > 0) { SearchHit hit = sr.getHits().getAt(0); lastIndexing = ProjectIndexingInfo.readFromDocument(hit.sourceAsMap()); } else { logger.debug("No last indexing info found in activity log for project {}", projectKey); } } catch (Exception e) { logger.warn("Error during LastProjectIndexingInfo reading from activity log ES index: {} {}", e.getClass() .getName(), e.getMessage()); } } return lastIndexing; } /** * Get running instance of jira river for given name. Used for REST management operations handling. * * @param riverName to get instance for * @return river instance or null if not found * @see #addRunningInstance(IJiraRiverMgm) * @see #getRunningInstances() */ public static IJiraRiverMgm getRunningInstance(String riverName) { if (riverName == null) return null; return riverInstances.get(riverName); } /** * Put running instance of jira river into registry. Used for REST management operations handling. * * @param jiraRiver to get instance for * @see #getRunningInstances() * @see #getRunningInstance(String) */ public static void addRunningInstance(IJiraRiverMgm jiraRiver) { riverInstances.put(jiraRiver.riverName().getName(), jiraRiver); } /** * Get running instances of all jira rivers. Used for REST management operations handling. * * @return Set with names of all jira river instances registered for management * @see #addRunningInstance(IJiraRiverMgm) * @see #getRunningInstance(String) */ public static Set<String> getRunningInstances() { return Collections.unmodifiableSet((riverInstances.keySet())); } /** * Remove rivers of given names. Note: this method was added because of unit tests. Do not call this method in * production code. * * @param riverNames names of the rivers to remove */ public static void removeRunningInstances(String... riverNames) { for (String riverName : riverNames) { riverInstances.remove(riverName); } } @Override public List<String> getAllIndexedProjectsKeys() throws Exception { if (allIndexedProjectsKeys == null || allIndexedProjectsKeysNextRefresh < System.currentTimeMillis()) { allIndexedProjectsKeys = jiraClient.getAllJIRAProjects(); if (projectKeysExcluded != null) { allIndexedProjectsKeys.removeAll(projectKeysExcluded); } allIndexedProjectsKeysNextRefresh = System.currentTimeMillis() + JIRA_PROJECTS_REFRESH_TIME; } return allIndexedProjectsKeys; } @Override public void reportIndexingFinished(ProjectIndexingInfo indexingInfo) { lastProjectIndexingInfo.put(indexingInfo.projectKey, indexingInfo); if (coordinatorInstance != null) { try { coordinatorInstance.reportIndexingFinished(indexingInfo.projectKey, indexingInfo.finishedOK, indexingInfo.fullUpdate); } catch (Exception e) { logger.warn("Indexing finished reporting to coordinator failed due {}", e.getMessage()); } } writeActivityLogRecord(indexingInfo); } /** * Write indexing info into activity log if enabled. * * @param indexingInfo to write */ protected void writeActivityLogRecord(ProjectIndexingInfo indexingInfo) { if (activityLogIndexName != null) { try { client.prepareIndex(activityLogIndexName, activityLogTypeName) .setSource(indexingInfo.buildDocument(jsonBuilder(), riverName().getName(), true, true)).execute() .actionGet(); } catch (Exception e) { logger.error("Error during index update result writing to the audit log {}", e.getMessage()); } } } @Override public void storeDatetimeValue(String projectKey, String propertyName, Date datetime, BulkRequestBuilder esBulk) throws IOException { String documentName = prepareValueStoreDocumentName(projectKey, propertyName); if (logger.isDebugEnabled()) logger.debug( "Going to write {} property with datetime value {} for project {} using {} update. Document name is {}.", propertyName, datetime, projectKey, (esBulk != null ? "bulk" : "direct"), documentName); if (esBulk != null) { esBulk.add(indexRequest(getRiverIndexName()).type(riverName.name()).id(documentName) .source(storeDatetimeValueBuildDocument(projectKey, propertyName, datetime))); } else { client.prepareIndex(getRiverIndexName(), riverName.name(), documentName) .setSource(storeDatetimeValueBuildDocument(projectKey, propertyName, datetime)).execute().actionGet(); } } /** * Constant for field in JSON document used to store values. * * @see #storeDatetimeValue(String, String, Date, BulkRequestBuilder) * @see #readDatetimeValue(String, String) * @see #storeDatetimeValueBuildDocument(String, String, Date) * */ protected static final String STORE_FIELD_VALUE = "value"; /** * Prepare JSON document to be stored inside {@link #storeDatetimeValue(String, String, Date, BulkRequestBuilder)}. * * @param projectKey key of project value is for * @param propertyName name of property * @param datetime value to store * @return JSON document * @throws IOException * @see #storeDatetimeValue(String, String, Date, BulkRequestBuilder) * @see #readDatetimeValue(String, String) */ protected XContentBuilder storeDatetimeValueBuildDocument(String projectKey, String propertyName, Date datetime) throws IOException { XContentBuilder builder = jsonBuilder().startObject(); if (projectKey != null) builder.field("projectKey", projectKey); builder.field("propertyName", propertyName).field(STORE_FIELD_VALUE, DateTimeUtils.formatISODateTime(datetime)); builder.endObject(); return builder; } @Override public Date readDatetimeValue(String projectKey, String propertyName) throws IOException { Date lastDate = null; String documentName = prepareValueStoreDocumentName(projectKey, propertyName); if (logger.isDebugEnabled()) logger.debug("Going to read datetime value from {} property for project {}. Document name is {}.", propertyName, projectKey, documentName); refreshSearchIndex(getRiverIndexName()); GetResponse lastSeqGetResponse = client.prepareGet(getRiverIndexName(), riverName.name(), documentName).execute() .actionGet(); if (lastSeqGetResponse.isExists()) { Object timestamp = lastSeqGetResponse.getSourceAsMap().get(STORE_FIELD_VALUE); if (timestamp != null) { lastDate = DateTimeUtils.parseISODateTime(timestamp.toString()); } } else { if (logger.isDebugEnabled()) logger.debug("{} document doesn't exist in JIRA river persistent store", documentName); } return lastDate; } @Override public boolean deleteDatetimeValue(String projectKey, String propertyName) { String documentName = prepareValueStoreDocumentName(projectKey, propertyName); if (logger.isDebugEnabled()) logger.debug("Going to delete datetime value from {} property for project {}. Document name is {}.", propertyName, projectKey, documentName); refreshSearchIndex(getRiverIndexName()); DeleteResponse lastSeqGetResponse = client.prepareDelete(getRiverIndexName(), riverName.name(), documentName) .execute().actionGet(); if (!lastSeqGetResponse.isFound()) { if (logger.isDebugEnabled()) { logger.debug("{} document doesn't exist in JIRA river persistent store", documentName); } return false; } else { return true; } } /** * @return */ protected String getRiverIndexName() { return "_river"; // return RiverIndexName.Conf.indexName(settings.globalSettings()); } /** * Prepare name of document where jira project related persistent value is stored * * @param projectKey key of jira project stored value is for * @param propertyName name of value * @return document name * * @see #storeDatetimeValue(String, String, Date, BulkRequestBuilder) * @see #readDatetimeValue(String, String) */ protected static String prepareValueStoreDocumentName(String projectKey, String propertyName) { if (projectKey != null) return "_" + propertyName + "_" + projectKey; else return "_" + propertyName; } @Override public BulkRequestBuilder prepareESBulkRequestBuilder() { return client.prepareBulk(); } @Override public void executeESBulkRequest(BulkRequestBuilder esBulk) throws Exception { BulkResponse response = esBulk.execute().actionGet(); if (response.hasFailures()) { throw new ElasticsearchException("Failed to execute ES index bulk update: " + response.buildFailureMessage()); } } @Override public Thread acquireIndexingThread(String threadName, Runnable runnable) { return EsExecutors.daemonThreadFactory(settings.globalSettings(), threadName).newThread(runnable); } @Override public void refreshSearchIndex(String indexName) { client.admin().indices().prepareRefresh(indexName).execute().actionGet(); } private static final long ES_SCROLL_KEEPALIVE = 60000; @Override public SearchRequestBuilder prepareESScrollSearchRequestBuilder(String indexName) { return client.prepareSearch(indexName).setScroll(new TimeValue(ES_SCROLL_KEEPALIVE)).setSearchType(SearchType.SCAN) .setSize(100); } public SearchResponse executeESSearchRequest(SearchRequestBuilder searchRequestBuilder) { return searchRequestBuilder.execute().actionGet(); } @Override public SearchResponse executeESScrollSearchNextRequest(SearchResponse scrollResp) { return client.prepareSearchScroll(scrollResp.getScrollId()).setScroll(new TimeValue(ES_SCROLL_KEEPALIVE)).execute() .actionGet(); } @Override public ESLogger createLogger(Class<?> clazz) { return Loggers.getLogger(clazz, settings.globalSettings(), riverName); } }