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}