/*
* (C) Copyright 2006-2008 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* Nuxeo - initial API and implementation
*
* $Id$
*
*/
package org.nuxeo.ecm.platform.pictures.tiles.restlets;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.nuxeo.common.utils.FileUtils;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.NuxeoException;
import org.nuxeo.ecm.core.io.download.DownloadService;
import org.nuxeo.ecm.platform.pictures.tiles.api.PictureTiles;
import org.nuxeo.ecm.platform.pictures.tiles.api.adapter.PictureTilesAdapter;
import org.nuxeo.ecm.platform.pictures.tiles.serializer.JSONPictureTilesSerializer;
import org.nuxeo.ecm.platform.pictures.tiles.serializer.PictureTilesSerializer;
import org.nuxeo.ecm.platform.pictures.tiles.serializer.XMLPictureTilesSerializer;
import org.nuxeo.ecm.platform.ui.web.restAPI.BaseStatelessNuxeoRestlet;
import org.nuxeo.runtime.api.Framework;
import org.restlet.data.CharacterSet;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Request;
import org.restlet.data.Response;
/**
* Restlet to provide a REST API on top of the PictureTilingService.
*
* @author tiry
*/
public class PictureTilesRestlets extends BaseStatelessNuxeoRestlet {
// cache duration in seconds
protected static int MAX_CACHE_LIFE = 60 * 10;
protected static Map<String, PictureTilesCachedEntry> cachedAdapters = new ConcurrentHashMap<String, PictureTilesCachedEntry>();
@Override
public void handle(Request req, Response res) {
HttpServletRequest request = getHttpRequest(req);
HttpServletResponse response = getHttpResponse(res);
String repo = (String) req.getAttributes().get("repoId");
String docid = (String) req.getAttributes().get("docId");
Integer tileWidth = Integer.decode((String) req.getAttributes().get("tileWidth"));
Integer tileHeight = Integer.decode((String) req.getAttributes().get("tileHeight"));
Integer maxTiles = Integer.decode((String) req.getAttributes().get("maxTiles"));
Form form = req.getResourceRef().getQueryAsForm();
String xpath = (String) form.getFirstValue("fieldPath");
String x = form.getFirstValue("x");
String y = form.getFirstValue("y");
String format = form.getFirstValue("format");
String test = form.getFirstValue("test");
if (test != null) {
try {
handleSendTest(res, repo, docid, tileWidth, tileHeight, maxTiles);
return;
} catch (IOException e) {
handleError(res, e);
return;
}
}
if (repo == null || repo.equals("*")) {
handleError(res, "you must specify a repository");
return;
}
if (docid == null || repo.equals("*")) {
handleError(res, "you must specify a documentId");
return;
}
Boolean init = initRepositoryAndTargetDocument(res, repo, docid);
if (!init) {
handleError(res, "unable to init repository connection");
return;
}
PictureTilesAdapter adapter;
try {
adapter = getFromCache(targetDocument, xpath);
if (adapter == null) {
adapter = targetDocument.getAdapter(PictureTilesAdapter.class);
if ((xpath != null) && (!"".equals(xpath))) {
adapter.setXPath(xpath);
}
updateCache(targetDocument, adapter, xpath);
}
} catch (NuxeoException e) {
handleError(res, e);
return;
}
if (adapter == null) {
handleNoTiles(res, null);
return;
}
PictureTiles tiles = null;
try {
tiles = adapter.getTiles(tileWidth, tileHeight, maxTiles);
} catch (NuxeoException e) {
handleError(res, e);
}
if ((x == null) || (y == null)) {
handleSendInfo(res, tiles, format);
return;
}
final Blob image;
try {
image = tiles.getTile(Integer.decode(x), Integer.decode(y));
} catch (NuxeoException | IOException e) {
handleError(res, e);
return;
}
String reason = "tile";
Boolean inline = Boolean.TRUE;
Map<String, Serializable> extendedInfos = new HashMap<>();
extendedInfos.put("x", x);
extendedInfos.put("y", y);
DownloadService downloadService = Framework.getService(DownloadService.class);
try {
downloadService.downloadBlob(request, response, targetDocument, xpath, image, image.getFilename(), reason,
extendedInfos, inline, byteRange -> setEntityToBlobOutput(image, byteRange, res));
} catch (IOException e) {
handleError(res, e);
}
}
protected void handleSendTest(Response res, String repoId, String docId, Integer tileWidth, Integer tileHeight,
Integer maxTiles) throws IOException {
MediaType mt = null;
mt = MediaType.TEXT_HTML;
File file = FileUtils.getResourceFileFromContext("testTiling.html");
String html = org.apache.commons.io.FileUtils.readFileToString(file);
html = html.replace("$repoId$", repoId);
html = html.replace("$docId$", docId);
html = html.replace("$tileWidth$", tileWidth.toString());
html = html.replace("$tileHeight$", tileHeight.toString());
html = html.replace("$maxTiles$", maxTiles.toString());
res.setEntity(html, mt);
}
protected void handleSendInfo(Response res, PictureTiles tiles, String format) {
if (format == null) {
format = "XML";
}
MediaType mt = null;
PictureTilesSerializer serializer = null;
if (format.equalsIgnoreCase("json")) {
serializer = new JSONPictureTilesSerializer();
mt = MediaType.APPLICATION_JSON;
} else {
serializer = new XMLPictureTilesSerializer();
mt = MediaType.TEXT_XML;
}
res.setEntity(serializer.serialize(tiles), mt);
res.getEntity().setCharacterSet(CharacterSet.UTF_8);
HttpServletResponse response = getHttpResponse(res);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
}
protected void handleNoTiles(Response res, Exception e) {
StringBuilder sb = new StringBuilder();
sb.append("<html><body><center><h1>");
if (e == null) {
sb.append("No Tiling is available for this document</h1>");
} else {
sb.append("Picture Tiling can not be generated for this document</h1>");
sb.append("<br/><pre>");
sb.append(e.toString());
sb.append("</pre>");
}
sb.append("</center></body></html>");
res.setEntity(sb.toString(), MediaType.TEXT_HTML);
HttpServletResponse response = getHttpResponse(res);
response.setHeader("Content-Disposition", "inline");
}
protected void updateCache(DocumentModel doc, PictureTilesAdapter adapter, String xpath) {
Calendar modified = (Calendar) doc.getProperty("dublincore", "modified");
PictureTilesCachedEntry entry = new PictureTilesCachedEntry(modified, adapter, xpath);
synchronized (cachedAdapters) {
cachedAdapters.put(doc.getId(), entry);
}
cacheGC();
}
protected void removeFromCache(String key) {
PictureTilesCachedEntry entry = cachedAdapters.get(key);
if (entry != null) {
entry.getAdapter().cleanup();
}
synchronized (cachedAdapters) {
cachedAdapters.remove(key);
}
}
protected boolean isSameDate(Calendar d1, Calendar d2) {
// because one of the date is stored in the repository
// the date may be 'rounded'
// so compare
long t1 = d1.getTimeInMillis() / 1000;
long t2 = d2.getTimeInMillis() / 1000;
return Math.abs(t1 - t2) <= 1;
}
protected PictureTilesAdapter getFromCache(DocumentModel doc, String xpath) {
if (cachedAdapters.containsKey(doc.getId())) {
if (xpath == null) {
xpath = "";
}
Calendar modified = (Calendar) doc.getProperty("dublincore", "modified");
PictureTilesCachedEntry entry = cachedAdapters.get(doc.getId());
if ((!isSameDate(entry.getModified(), modified)) || (!xpath.equals(entry.getXpath()))) {
removeFromCache(doc.getId());
return null;
} else {
return entry.getAdapter();
}
} else {
return null;
}
}
protected void cacheGC() {
for (String key : cachedAdapters.keySet()) {
long now = System.currentTimeMillis();
PictureTilesCachedEntry entry = cachedAdapters.get(key);
if ((now - entry.getTimeStamp()) > MAX_CACHE_LIFE * 1000) {
}
}
}
}