/*
* ScreenSlicer (TM)
* Copyright (C) 2013-2015 Machine Publishers, LLC
* ops@machinepublishers.com | screenslicer.com | machinepublishers.com
* Cincinnati, Ohio, USA
*
* You can redistribute this program and/or modify it under the terms of the GNU Affero General Public
* License version 3 as published by the Free Software Foundation.
*
* "ScreenSlicer", "jBrowserDriver", "Machine Publishers", and "automatic, zero-config web scraping"
* are trademarks of Machine Publishers, LLC.
*
* 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
* Affero General Public License version 3 for more details.
*
* You should have received a copy of the GNU Affero General Public License version 3 along with this
* program. If not, see http://www.gnu.org/licenses/
*
* For general details about how to investigate and report license violations, please see
* https://www.gnu.org/licenses/gpl-violation.html and email the author, ops@machinepublishers.com
*/
package com.screenslicer.webapp;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import com.screenslicer.api.datatype.Contact;
import com.screenslicer.api.datatype.HtmlNode;
import com.screenslicer.api.datatype.Result;
import com.screenslicer.api.request.Cancel;
import com.screenslicer.api.request.EmailExport;
import com.screenslicer.api.request.Extract;
import com.screenslicer.api.request.Fetch;
import com.screenslicer.api.request.FormLoad;
import com.screenslicer.api.request.FormQuery;
import com.screenslicer.api.request.KeywordQuery;
import com.screenslicer.api.request.Request;
import com.screenslicer.common.CommonUtil;
import com.screenslicer.common.Log;
@Path("/screenslicer")
public class ScreenSlicerDriver implements WebResource {
private static final Collection<String> cancelledJobs = new HashSet<String>();
private static final Object cancelledLock = new Object();
private static final long WAIT = 2000;
private static final Object doneMapLock = new Object();
private static Map<String, AtomicBoolean> doneMap = new HashMap<String, AtomicBoolean>();
private static AtomicLong latestThread = new AtomicLong();
private static AtomicLong curThread = new AtomicLong();
/**
* @Deprecated This class will be renamed in version 2.0.0
*/
public static void main(String[] args) throws Exception {
WebApp.start("driver", 8887, true, null, null);
}
public static final boolean isCancelled(String runGuid) {
if (!CommonUtil.isEmpty(runGuid)) {
synchronized (cancelledLock) {
return cancelledJobs.contains(runGuid);
}
}
return false;
}
@Path("cancel")
@POST
@Consumes("application/json")
public static final String cancel(String reqString) {
ScreenSlicer.cancel(Cancel.instance(reqString));
return "";
}
@Path("is-busy/{instanceIp}")
@GET
@Produces("text/plain; charset=utf-8")
public static final String isBusy(@PathParam("instanceIp") String instanceIp) {
return Boolean.toString(ScreenSlicer.isBusy(instanceIp));
}
@Path("export")
@POST
@Consumes("application/json")
public static final String export(final String reqString) {
process(reqString, new Callback() {
@Override
public String perform(Request request) {
ScreenSlicer.export(request, EmailExport.instance(reqString));
return null;
}
});
return "";
}
@Path("query-form")
@POST
@Consumes("application/json")
@Produces("application/json; charset=utf-8")
public static final String queryForm(final String reqString) {
return process(reqString, new Callback() {
@Override
public String perform(Request request) {
return Result.toJson(ScreenSlicer.queryForm(request, FormQuery.instance(reqString)));
}
});
}
@Path("query-keyword")
@POST
@Consumes("application/json")
@Produces("application/json; charset=utf-8")
public static final String queryKeyword(final String reqString) {
return process(reqString, new Callback() {
@Override
public String perform(Request request) {
return Result.toJson(ScreenSlicer.queryKeyword(request, KeywordQuery.instance(reqString)));
}
});
}
@Path("expand-search-result")
@POST
@Consumes("application/json")
@Produces("application/json; charset=utf-8")
public static final String expandSearchResult(final String reqString) {
return process(reqString, new Callback() {
@Override
public String perform(Request request) {
return Result.toJson(ScreenSlicer.expandSearchResult(
Result.instance(reqString)));
}
});
}
@Path("load-form")
@POST
@Consumes("application/json")
@Produces("application/json; charset=utf-8")
public static final String loadForm(final String reqString) {
return process(reqString, new Callback() {
@Override
public String perform(Request request) {
return HtmlNode.toJson(ScreenSlicer.loadForm(request, FormLoad.instance(reqString)));
}
});
}
@Path("extract-person")
@POST
@Consumes("application/json")
@Produces("application/json; charset=utf-8")
public static final String extractPerson(final String reqString) {
return process(reqString, new Callback() {
@Override
public String perform(Request request) {
return Contact.toJson(ScreenSlicer.extractPerson(request, Extract.instance(reqString)));
}
});
}
@Path("fetch")
@POST
@Consumes("application/json")
@Produces("text/plain; charset=utf-8")
public static final String fetch(final String reqString) {
return process(reqString, new Callback() {
@Override
public String perform(Request request) {
//TODO to be removed in version 2.0.0 after deprecated options removed
Map<String, Object> map = (Map<String, Object>) CommonUtil.gson.fromJson(reqString, CommonUtil.objectType);
if (map.containsKey("downloads")) {
return Result.toJson(ScreenSlicer.fetch(request, Fetch.instance(reqString)));
}
return ScreenSlicer._deprecated_fetch(request, Fetch.instance(reqString));
}
});
}
private static interface Callback {
String perform(Request request);
}
private static String process(String reqString, Callback callback) {
Request request = Request.instance(reqString);
String myInstance = null;
AtomicBoolean myDone = null;
try {
Map<String, AtomicBoolean> myDoneMap = new HashMap<String, AtomicBoolean>();
synchronized (doneMapLock) {
for (int i = 0; request.instances != null && i < request.instances.length; i++) {
if (!doneMap.containsKey(request.instances[i])) {
doneMap.put(request.instances[i], new AtomicBoolean(true));
}
}
myDoneMap.putAll(doneMap);
}
long myThread = latestThread.incrementAndGet();
while (true) {
if (isCancelled(request.runGuid)) {
curThread.incrementAndGet();
throw new CancellationException();
}
if (myThread == curThread.get() + 1) {
for (Map.Entry<String, AtomicBoolean> done : myDoneMap.entrySet()) {
if (done.getValue().compareAndSet(true, false)) {
if (ScreenSlicer.isBusy(done.getKey())) {
//TODO fixme?
done.getValue().set(true);
} else {
myInstance = done.getKey();
myDone = done.getValue();
break;
}
}
}
if (myInstance != null) {
break;
}
}
try {
Thread.sleep(WAIT);
} catch (Throwable t) {
Log.exception(t);
}
}
curThread.incrementAndGet();
request.instances = new String[] { myInstance };
return callback.perform(request);
} catch (Throwable t) {
Log.exception(t);
} finally {
myDone.set(true);
synchronized (cancelledLock) {
cancelledJobs.remove(request.runGuid);
}
}
return null;
}
}