001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Michal Obrebski - Nuxeo 016 */ 017 018package org.nuxeo.easyshare; 019 020 021import java.util.HashMap; 022import java.util.Hashtable; 023import java.util.Map; 024 025import javax.ws.rs.GET; 026import javax.ws.rs.Path; 027import javax.ws.rs.PathParam; 028import javax.ws.rs.Produces; 029import javax.ws.rs.core.Response; 030 031import org.apache.commons.logging.Log; 032import org.apache.commons.logging.LogFactory; 033import org.nuxeo.ecm.automation.AutomationService; 034import org.nuxeo.ecm.automation.OperationChain; 035import org.nuxeo.ecm.automation.OperationContext; 036import org.nuxeo.ecm.automation.jaxrs.io.documents.PaginableDocumentModelListImpl; 037import org.nuxeo.ecm.core.api.Blob; 038import org.nuxeo.ecm.core.api.NuxeoException; 039import org.nuxeo.ecm.core.api.CoreSession; 040import org.nuxeo.ecm.core.api.DocumentModel; 041import org.nuxeo.ecm.core.api.IdRef; 042import org.nuxeo.ecm.core.api.blobholder.BlobHolder; 043import org.nuxeo.ecm.platform.ec.notification.NotificationConstants; 044import org.nuxeo.ecm.platform.ec.notification.email.EmailHelper; 045import org.nuxeo.ecm.platform.ec.notification.service.NotificationServiceHelper; 046import org.nuxeo.ecm.platform.notification.api.Notification; 047import org.nuxeo.ecm.webengine.model.WebObject; 048import org.nuxeo.ecm.webengine.model.impl.ModuleRoot; 049import org.nuxeo.runtime.api.Framework; 050 051import javax.ws.rs.DefaultValue; 052import javax.ws.rs.QueryParam; 053 054import java.util.Date; 055 056/** 057 * The root entry for the WebEngine module. 058 * 059 * @author mikeobrebski 060 */ 061@Path("/easyshare") 062@Produces("text/html;charset=UTF-8") 063@WebObject(type = "EasyShare") 064public class EasyShare extends ModuleRoot { 065 066 private static final String DEFAULT_PAGE_INDEX = "0"; 067 private static final Long PAGE_SIZE = 20L; 068 private static final String SHARE_DOC_TYPE = "EasyShareFolder"; 069 private static AutomationService automationService; 070 protected final Log log = LogFactory.getLog(EasyShare.class); 071 072 @GET 073 public Object doGet() { 074 return getView("index"); 075 } 076 077 public EasyShareUnrestrictedRunner buildUnrestrictedRunner(final String docId, final Long pageIndex) { 078 079 return new EasyShareUnrestrictedRunner() { 080 @Override 081 public Object run(CoreSession session, IdRef docRef) throws NuxeoException { 082 if (session.exists(docRef)) { 083 DocumentModel docShare = session.getDocument(docRef); 084 085 if (!SHARE_DOC_TYPE.equals(docShare.getType())) { 086 return Response.serverError().status(Response.Status.NOT_FOUND).build(); 087 } 088 089 if (!checkIfShareIsValid(docShare)) { 090 return getView("expired").arg("docShare", docShare); 091 } 092 093 DocumentModel document = session.getDocument(new IdRef(docId)); 094 095 String query = buildQuery(document); 096 097 if (query == null) { 098 return getView("denied"); 099 } 100 101 try { 102 103 OperationContext opCtx = new OperationContext(session); 104 OperationChain chain = new OperationChain("getEasyShareContent"); 105 chain.add("Document.Query") 106 .set("query", query) 107 .set("currentPageIndex", pageIndex) 108 .set("pageSize", PAGE_SIZE); 109 110 PaginableDocumentModelListImpl paginable = (PaginableDocumentModelListImpl) getAutomationService().run(opCtx, chain); 111 112 OperationContext ctx = new OperationContext(session); 113 ctx.setInput(docShare); 114 115 // Audit Log 116 Map<String, Object> params = new HashMap<>(); 117 params.put("event", "Access"); 118 params.put("category", "Document"); 119 params.put("comment", "IP: " + getIpAddr()); 120 getAutomationService().run(ctx, "Audit.Log", params); 121 122 return getView("folderList") 123 .arg("isFolder", document.isFolder() && !SHARE_DOC_TYPE.equals(document.getType())) //Backward compatibility to non-collection 124 .arg("currentPageIndex", paginable.getCurrentPageIndex()) 125 .arg("numberOfPages", paginable.getNumberOfPages()) 126 .arg("docShare", docShare) 127 .arg("docList", paginable) 128 .arg("previousPageAvailable", paginable.isPreviousPageAvailable()) 129 .arg("nextPageAvailable", paginable.isNextPageAvailable()) 130 .arg("currentPageStatus", paginable.getProvider().getCurrentPageStatus()); 131 132 } catch (Exception ex) { 133 log.error(ex.getMessage()); 134 return getView("denied"); 135 } 136 137 } else { 138 return getView("denied"); 139 } 140 } 141 }; 142 } 143 144 145 protected static String buildQuery(DocumentModel documentModel) { 146 147 //Backward compatibility to non-collection 148 if (documentModel.isFolder() && !SHARE_DOC_TYPE.equals(documentModel.getType())) { 149 return " SELECT * FROM Document WHERE ecm:parentId = '" + documentModel.getId() + "' AND " + 150 "ecm:mixinType != 'HiddenInNavigation' AND " + 151 "ecm:mixinType != 'NotCollectionMember' AND " + 152 "ecm:isCheckedInVersion = 0 AND " + 153 "ecm:currentLifeCycleState != 'deleted'" 154 + "ORDER BY dc:title"; 155 156 } else if (SHARE_DOC_TYPE.equals(documentModel.getType())) { 157 return "SELECT * FROM Document where ecm:mixinType != 'HiddenInNavigation' AND " + 158 "ecm:isCheckedInVersion = 0 AND ecm:currentLifeCycleState != 'deleted' " + 159 "AND collectionMember:collectionIds/* = '" + documentModel.getId() + "'" + 160 "OR ecm:parentId = '" + documentModel.getId() + "'" 161 + "ORDER BY dc:title"; 162 } 163 return null; 164 } 165 166 private boolean checkIfShareIsValid(DocumentModel docShare) { 167 Date today = new Date(); 168 if (today.after(docShare.getProperty("dc:expired").getValue(Date.class))) { 169 170 //Email notification 171 Map<String, Object> mail = new HashMap<>(); 172 sendNotification("easyShareExpired", docShare, mail); 173 174 return false; 175 } 176 return true; 177 } 178 179 180 private static AutomationService getAutomationService() { 181 if (automationService == null) { 182 automationService = Framework.getService(AutomationService.class); 183 } 184 return automationService; 185 } 186 187 188 @Path("{shareId}/{folderId}") 189 @GET 190 public Object getFolderListing(@PathParam("shareId") String shareId, @PathParam("folderId") final String folderId, 191 @DefaultValue(DEFAULT_PAGE_INDEX) @QueryParam("p") final Long pageIndex) { 192 return buildUnrestrictedRunner(folderId, pageIndex).runUnrestricted(shareId); 193 } 194 195 @Path("{shareId}") 196 @GET 197 public Object getShareListing(@PathParam("shareId") String shareId, 198 @DefaultValue(DEFAULT_PAGE_INDEX) @QueryParam("p") Long pageIndex) { 199 return buildUnrestrictedRunner(shareId, pageIndex).runUnrestricted(shareId); 200 } 201 202 public String getFileName(DocumentModel doc) throws NuxeoException { 203 BlobHolder blobHolder = doc.getAdapter(BlobHolder.class); 204 if (blobHolder != null && blobHolder.getBlob() != null) { 205 return blobHolder.getBlob().getFilename(); 206 } 207 return doc.getName(); 208 } 209 210 @GET 211 @Path("{shareId}/{fileId}/{fileName}") 212 public Response getFileStream(@PathParam("shareId") final String shareId, @PathParam("fileId") String fileId) throws NuxeoException { 213 214 return (Response) new EasyShareUnrestrictedRunner() { 215 @Override 216 public Object run(CoreSession session, IdRef docRef) throws NuxeoException { 217 if (session.exists(docRef)) { 218 try { 219 DocumentModel doc = session.getDocument(docRef); 220 DocumentModel docShare = session.getDocument(new IdRef(shareId)); 221 222 if (!checkIfShareIsValid(docShare)) { 223 return Response.serverError().status(Response.Status.NOT_FOUND).build(); 224 } 225 226 Blob blob = doc.getAdapter(BlobHolder.class).getBlob(); 227 228 // Audit Log 229 OperationContext ctx = new OperationContext(session); 230 ctx.setInput(doc); 231 232 // Audit.Log automation parameter setting 233 Map<String, Object> params = new HashMap<>(); 234 params.put("event", "Download"); 235 params.put("category", "Document"); 236 params.put("comment", "IP: " + getIpAddr()); 237 AutomationService service = Framework.getLocalService(AutomationService.class); 238 service.run(ctx, "Audit.Log", params); 239 240 if (doc.isProxy()) { 241 DocumentModel liveDoc = session.getSourceDocument(docRef); 242 ctx.setInput(liveDoc); 243 service.run(ctx, "Audit.Log", params); 244 245 } 246 247 // Email notification 248 Map<String, Object> mail = new HashMap<>(); 249 mail.put("filename", blob.getFilename()); 250 sendNotification("easyShareDownload", docShare, mail); 251 252 return Response.ok(blob.getStream(), blob.getMimeType()).build(); 253 254 } catch (Exception ex) { 255 log.error("error ", ex); 256 return Response.serverError().status(Response.Status.NOT_FOUND).build(); 257 } 258 259 } else { 260 return Response.serverError().status(Response.Status.NOT_FOUND).build(); 261 } 262 } 263 }.runUnrestricted(fileId); 264 265 } 266 267 public void sendNotification(String notification, DocumentModel docShare, Map<String, Object> mail) { 268 269 Boolean hasNotification = docShare.getProperty("eshare:hasNotification").getValue(Boolean.class); 270 271 if (hasNotification) { 272 //Email notification 273 String email = docShare.getProperty("eshare:contactEmail").getValue(String.class); 274 try { 275 log.debug("Easyshare: starting email"); 276 EmailHelper emailHelper = new EmailHelper(); 277 Map<String, Object> mailProps = new Hashtable<>(); 278 mailProps.put("mail.from", Framework.getProperty("mail.from", "system@nuxeo.com")); 279 mailProps.put("mail.to", email); 280 mailProps.put("ip", getIpAddr()); 281 mailProps.put("docShare", docShare); 282 283 try { 284 Notification notif = NotificationServiceHelper.getNotificationService().getNotificationByName(notification); 285 286 if (notif.getSubjectTemplate() != null) { 287 mailProps.put(NotificationConstants.SUBJECT_TEMPLATE_KEY, notif.getSubjectTemplate()); 288 } 289 290 mailProps.put(NotificationConstants.SUBJECT_KEY, NotificationServiceHelper.getNotificationService().getEMailSubjectPrefix() + " " + notif.getSubject()); 291 mailProps.put(NotificationConstants.TEMPLATE_KEY, notif.getTemplate()); 292 293 mailProps.putAll(mail); 294 295 emailHelper.sendmail(mailProps); 296 297 } catch (NuxeoException e) { 298 log.warn(e.getMessage()); 299 } 300 301 log.debug("Easyshare: completed email"); 302 } catch (Exception ex) { 303 log.error("Cannot send easyShare notification email", ex); 304 } 305 } 306 } 307 308 309 protected String getIpAddr() { 310 String ip = request.getHeader("X-FORWARDED-FOR"); 311 if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 312 ip = request.getHeader("Proxy-Client-IP"); 313 } 314 if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 315 ip = request.getRemoteAddr(); 316 } 317 return ip; 318 } 319}