001/* 002 * (C) Copyright 2006-2012 Nuxeo SAS (http://nuxeo.com/) and contributors. 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.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 * Benjamin JALON <bjalon@nuxeo.com> 016 */ 017 018package org.nuxeo.ecm.mobile.webengine; 019 020import java.io.Serializable; 021import java.util.HashMap; 022import java.util.Map; 023import java.util.regex.Matcher; 024import java.util.regex.Pattern; 025 026import javax.ws.rs.GET; 027import javax.ws.rs.Path; 028import javax.ws.rs.PathParam; 029import javax.ws.rs.Produces; 030import javax.ws.rs.QueryParam; 031 032import org.apache.commons.lang.StringUtils; 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.ecm.core.api.CoreSession; 036import org.nuxeo.ecm.core.api.DocumentModel; 037import org.nuxeo.ecm.core.api.DocumentModelList; 038import org.nuxeo.ecm.core.api.DocumentRef; 039import org.nuxeo.ecm.core.api.IdRef; 040import org.nuxeo.ecm.core.api.NuxeoException; 041import org.nuxeo.ecm.core.api.PathRef; 042import org.nuxeo.ecm.mobile.webengine.document.MobileDocument; 043import org.nuxeo.ecm.platform.userworkspace.api.UserWorkspaceService; 044import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; 045import org.nuxeo.ecm.webengine.model.TypeNotFoundException; 046import org.nuxeo.ecm.webengine.model.WebObject; 047import org.nuxeo.ecm.webengine.model.impl.ModuleRoot; 048import org.nuxeo.runtime.api.Framework; 049 050import static org.nuxeo.ecm.mobile.filter.ApplicationRedirectionFilter.INITIAL_TARGET_URL_PARAM_NAME; 051import static org.nuxeo.ecm.mobile.webengine.adapter.DefaultMobileAdapter.ONLY_VISIBLE_CHILDREN; 052 053/** 054 * Entry point of the webengine application 055 * 056 * @author <a href="mailto:bjalon@nuxeo.com">Benjamin JALON</a> 057 * @since 5.5 058 */ 059@Path("mobile") 060@Produces("text/html;charset=UTF-8") 061@WebObject(type = "MobileApplication") 062public class MobileApplication extends ModuleRoot { 063 064 private static final Log log = LogFactory.getLog(MobileApplication.class); 065 066 private UserWorkspaceService userWorkspaceService; 067 068 protected static final Pattern CORDOVA_USER_AGENT_REGEXP = Pattern.compile("Cordova/(.+?) \\((.*)\\)"); 069 070 protected enum ToolbarPage { 071 HOME, BROWSE, PROFILE, SEARCH 072 } 073 074 @Override 075 protected void initialize(Object... args) { 076 // Check if the Client is using Cordova 077 // XXX Optimize it to prevent from regex all request 078 Map<String, Serializable> context = null; 079 080 String userAgent = getContext().getRequest().getHeader("User-Agent"); 081 if (StringUtils.isEmpty(userAgent)) { 082 log.debug("User-Agent empty: assuming not on a mobile device."); 083 return; 084 } 085 086 Matcher matcher = CORDOVA_USER_AGENT_REGEXP.matcher(userAgent); 087 if (matcher.find()) { 088 context = new HashMap<String, Serializable>(); 089 090 context.put("version", matcher.group(1)); 091 context.put("device", matcher.group(2)); 092 093 log.info("Cordova User-Agent detected"); 094 } 095 getContext().setProperty("Cordova", context); 096 } 097 098 /** 099 * Try to fetch document in targetURL parameter in URL if not this is the Home binding 100 */ 101 @GET 102 public Object doGet(@QueryParam(INITIAL_TARGET_URL_PARAM_NAME) String targetURL) { 103 104 DocumentRef targetRef = RedirectHelper.findDocumentRef(targetURL); 105 if (targetRef != null) { 106 setCurrentPage(ToolbarPage.BROWSE); 107 108 MobileDocument targetDoc = new MobileDocument(ctx, targetRef); 109 return targetDoc.doGet(); 110 } 111 112 // If SC mobile fragment is enable, redirect to the new homepage 113 if (getSocialObject() != null) { 114 return redirect(ctx.getServerURL() + ctx.getModulePath() + "/social"); 115 } 116 117 Map<String, Object> args = new HashMap<String, Object>(); 118 args.put("userWorkspace", getUserWorkspacesDocs()); 119 120 setCurrentPage(ToolbarPage.HOME); 121 return getView("index").args(args); 122 } 123 124 @Path("auth") 125 public Object doTraverseAuthentication() { 126 return ctx.newObject("WebMobileAuthentication"); 127 } 128 129 @Path("profile") 130 public Object doTraverseProfile() { 131 setCurrentPage(ToolbarPage.PROFILE); 132 return ctx.newObject("Profile"); 133 } 134 135 /** 136 * Generate the root view of the repository. First root descendant and user workspace are rendered 137 */ 138 @GET 139 @Path("root") 140 public Object getRootRepositoryView() { 141 Map<String, Object> args = new HashMap<String, Object>(); 142 143 CoreSession session = ctx.getCoreSession(); 144 145 DocumentModel doc = session.getRootDocument(); 146 DocumentModelList children; 147 do { 148 children = session.getChildren(doc.getRef(), null, ONLY_VISIBLE_CHILDREN, null); 149 if (children.size() == 1) { 150 doc = children.get(0); 151 } 152 } while (children.size() == 1); 153 args.put("domain", children); 154 155 setCurrentPage(ToolbarPage.BROWSE); 156 return getView("root").args(args); 157 } 158 159 @Path("docPath/@{adapter}") 160 public Object doTraverseRootDocumentByPath(@PathParam("adapter") String adapter) { 161 DocumentRef ref = new PathRef("/"); 162 setCurrentPage(ToolbarPage.BROWSE); 163 if ("search".equals(adapter)) { 164 return new MobileDocument(ctx, ref).search(); 165 } 166 return new MobileDocument(ctx, ref).disptachAdapter(adapter); 167 } 168 169 @Path("docPath{docPathValue:(/(?:(?!/@).)*)}") 170 public Object doTraverseDocumentByPath(@PathParam("docPathValue") String docPath) { 171 DocumentRef ref = new PathRef(docPath); 172 setCurrentPage(ToolbarPage.BROWSE); 173 return new MobileDocument(ctx, ref); 174 } 175 176 @Path("doc/{docId}") 177 public Object doTraverseDocument(@PathParam("docId") String docId) { 178 DocumentRef ref = new IdRef(docId); 179 setCurrentPage(ToolbarPage.BROWSE); 180 return new MobileDocument(ctx, ref); 181 } 182 183 @Path("search") 184 public Object doTraverseSearch() { 185 setCurrentPage(ToolbarPage.SEARCH); 186 return ctx.newObject("Search"); 187 } 188 189 @Path("task") 190 @Deprecated 191 // Since 5.6 with content routing. 192 public Object doTraverseTask() { 193 return null; 194 } 195 196 @Path("activity") 197 public Object doTraverseActivity() { 198 return ctx.newObject("Activity"); 199 } 200 201 @Path("social") 202 public Object doSocial() { 203 setCurrentPage(ToolbarPage.HOME); 204 return ctx.newObject("Social"); 205 } 206 207 protected void setCurrentPage(ToolbarPage page) { 208 getContext().setProperty("currentPage", page.name()); 209 } 210 211 protected DocumentModelList getUserWorkspacesDocs() { 212 CoreSession session = ctx.getCoreSession(); 213 DocumentModel userWorkspace = getUserWorkspaceService().getCurrentUserPersonalWorkspace(session, null); 214 return session.getChildren(userWorkspace.getRef(), null, ONLY_VISIBLE_CHILDREN, null); 215 } 216 217 protected Object getSocialObject() { 218 try { 219 return ctx.newObject("Social"); 220 } catch (TypeNotFoundException e) { 221 log.debug(e, e); 222 return null; 223 } 224 } 225 226 protected UserWorkspaceService getUserWorkspaceService() { 227 if (userWorkspaceService == null) { 228 userWorkspaceService = Framework.getLocalService(UserWorkspaceService.class); 229 } 230 return userWorkspaceService; 231 } 232 233 public String getNuxeoContextPath() { 234 return VirtualHostHelper.getBaseURL(request); 235 } 236 237 public String getDocumentMobileUrl(DocumentModel doc) { 238 String baseUrl = String.format("%s/doc/%s", getPath(), doc.getId()); 239 if (doc.isFolder()) { 240 baseUrl = String.format("%s/@folderish", baseUrl); 241 } 242 return baseUrl; 243 } 244}