/*
* (C) Copyright 2015-2016 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:
* Florent Guillaume
* Estelle Giuly <egiuly@nuxeo.com>
*/
package org.nuxeo.ecm.core.io.download;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URLEncoder;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Supplier;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.common.utils.URIUtils;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.CoreInstance;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentRef;
import org.nuxeo.ecm.core.api.DocumentSecurityException;
import org.nuxeo.ecm.core.api.IdRef;
import org.nuxeo.ecm.core.api.NuxeoException;
import org.nuxeo.ecm.core.api.NuxeoPrincipal;
import org.nuxeo.ecm.core.api.SystemPrincipal;
import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
import org.nuxeo.ecm.core.api.event.CoreEventConstants;
import org.nuxeo.ecm.core.api.local.ClientLoginModule;
import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
import org.nuxeo.ecm.core.blob.BlobManager.UsageHint;
import org.nuxeo.ecm.core.event.Event;
import org.nuxeo.ecm.core.event.EventContext;
import org.nuxeo.ecm.core.event.EventService;
import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
import org.nuxeo.ecm.core.event.impl.EventContextImpl;
import org.nuxeo.ecm.core.transientstore.api.TransientStore;
import org.nuxeo.ecm.core.transientstore.api.TransientStoreService;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.model.ComponentInstance;
import org.nuxeo.runtime.model.DefaultComponent;
import org.nuxeo.runtime.model.SimpleContributionRegistry;
import org.nuxeo.runtime.transaction.TransactionHelper;
/**
* This service allows the download of blobs to a HTTP response.
*
* @since 7.3
*/
public class DownloadServiceImpl extends DefaultComponent implements DownloadService {
private static final Log log = LogFactory.getLog(DownloadServiceImpl.class);
protected static final int DOWNLOAD_BUFFER_SIZE = 1024 * 512;
private static final String NUXEO_VIRTUAL_HOST = "nuxeo-virtual-host";
private static final String VH_PARAM = "nuxeo.virtual.host";
private static final String FORCE_NO_CACHE_ON_MSIE = "org.nuxeo.download.force.nocache.msie";
private static final String XP = "permissions";
private static final String REDIRECT_RESOLVER = "redirectResolver";
private static final String RUN_FUNCTION = "run";
protected static enum Action {DOWNLOAD, DOWNLOAD_FROM_DOC, INFO};
private DownloadPermissionRegistry registry = new DownloadPermissionRegistry();
private ScriptEngineManager scriptEngineManager;
protected RedirectResolver redirectResolver = new DefaultRedirectResolver();
protected List<RedirectResolverDescriptor> redirectResolverContributions = new ArrayList<>();
public static class DownloadPermissionRegistry extends SimpleContributionRegistry<DownloadPermissionDescriptor> {
@Override
public String getContributionId(DownloadPermissionDescriptor contrib) {
return contrib.getName();
}
@Override
public boolean isSupportingMerge() {
return true;
}
@Override
public DownloadPermissionDescriptor clone(DownloadPermissionDescriptor orig) {
return new DownloadPermissionDescriptor(orig);
}
@Override
public void merge(DownloadPermissionDescriptor src, DownloadPermissionDescriptor dst) {
dst.merge(src);
}
public DownloadPermissionDescriptor getDownloadPermissionDescriptor(String id) {
return getCurrentContribution(id);
}
/** Returns descriptors sorted by name. */
public List<DownloadPermissionDescriptor> getDownloadPermissionDescriptors() {
List<DownloadPermissionDescriptor> descriptors = new ArrayList<>(currentContribs.values());
Collections.sort(descriptors);
return descriptors;
}
}
public DownloadServiceImpl() {
scriptEngineManager = new ScriptEngineManager();
}
@Override
public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
if (XP.equals(extensionPoint)) {
DownloadPermissionDescriptor descriptor = (DownloadPermissionDescriptor) contribution;
registry.addContribution(descriptor);
} else if (REDIRECT_RESOLVER.equals(extensionPoint)) {
redirectResolver = ((RedirectResolverDescriptor) contribution).getObject();
// Save contribution
redirectResolverContributions.add((RedirectResolverDescriptor) contribution);
} else {
throw new UnsupportedOperationException(extensionPoint);
}
}
@Override
public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
if (XP.equals(extensionPoint)) {
DownloadPermissionDescriptor descriptor = (DownloadPermissionDescriptor) contribution;
registry.removeContribution(descriptor);
} else if (REDIRECT_RESOLVER.equals(extensionPoint)) {
redirectResolverContributions.remove(contribution);
if (redirectResolverContributions.size() == 0) {
// If no more custom contribution go back to the default one
redirectResolver = new DefaultRedirectResolver();
} else {
// Go back to the last contribution added
redirectResolver = redirectResolverContributions.get(redirectResolverContributions.size() - 1)
.getObject();
}
} else {
throw new UnsupportedOperationException(extensionPoint);
}
}
/**
* {@inheritDoc}
*
* Multipart download are not yet supported. You can only provide
* a blob singleton at this time.
*/
@Override
public String storeBlobs(List<Blob> blobs) {
if (blobs.size() > 1) {
throw new IllegalArgumentException("multipart download not yet implemented");
}
TransientStore ts = Framework.getService(TransientStoreService.class).getStore("download");
String storeKey = UUID.randomUUID().toString();
ts.putBlobs(storeKey, blobs);
return storeKey;
}
@Override
public String getDownloadUrl(DocumentModel doc, String xpath, String filename) {
return getDownloadUrl(doc.getRepositoryName(), doc.getId(), xpath, filename);
}
@Override
public String getDownloadUrl(String repositoryName, String docId, String xpath, String filename) {
StringBuilder sb = new StringBuilder();
sb.append(NXFILE);
sb.append("/").append(repositoryName);
sb.append("/").append(docId);
if (xpath != null) {
sb.append("/").append(xpath);
if (filename != null) {
sb.append("/").append(URIUtils.quoteURIPathComponent(filename, true));
}
}
return sb.toString();
}
@Override
public String getDownloadUrl(String storeKey) {
return NXBIGBLOB + "/" + storeKey;
}
/**
* Gets the download path and action of the URL to use to download blobs. For instance, from the path
* "nxfile/default/3727ef6b-cf8c-4f27-ab2c-79de0171a2c8/files:files/0/file/image.png", the pair
* ("default/3727ef6b-cf8c-4f27-ab2c-79de0171a2c8/files:files/0/file/image.png", Action.DOWNLOAD_FROM_DOC) is returned.
*
* @param path the path of the URL to use to download blobs
* @return the pair download path and action
* @since 9.1
*/
protected Pair<String, Action> getDownloadPathAndAction(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
int slash = path.indexOf('/');
if (slash < 0) {
return null;
}
String type = path.substring(0, slash);
String downloadPath = path.substring(slash + 1);
switch (type) {
case NXDOWNLOADINFO:
// used by nxdropout.js
return Pair.of(downloadPath, Action.INFO);
case NXFILE:
case NXBIGFILE:
return Pair.of(downloadPath, Action.DOWNLOAD_FROM_DOC);
case NXBIGZIPFILE:
case NXBIGBLOB:
return Pair.of(downloadPath, Action.DOWNLOAD);
default:
return null;
}
}
@Override
public Blob resolveBlobFromDownloadUrl(String url) {
String nuxeoUrl = Framework.getProperty("nuxeo.url");
if (!url.startsWith(nuxeoUrl)) {
return null;
}
String path = url.substring(nuxeoUrl.length() + 1);
Pair<String, Action> pair = getDownloadPathAndAction(path);
if (pair == null) {
return null;
}
String downloadPath = pair.getLeft();
try {
DownloadBlobInfo downloadBlobInfo = new DownloadBlobInfo(downloadPath);
try (CoreSession session = CoreInstance.openCoreSession(downloadBlobInfo.repository)) {
DocumentRef docRef = new IdRef(downloadBlobInfo.docId);
if (!session.exists(docRef)) {
return null;
}
DocumentModel doc = session.getDocument(docRef);
return resolveBlob(doc, downloadBlobInfo.xpath);
}
} catch (IllegalArgumentException e) {
return null;
}
}
@Override
public void handleDownload(HttpServletRequest req, HttpServletResponse resp, String baseUrl, String path)
throws IOException {
Pair<String, Action> pair = getDownloadPathAndAction(path);
if (pair == null) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid URL syntax");
return;
}
String downloadPath = pair.getLeft();
Action action = pair.getRight();
switch (action) {
case INFO:
handleDownload(req, resp, downloadPath, baseUrl, true);
break;
case DOWNLOAD_FROM_DOC:
handleDownload(req, resp, downloadPath, baseUrl, false);
break;
case DOWNLOAD:
downloadBlob(req, resp, downloadPath, "download");
break;
default:
resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid URL syntax");
}
}
protected void handleDownload(HttpServletRequest req, HttpServletResponse resp, String downloadPath, String baseUrl,
boolean info) throws IOException {
boolean tx = false;
DownloadBlobInfo downloadBlobInfo;
try {
downloadBlobInfo = new DownloadBlobInfo(downloadPath);
} catch (IllegalArgumentException e) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid URL syntax");
return;
}
try {
if (!TransactionHelper.isTransactionActive()) {
// Manually start and stop a transaction around repository access to be able to release transactional
// resources without waiting for the download that can take a long time (longer than the transaction
// timeout) especially if the client or the connection is slow.
tx = TransactionHelper.startTransaction();
}
String xpath = downloadBlobInfo.xpath;
String filename = downloadBlobInfo.filename;
try (CoreSession session = CoreInstance.openCoreSession(downloadBlobInfo.repository)) {
DocumentRef docRef = new IdRef(downloadBlobInfo.docId);
if (!session.exists(docRef)) {
// Send a security exception to force authentication, if the current user is anonymous
Principal principal = req.getUserPrincipal();
if (principal instanceof NuxeoPrincipal) {
NuxeoPrincipal nuxeoPrincipal = (NuxeoPrincipal) principal;
if (nuxeoPrincipal.isAnonymous()) {
throw new DocumentSecurityException(
"Authentication is needed for downloading the blob");
}
}
resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No document found");
return;
}
DocumentModel doc = session.getDocument(docRef);
if (info) {
Blob blob = resolveBlob(doc, xpath);
if (blob == null) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No blob found");
return;
}
String downloadUrl = baseUrl + getDownloadUrl(doc, xpath, filename);
String result = blob.getMimeType() + ':' + URLEncoder.encode(blob.getFilename(), "UTF-8") + ':'
+ downloadUrl;
resp.setContentType("text/plain");
resp.getWriter().write(result);
resp.getWriter().flush();
} else {
downloadBlob(req, resp, doc, xpath, null, filename, "download");
}
}
} catch (NuxeoException e) {
if (tx) {
TransactionHelper.setTransactionRollbackOnly();
}
throw new IOException(e);
} finally {
if (tx) {
TransactionHelper.commitOrRollbackTransaction();
}
}
}
@Override
public void downloadBlob(HttpServletRequest request, HttpServletResponse response, String key, String reason) throws IOException {
TransientStore ts = Framework.getService(TransientStoreService.class).getStore("download");
try {
List<Blob> blobs = ts.getBlobs(key);
if (blobs == null) {
throw new IllegalArgumentException("no such blobs referenced with " + key);
}
if (blobs.size() > 1) {
throw new IllegalArgumentException("multipart download not yet implemented");
}
Blob blob = blobs.get(0);
downloadBlob(request, response, null, null, blob, blob.getFilename(), reason);
} finally {
ts.remove(key);
}
}
@Override
public void downloadBlob(HttpServletRequest request, HttpServletResponse response, DocumentModel doc, String xpath,
Blob blob, String filename, String reason) throws IOException {
downloadBlob(request, response, doc, xpath, blob, filename, reason, Collections.emptyMap());
}
@Override
public void downloadBlob(HttpServletRequest request, HttpServletResponse response, DocumentModel doc, String xpath,
Blob blob, String filename, String reason, Map<String, Serializable> extendedInfos) throws IOException {
downloadBlob(request, response, doc, xpath, blob, filename, reason, extendedInfos, null);
}
@Override
public void downloadBlob(HttpServletRequest request, HttpServletResponse response, DocumentModel doc, String xpath,
Blob blob, String filename, String reason, Map<String, Serializable> extendedInfos, Boolean inline)
throws IOException {
if (blob == null) {
if (doc == null || xpath == null) {
throw new NuxeoException("No blob or doc xpath");
}
blob = resolveBlob(doc, xpath);
if (blob == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "No blob found");
return;
}
}
final Blob fblob = blob;
downloadBlob(request, response, doc, xpath, blob, filename, reason, extendedInfos, inline,
byteRange -> transferBlobWithByteRange(fblob, byteRange, response));
}
@Override
public void downloadBlob(HttpServletRequest request, HttpServletResponse response, DocumentModel doc, String xpath,
Blob blob, String filename, String reason, Map<String, Serializable> extendedInfos, Boolean inline,
Consumer<ByteRange> blobTransferer) throws IOException {
Objects.requireNonNull(blob);
// check blob permissions
if (!checkPermission(doc, xpath, blob, reason, extendedInfos)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Permission denied");
return;
}
// check Blob Manager external download link
URI uri = redirectResolver.getURI(blob, UsageHint.DOWNLOAD, request);
if (uri != null) {
try {
Map<String, Serializable> ei = new HashMap<>();
if (extendedInfos != null) {
ei.putAll(extendedInfos);
}
ei.put("redirect", uri.toString());
logDownload(doc, xpath, filename, reason, ei);
response.sendRedirect(uri.toString());
} catch (IOException ioe) {
DownloadHelper.handleClientDisconnect(ioe);
}
return;
}
try {
String digest = blob.getDigest();
if (digest == null) {
digest = DigestUtils.md5Hex(blob.getStream());
}
String etag = '"' + digest + '"'; // with quotes per RFC7232 2.3
response.setHeader("ETag", etag); // re-send even on SC_NOT_MODIFIED
addCacheControlHeaders(request, response);
String ifNoneMatch = request.getHeader("If-None-Match");
if (ifNoneMatch != null) {
boolean match = false;
if (ifNoneMatch.equals("*")) {
match = true;
} else {
for (String previousEtag : StringUtils.split(ifNoneMatch, ", ")) {
if (previousEtag.equals(etag)) {
match = true;
break;
}
}
}
if (match) {
String method = request.getMethod();
if (method.equals("GET") || method.equals("HEAD")) {
response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
} else {
// per RFC7232 3.2
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
}
return;
}
}
// regular processing
if (StringUtils.isBlank(filename)) {
filename = StringUtils.defaultIfBlank(blob.getFilename(), "file");
}
String contentDisposition = DownloadHelper.getRFC2231ContentDisposition(request, filename, inline);
response.setHeader("Content-Disposition", contentDisposition);
response.setContentType(blob.getMimeType());
if (blob.getEncoding() != null) {
response.setCharacterEncoding(blob.getEncoding());
}
long length = blob.getLength();
response.setHeader("Accept-Ranges", "bytes");
String range = request.getHeader("Range");
ByteRange byteRange;
if (StringUtils.isBlank(range)) {
byteRange = null;
} else {
byteRange = DownloadHelper.parseRange(range, length);
if (byteRange == null) {
log.error("Invalid byte range received: " + range);
} else {
response.setHeader("Content-Range", "bytes " + byteRange.getStart() + "-" + byteRange.getEnd()
+ "/" + length);
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
}
}
long contentLength = byteRange == null ? length : byteRange.getLength();
if (contentLength < Integer.MAX_VALUE) {
response.setContentLength((int) contentLength);
}
logDownload(doc, xpath, filename, reason, extendedInfos);
// execute the final download
blobTransferer.accept(byteRange);
} catch (UncheckedIOException e) {
DownloadHelper.handleClientDisconnect(e.getCause());
} catch (IOException ioe) {
DownloadHelper.handleClientDisconnect(ioe);
}
}
protected void transferBlobWithByteRange(Blob blob, ByteRange byteRange, HttpServletResponse response)
throws UncheckedIOException {
transferBlobWithByteRange(blob, byteRange, () -> {
try {
return response.getOutputStream();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
try {
response.flushBuffer();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public void transferBlobWithByteRange(Blob blob, ByteRange byteRange, Supplier<OutputStream> outputStreamSupplier)
throws UncheckedIOException {
try (InputStream in = blob.getStream()) {
@SuppressWarnings("resource")
OutputStream out = outputStreamSupplier.get(); // not ours to close
BufferingServletOutputStream.stopBuffering(out);
if (byteRange == null) {
IOUtils.copy(in, out);
} else {
IOUtils.copyLarge(in, out, byteRange.getStart(), byteRange.getLength());
}
out.flush();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
protected String fixXPath(String xpath) {
// Hack for Flash Url wich doesn't support ':' char
return xpath == null ? null : xpath.replace(';', ':');
}
@Override
public Blob resolveBlob(DocumentModel doc, String xpath) {
xpath = fixXPath(xpath);
Blob blob;
if (xpath.startsWith(BLOBHOLDER_PREFIX)) {
BlobHolder bh = doc.getAdapter(BlobHolder.class);
if (bh == null) {
log.debug("Not a BlobHolder");
return null;
}
String suffix = xpath.substring(BLOBHOLDER_PREFIX.length());
int index;
try {
index = Integer.parseInt(suffix);
} catch (NumberFormatException e) {
log.debug(e.getMessage());
return null;
}
if (!suffix.equals(Integer.toString(index))) {
// attempt to use a non-canonical integer, could be used to bypass
// a permission function checking just "blobholder:1" and receiving "blobholder:01"
log.debug("Non-canonical index: " + suffix);
return null;
}
if (index == 0) {
blob = bh.getBlob();
} else {
blob = bh.getBlobs().get(index);
}
} else {
if (!xpath.contains(":")) {
// attempt to use a xpath not prefix-qualified, could be used to bypass
// a permission function checking just "file:content" and receiving "content"
log.debug("Non-canonical xpath: " + xpath);
return null;
}
try {
blob = (Blob) doc.getPropertyValue(xpath);
} catch (PropertyNotFoundException e) {
log.debug(e.getMessage());
return null;
}
}
return blob;
}
@Override
public boolean checkPermission(DocumentModel doc, String xpath, Blob blob, String reason,
Map<String, Serializable> extendedInfos) {
List<DownloadPermissionDescriptor> descriptors = registry.getDownloadPermissionDescriptors();
if (descriptors.isEmpty()) {
return true;
}
xpath = fixXPath(xpath);
Map<String, Object> context = new HashMap<>();
Map<String, Serializable> ei = extendedInfos == null ? Collections.emptyMap() : extendedInfos;
NuxeoPrincipal currentUser = ClientLoginModule.getCurrentPrincipal();
context.put("Document", doc);
context.put("XPath", xpath);
context.put("Blob", blob);
context.put("Reason", reason);
context.put("Infos", ei);
context.put("Rendition", ei.get("rendition"));
context.put("CurrentUser", currentUser);
for (DownloadPermissionDescriptor descriptor : descriptors) {
ScriptEngine engine = scriptEngineManager.getEngineByName(descriptor.getScriptLanguage());
if (engine == null) {
throw new NuxeoException("Engine not found for language: " + descriptor.getScriptLanguage()
+ " in permission: " + descriptor.getName());
}
if (!(engine instanceof Invocable)) {
throw new NuxeoException("Engine " + engine.getClass().getName() + " not Invocable for language: "
+ descriptor.getScriptLanguage() + " in permission: " + descriptor.getName());
}
Object result;
try {
engine.eval(descriptor.getScript());
engine.getBindings(ScriptContext.ENGINE_SCOPE).putAll(context);
result = ((Invocable) engine).invokeFunction(RUN_FUNCTION);
} catch (NoSuchMethodException e) {
throw new NuxeoException("Script does not contain function: " + RUN_FUNCTION + "() in permission: "
+ descriptor.getName(), e);
} catch (ScriptException e) {
log.error("Failed to evaluate script: " + descriptor.getName(), e);
continue;
}
if (!(result instanceof Boolean)) {
log.error("Failed to get boolean result from permission: " + descriptor.getName() + " (" + result + ")");
continue;
}
boolean allow = ((Boolean) result).booleanValue();
if (!allow) {
return false;
}
}
return true;
}
/**
* Internet Explorer file downloads over SSL do not work with certain HTTP cache control headers
* <p>
* See http://support.microsoft.com/kb/323308/
* <p>
* What is not mentioned in the above Knowledge Base is that "Pragma: no-cache" also breaks download in MSIE over
* SSL
*/
protected void addCacheControlHeaders(HttpServletRequest request, HttpServletResponse response) {
String userAgent = request.getHeader("User-Agent");
boolean secure = request.isSecure();
if (!secure) {
String nvh = request.getHeader(NUXEO_VIRTUAL_HOST);
if (nvh == null) {
nvh = Framework.getProperty(VH_PARAM);
}
if (nvh != null) {
secure = nvh.startsWith("https");
}
}
if (userAgent != null && userAgent.contains("MSIE") && (secure || forceNoCacheOnMSIE())) {
String cacheControl = "max-age=15, must-revalidate";
log.debug("Setting Cache-Control: " + cacheControl);
response.setHeader("Cache-Control", cacheControl);
}
}
protected static boolean forceNoCacheOnMSIE() {
// see NXP-7759
return Framework.isBooleanPropertyTrue(FORCE_NO_CACHE_ON_MSIE);
}
@Override
public void logDownload(DocumentModel doc, String xpath, String filename, String reason,
Map<String, Serializable> extendedInfos) {
EventService eventService = Framework.getService(EventService.class);
if (eventService == null) {
return;
}
EventContext ctx;
if (doc != null) {
@SuppressWarnings("resource")
CoreSession session = doc.getCoreSession();
Principal principal = session == null ? getPrincipal() : session.getPrincipal();
ctx = new DocumentEventContext(session, principal, doc);
ctx.setProperty(CoreEventConstants.REPOSITORY_NAME, doc.getRepositoryName());
ctx.setProperty(CoreEventConstants.SESSION_ID, doc.getSessionId());
} else {
ctx = new EventContextImpl(null, getPrincipal());
}
Map<String, Serializable> map = new HashMap<>();
map.put("blobXPath", xpath);
map.put("blobFilename", filename);
map.put("downloadReason", reason);
if (extendedInfos != null) {
map.putAll(extendedInfos);
}
ctx.setProperty("extendedInfos", (Serializable) map);
ctx.setProperty("comment", filename);
Event event = ctx.newEvent(EVENT_NAME);
eventService.fireEvent(event);
}
protected static NuxeoPrincipal getPrincipal() {
NuxeoPrincipal principal = ClientLoginModule.getCurrentPrincipal();
if (principal == null) {
if (!Framework.isTestModeSet()) {
throw new NuxeoException("Missing security context, login() not done");
}
principal = new SystemPrincipal(null);
}
return principal;
}
}