001/*
002 * (C) Copyright 2006-2007 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 *     Nuxeo - initial API and implementation
016 *
017 * $Id: JOOoConvertPluginImpl.java 18651 2007-05-13 20:28:53Z sfermigier $
018 */
019
020package org.nuxeo.ecm.platform.ui.web.rest;
021
022import static org.jboss.seam.ScopeType.EVENT;
023
024import java.io.IOException;
025import java.io.Serializable;
026import java.util.HashMap;
027import java.util.Map;
028import java.util.regex.Pattern;
029
030import javax.faces.component.UIViewRoot;
031import javax.faces.context.FacesContext;
032import javax.servlet.ServletRequest;
033import javax.servlet.ServletResponse;
034import javax.servlet.http.HttpServletRequest;
035import javax.servlet.http.HttpServletResponse;
036
037import org.apache.commons.lang.StringUtils;
038import org.jboss.seam.ScopeType;
039import org.jboss.seam.annotations.Begin;
040import org.jboss.seam.annotations.Factory;
041import org.jboss.seam.annotations.In;
042import org.jboss.seam.annotations.Name;
043import org.jboss.seam.annotations.Scope;
044import org.jboss.seam.contexts.Contexts;
045import org.jboss.seam.core.Manager;
046import org.jboss.seam.international.LocaleSelector;
047import org.nuxeo.ecm.core.api.DocumentLocation;
048import org.nuxeo.ecm.core.api.DocumentModel;
049import org.nuxeo.ecm.core.api.DocumentRef;
050import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl;
051import org.nuxeo.ecm.platform.types.adapter.TypeInfo;
052import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
053import org.nuxeo.ecm.platform.ui.web.api.WebActions;
054import org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants;
055import org.nuxeo.ecm.platform.ui.web.tag.fn.DocumentModelFunctions;
056import org.nuxeo.ecm.platform.ui.web.util.BaseURL;
057import org.nuxeo.ecm.platform.url.DocumentViewImpl;
058import org.nuxeo.ecm.platform.url.api.DocumentView;
059import org.nuxeo.ecm.platform.util.RepositoryLocation;
060
061/**
062 * Helper for generation of URLs and related update of document context.
063 *
064 * @author tiry
065 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
066 * @author Florent Guillaume
067 */
068@Name("restHelper")
069@Scope(EVENT)
070public class RestHelper implements Serializable {
071
072    private static final long serialVersionUID = 1L;
073
074    @In(create = true)
075    protected transient NavigationContext navigationContext;
076
077    @In(create = true)
078    protected transient WebActions webActions;
079
080    protected DocumentView docView;
081
082    protected String baseURL = "";
083
084    @In(create = true)
085    protected transient LocaleSelector localeSelector;
086
087    /**
088     * Sets current server location (core repository) and core document as provided by the document view.
089     * <p>
090     * Only sets current server location if the document reference is null.
091     */
092    @Begin(id = "#{conversationIdGenerator.currentOrNewMainConversationId}", join = true)
093    public String initContextFromRestRequest(DocumentView docView) {
094        String outcome = null;
095
096        if (docView != null) {
097            DocumentLocation docLoc = docView.getDocumentLocation();
098            String serverName = docLoc.getServerName();
099            if (serverName != null) {
100                DocumentRef docRef = docLoc.getDocRef();
101                RepositoryLocation repoLoc = new RepositoryLocation(serverName);
102                if (docRef != null) {
103                    if (docView.getParameter(WebActions.MAIN_TAB_ID_PARAMETER) == null
104                            && !webActions.hasCurrentTabId(WebActions.MAIN_TABS_CATEGORY)) {
105                        webActions.setCurrentTabId(WebActions.MAIN_TABS_CATEGORY, WebActions.DOCUMENTS_MAIN_TAB_ID);
106                    }
107                    outcome = navigationContext.navigateTo(repoLoc, docRef);
108                } else {
109                    navigationContext.setCurrentServerLocation(repoLoc);
110                }
111            }
112            if (outcome == null) {
113                outcome = docView.getViewId();
114            }
115        }
116
117        return outcome;
118    }
119
120    public void setDocumentView(DocumentView docView) {
121        this.docView = docView;
122    }
123
124    public DocumentView getNewDocumentView(String mainTabId) {
125        DocumentView docView = null;
126        DocumentModel currentDocument = navigationContext.getCurrentDocument();
127        if (currentDocument != null) {
128            DocumentLocation docLoc = new DocumentLocationImpl(currentDocument);
129            TypeInfo typeInfo = currentDocument.getAdapter(TypeInfo.class);
130            Map<String, String> params = new HashMap<String, String>();
131            if (currentDocument.isVersion()) {
132                params.put("version", "true");
133            }
134            if (!StringUtils.isEmpty(mainTabId)) {
135                params.put(WebActions.MAIN_TAB_ID_PARAMETER, WebActions.MAIN_TABS_CATEGORY + ":" + mainTabId);
136            }
137            // additional params will be set according to the url pattern,
138            // calling getters on bindings.
139            docView = new DocumentViewImpl(docLoc, typeInfo.getDefaultView(), params);
140        }
141        return docView;
142    }
143
144    public DocumentView getNewDocumentView() {
145        return getNewDocumentView("documents");
146    }
147
148    public DocumentView getDocumentView() {
149        return docView;
150    }
151
152    /**
153     * @return the Seam conversation manager.
154     */
155    public static Manager getConversationManager() {
156        if (Contexts.isEventContextActive()) {
157            return Manager.instance();
158        }
159        return null;
160    }
161
162    protected static String addConversationRequestParameters(String url, Manager conversationManager,
163            String conversationId) {
164        Map<String, Object> params = new HashMap<String, Object>();
165        params.put(conversationManager.getConversationIdParameter(), conversationId);
166        return conversationManager.encodeParameters(url, params);
167    }
168
169    /**
170     * Adds current conversation request parameters to the given url.
171     *
172     * @param url
173     * @return the url with additional conversation request parameters
174     */
175    public static String addCurrentConversationParameters(String url) {
176        Manager conversationManager = getConversationManager();
177        if (conversationManager == null) {
178            return url;
179        }
180        // XXX : deprecated
181        return conversationManager.encodeConversationId(url);
182    }
183
184    /**
185     * Adds main conversation request parameters to the given url.
186     *
187     * @param url
188     * @return the url with additional conversation request parameters
189     */
190    public static String addMainConversationParameters(String url) {
191        Manager conversationManager = getConversationManager();
192        if (conversationManager == null) {
193            return url;
194        } else {
195            String conversationId;
196            if (conversationManager.isNestedConversation()) {
197                conversationId = conversationManager.getParentConversationId();
198            } else {
199                conversationId = conversationManager.getCurrentConversationId();
200            }
201            return addConversationRequestParameters(url, conversationManager, conversationId);
202        }
203    }
204
205    public String getDocumentUrl(DocumentModel doc) {
206        return DocumentModelFunctions.documentUrl(doc);
207    }
208
209    public String getDocumentUrl(DocumentModel doc, String viewId, boolean newConversation) {
210        return DocumentModelFunctions.documentUrl(null, doc, viewId, null, newConversation, BaseURL.getBaseURL());
211    }
212
213    public String getDocumentUrl(String patternName, DocumentModel doc, String viewId, Map<String, String> parameters,
214            boolean newConversation) {
215        return DocumentModelFunctions.documentUrl(patternName, doc, viewId, parameters, newConversation,
216                BaseURL.getBaseURL());
217    }
218
219    @Factory(value = "baseURL", scope = ScopeType.CONVERSATION)
220    public String getBaseURL() {
221        if (baseURL.equals("")) {
222            baseURL = BaseURL.getBaseURL();
223        }
224        return baseURL;
225    }
226
227    @Factory(value = "contextPath", scope = ScopeType.CONVERSATION)
228    public String getContextPath() {
229        return BaseURL.getContextPath();
230    }
231
232    public String doPrint(String defaultTheme) throws IOException {
233        return doPrint(navigationContext.getCurrentDocument(), defaultTheme);
234    }
235
236    public String doPrint(DocumentModel doc, String defaultTheme) throws IOException {
237        FacesContext facesContext = FacesContext.getCurrentInstance();
238        if (facesContext == null) {
239            return null;
240        }
241        HttpServletResponse response = getHttpServletResponse();
242        if (response != null) {
243            handleRedirect(response, getPrintUrl(doc, defaultTheme));
244        }
245        return null;
246    }
247
248    public String getPrintUrl(String defaultTheme) {
249        return getPrintUrl(navigationContext.getCurrentDocument(), defaultTheme);
250    }
251
252    public String getPrintUrl(DocumentModel doc, String defaultTheme) {
253        Map<String, String> parameters = new HashMap<String, String>();
254        int separatorIndex = defaultTheme.indexOf("/");
255        if (separatorIndex != -1) {
256            // defaultTheme includes the default page
257            defaultTheme = defaultTheme.substring(0, separatorIndex);
258            StringBuilder sb = new StringBuilder();
259            sb.append(defaultTheme);
260            sb.append("/print");
261            parameters.put("page", sb.toString());
262        }
263        return DocumentModelFunctions.documentUrl(null, doc, null, parameters, false, BaseURL.getBaseURL());
264    }
265
266    public static HttpServletResponse getHttpServletResponse() {
267        ServletResponse response = null;
268        final FacesContext facesContext = FacesContext.getCurrentInstance();
269        if (facesContext != null) {
270            response = (ServletResponse) facesContext.getExternalContext().getResponse();
271        }
272
273        if (response != null && response instanceof HttpServletResponse) {
274            return (HttpServletResponse) response;
275        }
276        return null;
277    }
278
279    public static HttpServletRequest getHttpServletRequest() {
280        ServletRequest request = null;
281        final FacesContext facesContext = FacesContext.getCurrentInstance();
282        if (facesContext != null) {
283            request = (ServletRequest) facesContext.getExternalContext().getRequest();
284        }
285
286        if (request != null && request instanceof HttpServletRequest) {
287            return (HttpServletRequest) request;
288        }
289        return null;
290    }
291
292    public static void handleRedirect(HttpServletResponse response, String url) throws IOException {
293        response.resetBuffer();
294        response.sendRedirect(url);
295        response.flushBuffer();
296        getHttpServletRequest().setAttribute(NXAuthConstants.DISABLE_REDIRECT_REQUEST_KEY, Boolean.TRUE);
297        FacesContext.getCurrentInstance().responseComplete();
298    }
299
300    /**
301     * Returns the locale string.
302     * <p>
303     * Useful for url pattern bindings.
304     *
305     * @since 5.4.2
306     */
307    public String getLocaleString() {
308        return localeSelector.getLocaleString();
309    }
310
311    public static final Pattern VALID_LOCALE = Pattern.compile("[A-Za-z0-9-_]*");
312
313    /**
314     * Sets the locale string if given string is not null and not empty, as well as on faces context view root in case
315     * it was already created so that it holds the new locale for future lookups by JSF components.
316     * <p>
317     * Useful for url pattern bindings.
318     */
319    public void setLocaleString(String localeString) {
320        // injected directly in JavaScript in a number of places, so should be sanitized
321        if (!StringUtils.isBlank(localeString) && VALID_LOCALE.matcher(localeString).matches()) {
322            localeSelector.setLocaleString(localeString.trim());
323            FacesContext ctx = FacesContext.getCurrentInstance();
324            if (ctx != null) {
325                UIViewRoot viewRoot = ctx.getViewRoot();
326                if (viewRoot != null) {
327                    viewRoot.setLocale(localeSelector.getLocale());
328                }
329            }
330        }
331    }
332
333}