package org.limewire.core.impl.inspections;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.limewire.core.settings.ApplicationSettings;
import org.limewire.core.settings.InspectionsSettings;
import org.limewire.facebook.service.settings.InspectionsServerUrls;
import org.limewire.http.httpclient.HttpClientInstanceUtils;
import org.limewire.http.httpclient.HttpClientUtils;
import org.limewire.inject.EagerSingleton;
import org.limewire.inspection.Inspector;
import org.limewire.io.InvalidDataException;
import org.limewire.lifecycle.Service;
import org.limewire.lifecycle.ServiceRegistry;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;
import org.limewire.setting.StringSetting;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.name.Named;
/**
* - request/receive instructions from LW server for which inspections
* to send and when (how frequent)
* - receive inspections from classes with event driven inspection points
* (if requested by server)
* - pre-emptively perform inspections as per server instructions
* (which inspections, when and how frequent)
*/
@EagerSingleton
public class InspectionsCommunicatorImpl implements InspectionsCommunicator, Service {
private static final Log LOG = LogFactory.getLog(InspectionsCommunicatorImpl.class);
private static final String SERVICE_NAME = "Push Inspections Communicator";
// used to schedule standard inspection tasks
private final ScheduledExecutorService scheduler;
// for performing inspections
private final Inspector inspector;
private InspectionsResultProcessor processor;
private final ClientConnectionManager httpConnectionManager;
private final InspectionsParser parser;
private final Provider<Map<String, StringSetting>> inspectionsServerUrls;
private final AtomicBoolean started = new AtomicBoolean(false);
private List<InspectionsSpec> inspectionsSpecs = new ArrayList<InspectionsSpec>();
private final HttpClientInstanceUtils httpClientInstanceUtils;
@Inject
public InspectionsCommunicatorImpl(@Named("fastExecutor")ScheduledExecutorService scheduler,
@Named("sslConnectionManager") ClientConnectionManager httpConnectionManager,
@InspectionsServerUrls Provider<Map<String, StringSetting>> inspectionsServerUrls,
Inspector inspector,
HttpClientInstanceUtils httpClientInstanceUtils) {
this.scheduler = scheduler;
this.inspector = inspector;
this.httpConnectionManager = httpConnectionManager;
this.inspectionsServerUrls = inspectionsServerUrls;
this.httpClientInstanceUtils = httpClientInstanceUtils;
this.processor = null;
this.parser = new InspectionsParser();
}
@Inject
public void register(ServiceRegistry serviceRegistry) {
serviceRegistry.register(this);
}
@Override
public void setResultProcessor(InspectionsResultProcessor processor) {
this.processor = processor;
}
@Override
public InspectionsResultProcessor getResultProcessor() {
if (processor == null) {
processor = new DefaultInspectionsProcessor();
}
return processor;
}
@Override
public synchronized void initInspectionSpecs(List<InspectionsSpec> inspSpecs) {
for (InspectionsSpec spec : inspSpecs) {
// check inspections - skip insp. spec if no inspections.
if (!spec.getInspectionPoints().isEmpty()) {
spec.schedule(getResultProcessor(), inspector, scheduler);
inspectionsSpecs.add(spec);
}
}
}
public synchronized void cancelInspections(List<InspectionsSpec> inspSpecs) {
// cancel pending inspections scheduled
for (InspectionsSpec spec : inspSpecs) {
spec.ensureCancelled();
}
inspectionsSpecs.removeAll(inspSpecs);
}
private byte[] queryInspectionsServer(String serverUrl, HttpEntity entity) throws IOException {
boolean usage = ApplicationSettings.ALLOW_ANONYMOUS_STATISTICS_GATHERING.get();
serverUrl = httpClientInstanceUtils.addClientInfoToUrl(serverUrl) + "&urs=" + Boolean.toString(usage);
HttpPost httpPost = new HttpPost(serverUrl);
httpPost.addHeader("Accept-Encoding", "gzip");
httpPost.addHeader("Connection", "close");
if (entity != null) {
httpPost.setEntity(entity);
}
return executeRequest(httpPost);
}
/********** HTTP methods **********/
/**
* Executes the http request, returning the response as byte[]
* @param request http request
* @return byte[] response
* @throws java.io.IOException for but a 200 response.
*/
private byte[] executeRequest(HttpUriRequest request) throws IOException {
// if inspections were disabled, stop everything
if (!InspectionsSettings.PUSH_INSPECTIONS_ENABLED.getValue()) {
stop();
return new byte[0];
}
HttpClient httpClient = new DefaultHttpClient(httpConnectionManager, null);
HttpResponse response = httpClient.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (statusCode != 200 || entity == null) {
throw new IOException("invalid http response, status: " + statusCode
+ ", entity: " + ((entity != null) ? EntityUtils.toString(entity) : "none"));
}
byte[] responseBytes = EntityUtils.toByteArray(entity);
HttpClientUtils.releaseConnection(response);
return responseBytes;
}
/********** Service methods **********/
/**
* async because we contact the http server, retrying upon failure
*/
@Override
public void start() {
if (InspectionsSettings.PUSH_INSPECTIONS_ENABLED.get()) {
started.set(true);
scheduler.execute(new Runnable(){
@Override
public void run() {
// contact server, get insp. instructions
List<InspectionsSpec> specs = Collections.emptyList();
try {
String requestUrl = inspectionsServerUrls.get().get(
InspectionsServerUrls.INSPECTION_SPEC_REQUEST_URL).get();
byte[] rawInspectionSpecs = queryInspectionsServer(requestUrl, null);
specs = parser.parseInspectionSpecs(rawInspectionSpecs);
} catch (IOException e) {
LOG.error("Error in getting inspections specifications from server", e);
} catch (InvalidDataException e) {
LOG.error("Error in getting inspections specifications from server", e);
}
if (!specs.isEmpty()) {
initInspectionSpecs(specs);
}
}
});
}
}
@Override
public void stop() {
// todo: send any unsent (such as previously failed, or not yet sent) inspections results
if (started.get()) {
// cancel any pending inspections scheduled
cancelInspections(inspectionsSpecs);
}
}
@Override
public void initialize() { }
@Override
public String getServiceName() {
return SERVICE_NAME;
}
// default implementation currently just logs and sends inspection results to server
//
// todo: another, better implementation can add to a queue, and a separate thread is responsible for all sending
private class DefaultInspectionsProcessor implements InspectionsResultProcessor {
@Override
public void inspectionsPerformed(InspectionDataContainer insps) throws InspectionProcessingException {
try {
byte[] bytesToSend = parser.inspectionResultToByteArray(insps);
String submitUrl = inspectionsServerUrls.get().get(
InspectionsServerUrls.INSPECTION_SPEC_SUBMIT_URL).get();
queryInspectionsServer(submitUrl, new ByteArrayEntity(bytesToSend));
} catch (IOException e) {
LOG.debug("Error sending inspections results to server", e);
throw new InspectionProcessingException(e);
}
}
}
}