/*
* Copyright 2012 Nodeable Inc
*
* 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 com.streamreduce.core.jobs;
import com.google.code.morphia.mapping.Mapper;
import com.google.code.morphia.query.Query;
import com.google.code.morphia.query.UpdateOperations;
import com.streamreduce.connections.CloudProvider;
import com.streamreduce.connections.ConnectionProviderFactory;
import com.streamreduce.connections.FeedProvider;
import com.streamreduce.connections.GoogleAnalyticsProvider;
import com.streamreduce.connections.MonitoringProvider;
import com.streamreduce.connections.ProjectHostingProvider;
import com.streamreduce.connections.TwitterProvider;
import com.streamreduce.core.dao.ConnectionDAO;
import com.streamreduce.core.model.Connection;
import com.streamreduce.core.service.ConnectionService;
import com.streamreduce.core.service.EmailService;
import com.streamreduce.core.service.exception.InvalidCredentialsException;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Component
@ManagedResource(objectName="com.streamreduce.core.jobs:type=ConnectionPollingJob,name=connection-polling-job-mgmt", currencyTimeLimit = 15)
public class ConnectionPollingJob {
private final Timer REFRESH_TIMER = Metrics.newTimer(ConnectionPollingJob.class,
"connection-polling-job-metrics", TimeUnit.SECONDS,
TimeUnit.MINUTES);
private transient Logger logger = LoggerFactory.getLogger(getClass());
@Value("${cloud.polling.job.interval}")
private long cloudPollingJobInterval;
@Value("${projecthosting.polling.job.interval}")
private long projectHostingPollingJobInterval;
@Value("${feed.polling.job.interval}")
private long feedPollingJobInterval;
@Value("${monitoring.polling.job.interval}")
private long monitoringPollingJobInterval;
@Value("${twitter.polling.job.interval}")
private long twitterPollingJobInterval;
@Value("${googleanalytics.polling.job.interval}")
private long googleAnalyticsPollingJobInterval;
@Value("${connection.polling.job.max.failed.count}")
private long pollingJobMaxFailedCount;
@Value("${connection.polling.job.broken.sleep.time}")
private long pollingJobBrokenSleepTime;
@Value("${connection.polling.job.bootstrap.batch.size}")
private long pollingJobBootstrapBatchSize;
@Value("${nodeable.polling.enabled}")
private boolean pollingEnabled;
@Autowired
private ThreadPoolTaskExecutor executor;
@Autowired
private ConnectionProviderFactory connectionProviderFactory;
@Autowired
private ConnectionService connectionService;
@Autowired
private ConnectionDAO connectionDAO;
@Autowired
private EmailService emailService;
/**
* Refreshes the inventory cache for appropriate connection objects.
*/
@Scheduled(fixedRate = 30000)
@ManagedOperation(description = "Refreshes the inventory cache for appropriate connection objects")
public void execute() {
if (!pollingEnabled) {
return;
}
long now = System.currentTimeMillis();
List<Connection> connectionsNeedingPolling = new ArrayList<>();
List<Connection> allConnections = connectionService.getConnections(null);
for (Connection connection : allConnections) {
if (connectionProviderFactory.pushConnectionProviderFromId(connection.getProviderId()) != null || connection.isDisabled()) {
continue;
}
long interval = getPollingIntervalForConnection(connection);
long elapsedTimeSinceLastRun = now - connection.getPollingLastExecutionTime();
boolean pollingNeeded = !connection.isPollingInProgress() && (connection.getPollingFailedCount() <= pollingJobMaxFailedCount) && (elapsedTimeSinceLastRun > interval);
boolean pollingTakingTooLong = connection.isPollingInProgress() && (elapsedTimeSinceLastRun > (5 * interval));
boolean pollingPastSleepingPeriod = !connection.isPollingInProgress() && (connection.getPollingFailedCount() > pollingJobMaxFailedCount) && (elapsedTimeSinceLastRun > pollingJobBrokenSleepTime);
if (pollingNeeded || pollingTakingTooLong || pollingPastSleepingPeriod) {
connectionsNeedingPolling.add(connection);
if (connectionsNeedingPolling.size() == pollingJobBootstrapBatchSize) {
break;
}
}
}
for (final Connection connection : connectionsNeedingPolling) {
logger.info("[JOB] Connection " + connection.getId() + " [" + connection.getAlias() + "] last polled at " + new Date(connection.getPollingLastExecutionTime()) + ", refreshing");
executor.execute(new Runnable() {
@Override
public void run() {
refresh(connection);
}
});
}
}
private long getPollingIntervalForConnection(Connection connection) {
String providerType = connection.getType();
long interval;
if (providerType.equals(CloudProvider.TYPE)) {
interval = cloudPollingJobInterval;
} else if (providerType.equals(ProjectHostingProvider.TYPE)) {
interval = projectHostingPollingJobInterval;
} else if (providerType.equals(GoogleAnalyticsProvider.TYPE)) {
interval = googleAnalyticsPollingJobInterval;
} else if (providerType.equals(FeedProvider.TYPE)) {
interval = feedPollingJobInterval;
} else if (providerType.equals(MonitoringProvider.TYPE)) {
interval = monitoringPollingJobInterval;
} else if (providerType.equals(TwitterProvider.TYPE)) {
interval = twitterPollingJobInterval;
} else {
logger.error("[JOB] Connection " + connection.getId() + " [" + connection.getAlias() + "] unable to get the schedule interval for the polling job of providerType " + connection.getType());
interval = Long.MAX_VALUE;
}
return interval;
}
private void refresh(Connection connection) {
long now = System.currentTimeMillis();
try {
logger.info("[JOB] Connection " + connection.getId() + " [" + connection.getAlias() + "] polling job started");
Query<Connection> updateQuery = connectionDAO.createQuery().field(Mapper.ID_KEY).equal(connection.getId());
UpdateOperations<Connection> ops = connectionDAO.createUpdateOperations().set("pollingInProgress", true).set("pollingLastExecutionTime", now);
connectionDAO.update(updateQuery, ops);
// connection fields have to be updated because connection is saved again in the inventory refresh process
connection.setPollingInProgress(true);
connection.setPollingLastExecutionTime(now);
connectionService.fireOneTimeHighPriorityJobForConnection(connection);
logger.info("[JOB] Connection " + connection.getId() + " [" + connection.getAlias() + "] " + connection.getType() + " polling job finished (" + (System.currentTimeMillis() - now) + " ms)");
ops = connectionDAO.createUpdateOperations().set("pollingInProgress", false).unset("pollingFailedCount");
connectionDAO.update(updateQuery, ops);
} catch (InvalidCredentialsException e) {
logger.error("[JOB] Connection " + connection.getId() + " [" + connection.getAlias() + "] authentication failure in polling job, set as broken", e);
Query<Connection> updateQuery = connectionDAO.createQuery().field(Mapper.ID_KEY).equal(connection.getId());
UpdateOperations<Connection> ops = connectionDAO.createUpdateOperations().set("pollingInProgress", false).set("broken", true).set("lastErrorMessage", e.getMessage());
connectionDAO.update(updateQuery, ops);
emailService.sendConnectionBrokenEmail(connection);
} catch (Exception e) {
logger.error("[JOB] Connection " + connection.getId() + " [" + connection.getAlias() + "] " + connection.getType() + " polling job borked (" + (System.currentTimeMillis() - now) + " ms)", e);
Query<Connection> updateQuery = connectionDAO.createQuery().field(Mapper.ID_KEY).equal(connection.getId());
UpdateOperations<Connection> ops = connectionDAO.createUpdateOperations().set("pollingInProgress", false).inc("pollingFailedCount");
connectionDAO.update(updateQuery, ops);
long failedCount = connection.getPollingFailedCount() + 1;
if (failedCount > pollingJobMaxFailedCount) {
logger.error("[JOB] Connection " + connection.getId() + " [" + connection.getAlias() + "] failed count exceeded maximum value " + pollingJobMaxFailedCount + ", sleeping");
}
}
}
@ManagedAttribute(description = "Cloud Polling Job Interval")
public long getCloudPollingJobInterval() {
return cloudPollingJobInterval;
}
@ManagedAttribute(description = "Cloud Polling Job Interval")
public void setCloudPollingJobInterval(long cloudPollingJobInterval) {
this.cloudPollingJobInterval = cloudPollingJobInterval;
}
@ManagedAttribute(description = "Project Hosting Polling Job Interval")
public long getProjectHostingPollingJobInterval() {
return projectHostingPollingJobInterval;
}
@ManagedAttribute(description = "Project Hosting Polling Job Interval")
public void setProjectHostingPollingJobInterval(long projectHostingPollingJobInterval) {
this.projectHostingPollingJobInterval = projectHostingPollingJobInterval;
}
@ManagedAttribute(description = "Google Analytics Polling Job Interval")
public long getGoogleAnalyticsPollingJobInterval() {
return googleAnalyticsPollingJobInterval;
}
@ManagedAttribute(description = "Google Analytics Polling Job Interval")
public void setGoogleAnalyticsPollingJobInterval(long googleAnalyticsPollingJobInterval) {
this.googleAnalyticsPollingJobInterval = googleAnalyticsPollingJobInterval;
}
@ManagedAttribute(description = "Feed Polling Job Interval")
public long getFeedPollingJobInterval() {
return feedPollingJobInterval;
}
@ManagedAttribute(description = "Feed Polling Job Interval")
public void setFeedPollingJobInterval(long feedPollingJobInterval) {
this.feedPollingJobInterval = feedPollingJobInterval;
}
@ManagedAttribute(description = "Monitoring Polling Job Interval")
public long getMonitoringPollingJobInterval() {
return monitoringPollingJobInterval;
}
@ManagedAttribute(description = "Monitoring Polling Job Interval")
public void setMonitoringPollingJobInterval(long monitoringPollingJobInterval) {
this.monitoringPollingJobInterval = monitoringPollingJobInterval;
}
@ManagedAttribute(description = "Twitter Polling Job Interval")
public long getTwitterPollingJobInterval() {
return twitterPollingJobInterval;
}
@ManagedAttribute(description = "Twitter Polling Job Interval")
public void setTwitterPollingJobInterval(long twitterPollingJobInterval) {
this.twitterPollingJobInterval = twitterPollingJobInterval;
}
@ManagedAttribute(description = "Polling Job Max Failed Count")
public long getPollingJobMaxFailedCount() {
return pollingJobMaxFailedCount;
}
@ManagedAttribute(description = "Polling Job Max Failed Count")
public void setPollingJobMaxFailedCount(long pollingJobMaxFailedCount) {
this.pollingJobMaxFailedCount = pollingJobMaxFailedCount;
}
@ManagedAttribute(description = "Polling Job Broken Sleep Time")
public long getPollingJobBrokenSleepTime() {
return pollingJobBrokenSleepTime;
}
@ManagedAttribute(description = "Polling Job Broken Sleep Time")
public void setPollingJobBrokenSleepTime(long pollingJobBrokenSleepTime) {
this.pollingJobBrokenSleepTime = pollingJobBrokenSleepTime;
}
@ManagedAttribute(description = "Polling Job Bootstrap Batch Size")
public long getPollingJobBootstrapBatchSize() {
return pollingJobBootstrapBatchSize;
}
@ManagedAttribute(description = "Polling Job Bootstrap Batch Size")
public void setPollingJobBootstrapBatchSize(long pollingJobBootstrapBatchSize) {
this.pollingJobBootstrapBatchSize = pollingJobBootstrapBatchSize;
}
}