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