/**
* AbstractPushServlet
* Copyright 27.07.2015 by Dang Hai An, @zyzo
* <p/>
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* <p/>
* This library 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
* Lesser General Public License for more details.
* <p/>
* You should have received a copy of the GNU Lesser General Public License
* along with this program in the file lgpl21.txt
* If not, see <http://www.gnu.org/licenses/>.
*/
package org.loklak.api.iot;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.github.fge.jsonschema.core.report.ProcessingReport;
import org.eclipse.jetty.util.log.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import org.loklak.data.DAO;
import org.loklak.geo.LocationSource;
import org.loklak.harvester.JsonFieldConverter;
import org.loklak.harvester.JsonValidator;
import org.loklak.http.ClientConnection;
import org.loklak.http.RemoteAccess;
import org.loklak.objects.MessageEntry;
import org.loklak.objects.SourceType;
import org.loklak.objects.QueryEntry.PlaceContext;
import org.loklak.server.Query;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public abstract class AbstractPushServlet extends HttpServlet {
private static final long serialVersionUID = 8849199146929807638L;
private JsonValidator validator;
private JsonFieldConverter converter;
@Override
public void init() throws ServletException {
try {
validator = new JsonValidator(this.getValidatorSchema());
} catch (IOException e) {
Log.getLog().warn("Unable to initialize push servlet validator : " + e);
}
try {
converter = new JsonFieldConverter(this.getConversionSchema());
} catch (IOException e) {
Log.getLog().warn("Unable to initialize push servlet field converter : " + e);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Query post = RemoteAccess.evaluate(request);
String remoteHash = Integer.toHexString(Math.abs(post.getClientHost().hashCode()));
// manage DoS
if (post.isDoS_blackout()) {
response.sendError(503, "your request frequency is too high");
return;
}
String url = post.get("url", "");
if (url == null || url.length() == 0) {
response.sendError(400, "your request does not contain an url to your data object");
return;
}
String screen_name = post.get("screen_name", "");
if (screen_name == null || screen_name.length() == 0) {
response.sendError(400, "your request does not contain required screen_name parameter");
return;
}
JSONObject map;
String jsonString = "";
try {
jsonString = new String(ClientConnection.download(url), StandardCharsets.UTF_8);
map = new JSONObject(jsonString);
} catch (Exception e) {
response.sendError(400, "error reading json file from url");
return;
}
// validation phase
ProcessingReport report = this.validator.validate(jsonString);
if (!report.isSuccess()) {
response.sendError(400, "json does not conform to schema : " + this.getValidatorSchema().name() + "\n" + report);
return;
}
// conversion phase
Object extractResults = extractMessages(map);
JSONArray messages;
if (extractResults instanceof JSONArray) {
messages = (JSONArray) extractResults;
} else if (extractResults instanceof JSONObject) {
messages = new JSONArray();
messages.put((JSONObject) extractResults);
} else {
throw new IOException("extractMessages must return either a List or a Map. Get " + (extractResults == null ? "null" : extractResults.getClass().getCanonicalName()) + " instead");
}
JSONArray convertedMessages = this.converter.convert(messages);
PushReport nodePushReport = new PushReport();
ObjectWriter ow = new ObjectMapper().writerWithDefaultPrettyPrinter();
// custom treatment for each message
for (int i = 0; i < messages.length(); i++) {
JSONObject message = (JSONObject) convertedMessages.get(i);
message.put("source_type", this.getSourceType().toString());
message.put("location_source", LocationSource.USER.name());
message.put("place_context", PlaceContext.ABOUT.name());
if (message.get("text") == null) {
message.put("text", "");
}
// append rich-text attachment
String jsonToText = ow.writeValueAsString(messages.get(i));
message.put("text", message.get("text") + MessageEntry.RICH_TEXT_SEPARATOR + jsonToText);
customProcessing(message);
if (message.get("mtime") == null) {
String existed = PushServletHelper.checkMessageExistence(message);
// message known
if (existed != null) {
messages.remove(i);
nodePushReport.incrementKnownCount(existed);
continue;
}
// updated message -> save with new mtime value
message.put("mtime", Long.toString(System.currentTimeMillis()));
}
try {
message.put("id_str", PushServletHelper.computeMessageId(message, getSourceType()));
} catch (Exception e) {
DAO.log("Problem computing id : " + e.getMessage());
}
}
try {
PushReport savingReport = PushServletHelper.saveMessagesAndImportProfile(convertedMessages, jsonString.hashCode(), post, getSourceType(), screen_name);
nodePushReport.combine(savingReport);
} catch (IOException e) {
response.sendError(404, e.getMessage());
return;
}
String res = PushServletHelper.buildJSONResponse(post.get("callback", ""), nodePushReport);
post.setResponse(response, "application/javascript");
response.getOutputStream().println(res);
DAO.log(request.getServletPath()
+ " -> records = " + nodePushReport.getRecordCount()
+ ", new = " + nodePushReport.getNewCount()
+ ", known = " + nodePushReport.getKnownCount()
+ ", error = " + nodePushReport.getErrorCount()
+ ", from host hash " + remoteHash);
}
protected abstract SourceType getSourceType();
protected abstract JsonValidator.JsonSchemaEnum getValidatorSchema();
protected abstract JsonFieldConverter.JsonConversionSchemaEnum getConversionSchema();
// return either a list or a map of <String,Object>
protected abstract JSONArray extractMessages(JSONObject data);
protected abstract void customProcessing(JSONObject message);
}