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}