001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ui.apps;
029
030import org.opencms.file.CmsObject;
031import org.opencms.main.CmsBroadcast;
032import org.opencms.main.CmsLog;
033import org.opencms.main.CmsSessionInfo;
034import org.opencms.main.OpenCms;
035import org.opencms.security.CmsRole;
036import org.opencms.ui.A_CmsUI;
037import org.opencms.ui.CmsUserIconHelper;
038import org.opencms.ui.CmsVaadinErrorHandler;
039import org.opencms.ui.CmsVaadinUtils;
040import org.opencms.ui.I_CmsAppView;
041import org.opencms.ui.apps.CmsAppView.CacheStatus;
042import org.opencms.ui.apps.CmsWorkplaceAppManager.NavigationState;
043import org.opencms.ui.components.I_CmsWindowCloseListener;
044import org.opencms.ui.components.extensions.CmsHistoryExtension;
045import org.opencms.ui.components.extensions.CmsPollServerExtension;
046import org.opencms.ui.components.extensions.CmsWindowCloseExtension;
047import org.opencms.ui.login.CmsLoginHelper;
048import org.opencms.util.CmsExpiringValue;
049import org.opencms.util.CmsStringUtil;
050
051import java.util.HashMap;
052import java.util.HashSet;
053import java.util.Locale;
054import java.util.Map;
055import java.util.Set;
056
057import org.apache.commons.collections.Buffer;
058import org.apache.commons.logging.Log;
059
060import com.vaadin.annotations.Theme;
061import com.vaadin.navigator.NavigationStateManager;
062import com.vaadin.navigator.Navigator;
063import com.vaadin.navigator.View;
064import com.vaadin.navigator.ViewChangeListener;
065import com.vaadin.navigator.ViewDisplay;
066import com.vaadin.navigator.ViewProvider;
067import com.vaadin.server.Extension;
068import com.vaadin.server.Page;
069import com.vaadin.server.Page.BrowserWindowResizeEvent;
070import com.vaadin.server.Page.BrowserWindowResizeListener;
071import com.vaadin.server.VaadinRequest;
072import com.vaadin.ui.AbstractComponent;
073import com.vaadin.ui.Component;
074import com.vaadin.ui.Notification;
075import com.vaadin.ui.Notification.Type;
076
077/**
078 * The workplace ui.<p>
079 */
080@Theme("opencms")
081public class CmsAppWorkplaceUi extends A_CmsUI
082implements ViewDisplay, ViewProvider, ViewChangeListener, I_CmsWindowCloseListener, BrowserWindowResizeListener {
083
084    /**
085     * View which directly changes the state to the launchpad.<p>
086     */
087    class LaunchpadRedirectView implements View {
088
089        /** Serial version id. */
090        private static final long serialVersionUID = 1L;
091
092        /**
093         * @see com.vaadin.navigator.View#enter(com.vaadin.navigator.ViewChangeListener.ViewChangeEvent)
094         */
095        public void enter(ViewChangeEvent event) {
096
097            A_CmsUI.get().getNavigator().navigateTo(CmsAppHierarchyConfiguration.APP_ID);
098        }
099    }
100
101    /** The OpenCms window title prefix. */
102    public static final String WINDOW_TITLE_PREFIX = "OpenCms - ";
103
104    /** The workplace app id separator. */
105    public static final String WORKPLACE_APP_ID_SEPARATOR = "#!";
106
107    /** The workplace state separator. */
108    public static final String WORKPLACE_STATE_SEPARATOR = "/";
109
110    /** Logger instance for this class. */
111    private static final Log LOG = CmsLog.getLog(CmsAppWorkplaceUi.class);
112
113    /** The serial version id. */
114    private static final long serialVersionUID = -5606711048683809028L;
115
116    /** Launch pad redirect view. */
117    protected View m_launchRedirect = new LaunchpadRedirectView();
118
119    /** The cached views. */
120    private Map<String, I_CmsAppView> m_cachedViews;
121
122    /** The current view in case it implements view change listener. */
123    private View m_currentView;
124
125    /** The history extension. */
126    private CmsHistoryExtension m_history;
127
128    /** Cache for workplace locale. */
129    private CmsExpiringValue<Locale> m_localeCache = new CmsExpiringValue<Locale>(1000);
130
131    /** The navigation state manager. */
132    private NavigationStateManager m_navigationStateManager;
133
134    /** Currently refreshing? */
135    private boolean m_refreshing;
136
137    /**
138     * Constructor.<p>
139     */
140    public CmsAppWorkplaceUi() {
141
142        m_cachedViews = new HashMap<String, I_CmsAppView>();
143    }
144
145    /**
146     * Gets the current UI instance.<p>
147     *
148     * @return the current UI instance
149     */
150    public static CmsAppWorkplaceUi get() {
151
152        return (CmsAppWorkplaceUi)A_CmsUI.get();
153    }
154
155    /**
156     * Returns whether the current project is the online project.<p>
157     *
158     * @return <code>true</code> if the current project is the online project
159     */
160    public static boolean isOnlineProject() {
161
162        return getCmsObject().getRequestContext().getCurrentProject().isOnlineProject();
163    }
164
165    /**
166     * Sets the window title adding an OpenCms prefix.<p>
167     *
168     * @param title the window title
169     */
170    public static void setWindowTitle(String title) {
171
172        get().getPage().setTitle(WINDOW_TITLE_PREFIX + title);
173    }
174
175    /**
176     * @see com.vaadin.navigator.ViewChangeListener#afterViewChange(com.vaadin.navigator.ViewChangeListener.ViewChangeEvent)
177     */
178    public void afterViewChange(ViewChangeEvent event) {
179
180        if ((m_currentView != null) && (m_currentView instanceof ViewChangeListener)) {
181            ((ViewChangeListener)m_currentView).afterViewChange(event);
182        }
183        cacheView(event.getNewView());
184    }
185
186    /**
187     * @see com.vaadin.navigator.ViewChangeListener#beforeViewChange(com.vaadin.navigator.ViewChangeListener.ViewChangeEvent)
188     */
189    public boolean beforeViewChange(ViewChangeEvent event) {
190
191        cacheView(m_currentView);
192        if ((m_currentView != null) && (m_currentView instanceof ViewChangeListener)) {
193            return ((ViewChangeListener)m_currentView).beforeViewChange(event);
194        }
195        return true;
196    }
197
198    /**
199     * @see com.vaadin.server.Page.BrowserWindowResizeListener#browserWindowResized(com.vaadin.server.Page.BrowserWindowResizeEvent)
200     */
201    public void browserWindowResized(BrowserWindowResizeEvent event) {
202
203        markAsDirtyRecursive();
204        if ((m_currentView != null) && (m_currentView instanceof BrowserWindowResizeListener)) {
205            ((BrowserWindowResizeListener)m_currentView).browserWindowResized(event);
206        }
207    }
208
209    /**
210     * Call to add a new browser history entry.<p>
211     *
212     * @param state the current app view state
213     */
214    public void changeCurrentAppState(String state) {
215
216        String completeState = m_navigationStateManager.getState();
217        String view = getViewName(completeState);
218        String newCompleteState = view;
219        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(state)) {
220            newCompleteState += NavigationState.PARAM_SEPARATOR + state;
221        }
222        m_navigationStateManager.setState(newCompleteState);
223
224    }
225
226    /**
227     * Checks for new broadcasts.<p>
228     */
229    @SuppressWarnings("unchecked")
230    public void checkBroadcasts() {
231
232        Set<CmsBroadcast> repeatedBroadcasts = new HashSet<CmsBroadcast>();
233        CmsSessionInfo info = OpenCms.getSessionManager().getSessionInfo(getHttpSession());
234        if (info == null) {
235            return; //Session was killed..
236        }
237        Buffer queue = info.getBroadcastQueue();
238
239        if (!queue.isEmpty()) {
240            StringBuffer broadcasts = new StringBuffer();
241            String picPath = "";
242            while (!queue.isEmpty()) {
243                CmsBroadcast broadcastMessage = (CmsBroadcast)queue.remove();
244                if ((broadcastMessage.getLastDisplay()
245                    + CmsBroadcast.DISPLAY_AGAIN_TIME) < System.currentTimeMillis()) {
246                    CmsUserIconHelper helper = OpenCms.getWorkplaceAppManager().getUserIconHelper();
247
248                    String from = broadcastMessage.getUser() != null
249                    ? broadcastMessage.getUser().getName()
250                    : CmsVaadinUtils.getMessageText(org.opencms.workplace.Messages.GUI_LABEL_BROADCAST_FROM_SYSTEM_0);
251                    if (broadcastMessage.getUser() != null) {
252                        picPath = helper.getSmallIconPath(A_CmsUI.getCmsObject(), broadcastMessage.getUser());
253                    }
254                    String date = CmsVaadinUtils.getWpMessagesForCurrentLocale().getDateTime(
255                        broadcastMessage.getSendTime());
256                    String content = broadcastMessage.getMessage();
257                    broadcasts.append("<p>" + getImgHTML(picPath) + "<em>").append(date).append("</em><br />");
258                    broadcasts.append(
259                        CmsVaadinUtils.getMessageText(
260                            org.opencms.workplace.Messages.GUI_LABEL_BROADCASTMESSAGEFROM_0)).append(" <b>").append(
261                                from).append("</b>:</p><div class='o-broadcast-message'>");
262                    broadcasts.append(content).append("</div>");
263                    if (broadcastMessage.isRepeat()) {
264                        repeatedBroadcasts.add(broadcastMessage.withLastDisplay(System.currentTimeMillis()));
265                    }
266                } else {
267                    repeatedBroadcasts.add(broadcastMessage);
268                }
269            }
270            if (broadcasts.length() > 0) {
271                Notification notification = new Notification(
272                    CmsVaadinUtils.getMessageText(Messages.GUI_BROADCAST_TITLE_0),
273                    broadcasts.toString(),
274                    Type.WARNING_MESSAGE,
275                    true);
276                notification.setDelayMsec(-1);
277                notification.show(getPage());
278            }
279        }
280
281        if (!repeatedBroadcasts.isEmpty()) {
282            for (CmsBroadcast broadcast : repeatedBroadcasts) {
283                queue.add(broadcast);
284            }
285        }
286    }
287
288    /**
289     * @see org.opencms.ui.A_CmsUI#closeWindows()
290     */
291    @Override
292    public void closeWindows() {
293
294        super.closeWindows();
295        if (m_currentView instanceof CmsAppView) {
296            ((CmsAppView)m_currentView).getComponent().closePopupViews();
297        }
298    }
299
300    /**
301     * @see com.vaadin.ui.UI#detach()
302     */
303    @Override
304    public void detach() {
305
306        clearCachedViews();
307        super.detach();
308    }
309
310    /**
311     * Disables the global keyboard shortcuts.<p>
312     */
313    public void disableGlobalShortcuts() {
314
315        if (m_currentView instanceof I_CmsAppView) {
316            ((I_CmsAppView)m_currentView).disableGlobalShortcuts();
317        }
318    }
319
320    /**
321     * Enables the global keyboard shortcuts.<p>
322     */
323    public void enableGlobalShortcuts() {
324
325        if (m_currentView instanceof I_CmsAppView) {
326            ((I_CmsAppView)m_currentView).enableGlobalShortcuts();
327        }
328    }
329
330    /**
331     * Returns the state parameter of the current app.<p>
332     *
333     * @return the state parameter of the current app
334     */
335    public String getAppState() {
336
337        NavigationState state = new NavigationState(m_navigationStateManager.getState());
338        return state.getParams();
339    }
340
341    /**
342     * Gets the current view.<p>
343     *
344     * @return the current view
345     */
346    public View getCurrentView() {
347
348        return m_currentView;
349    }
350
351    /**
352     * @see com.vaadin.ui.AbstractComponent#getLocale()
353     */
354    @Override
355    public Locale getLocale() {
356
357        Locale result = m_localeCache.get();
358        if (result == null) {
359            CmsObject cms = getCmsObject();
360            result = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
361            m_localeCache.set(result);
362        }
363        return result;
364    }
365
366    /**
367     * @see com.vaadin.navigator.ViewProvider#getView(java.lang.String)
368     */
369    public View getView(String viewName) {
370
371        if (m_cachedViews.containsKey(viewName)) {
372            View view = m_cachedViews.get(viewName);
373            if (view instanceof CmsAppView) {
374                CmsAppView appView = (CmsAppView)view;
375                if (appView.getCacheStatus() == CacheStatus.cache) {
376                    appView.setRequiresRestore(true);
377                    return appView;
378                } else if (appView.getCacheStatus() == CacheStatus.cacheOnce) {
379                    appView.setCacheStatus(CacheStatus.noCache);
380                    appView.setRequiresRestore(true);
381                    return appView;
382                }
383            }
384        }
385        I_CmsWorkplaceAppConfiguration appConfig = OpenCms.getWorkplaceAppManager().getAppConfiguration(viewName);
386        if (appConfig != null) {
387            return new CmsAppView(appConfig);
388        } else {
389            LOG.warn("Nonexistant view '" + viewName + "' requested");
390            return m_launchRedirect;
391        }
392    }
393
394    /**
395     * @see com.vaadin.navigator.ViewProvider#getViewName(java.lang.String)
396     */
397    public String getViewName(String viewAndParameters) {
398
399        NavigationState state = new NavigationState(viewAndParameters);
400        return state.getViewName();
401    }
402
403    /**
404     * Executes the history back function.<p>
405     */
406    public void historyBack() {
407
408        m_history.historyBack();
409    }
410
411    /**
412     * Executes the history forward function.<p>
413     */
414    public void historyForward() {
415
416        m_history.historyForward();
417    }
418
419    /**
420     * Called when an error occurs.<p>
421     */
422    public void onError() {
423
424        // do nothing for now
425
426    }
427
428    /**
429     * @see org.opencms.ui.components.I_CmsWindowCloseListener#onWindowClose()
430     */
431    public void onWindowClose() {
432
433        if ((m_currentView != null) && (m_currentView instanceof I_CmsWindowCloseListener)) {
434            ((I_CmsWindowCloseListener)m_currentView).onWindowClose();
435        }
436        cacheView(m_currentView);
437    }
438
439    /**
440     * @see org.opencms.ui.A_CmsUI#reload()
441     */
442    @Override
443    public void reload() {
444
445        if (m_currentView instanceof I_CmsAppView) {
446            Component component = ((I_CmsAppView)m_currentView).reinitComponent();
447            setContent(component);
448            ((I_CmsAppView)m_currentView).enter(getAppState());
449        }
450    }
451
452    /**
453     * @see com.vaadin.ui.UI#setLastHeartbeatTimestamp(long)
454     */
455    @Override
456    public void setLastHeartbeatTimestamp(long lastHeartbeat) {
457
458        super.setLastHeartbeatTimestamp(lastHeartbeat);
459
460        // check for new broadcasts on every heart beat
461        checkBroadcasts();
462    }
463
464    /**
465     * Navigates to the given app.<p>
466     *
467     * @param appConfig the app configuration
468     */
469    public void showApp(I_CmsWorkplaceAppConfiguration appConfig) {
470
471        getNavigator().navigateTo(appConfig.getId());
472    }
473
474    /**
475     * Navigates to the given app.<p>
476     *
477     * @param appConfig the app configuration
478     * @param state the app state to call
479     */
480    public void showApp(I_CmsWorkplaceAppConfiguration appConfig, String state) {
481
482        getNavigator().navigateTo(appConfig.getId() + WORKPLACE_STATE_SEPARATOR + state);
483    }
484
485    /**
486     * Navigates to the given app.<p>
487     *
488     * @param appId the app id
489     * @param state the app state to call
490     */
491    public void showApp(String appId, String state) {
492
493        getNavigator().navigateTo(appId + WORKPLACE_STATE_SEPARATOR + state);
494    }
495
496    /**
497     * Navigates to the home screen.<p>
498     */
499    public void showHome() {
500
501        getNavigator().navigateTo(CmsAppHierarchyConfiguration.APP_ID);
502    }
503
504    /**
505     * @see com.vaadin.navigator.ViewDisplay#showView(com.vaadin.navigator.View)
506     */
507    public void showView(View view) {
508
509        closeWindows();
510
511        // remove current component form the view change listeners
512        m_currentView = view;
513        Component component = null;
514        if (view instanceof I_CmsAppView) {
515            if (((I_CmsAppView)view).requiresRestore()) {
516                ((I_CmsAppView)view).restoreFromCache();
517            }
518            component = ((I_CmsAppView)view).getComponent();
519        } else if (view instanceof Component) {
520            component = (Component)view;
521        }
522        initializeClientPolling(component);
523        if (component != null) {
524            setContent(component);
525        }
526    }
527
528    /**
529    * @see com.vaadin.ui.UI#init(com.vaadin.server.VaadinRequest)
530    */
531    @Override
532    protected void init(VaadinRequest req) {
533
534        super.init(req);
535        if (!OpenCms.getRoleManager().hasRole(getCmsObject(), CmsRole.ELEMENT_AUTHOR)) {
536            Notification.show(
537                CmsVaadinUtils.getMessageText(Messages.GUI_WORKPLACE_ACCESS_DENIED_TITLE_0),
538                CmsVaadinUtils.getMessageText(Messages.GUI_WORKPLACE_ACCESS_DENIED_MESSAGE_0),
539                Type.ERROR_MESSAGE);
540            return;
541        }
542        getSession().setErrorHandler(new CmsVaadinErrorHandler(this));
543
544        m_navigationStateManager = new Navigator.UriFragmentManager(getPage());
545        CmsAppNavigator navigator = new CmsAppNavigator(this, m_navigationStateManager, this);
546        navigator.addProvider(this);
547        setNavigator(navigator);
548
549        Page.getCurrent().addBrowserWindowResizeListener(this);
550        m_history = new CmsHistoryExtension(getCurrent());
551        CmsWindowCloseExtension windowClose = new CmsWindowCloseExtension(getCurrent());
552        windowClose.addWindowCloseListener(this);
553        navigator.addViewChangeListener(this);
554        navigateToFragment();
555
556        getReconnectDialogConfiguration().setDialogText(
557            CmsVaadinUtils.getMessageText(org.opencms.ui.Messages.GUI_SYSTEM_CONNECTION_LOST_TRYING_RECONNECT_0));
558        getReconnectDialogConfiguration().setDialogTextGaveUp(
559            CmsVaadinUtils.getMessageText(org.opencms.ui.Messages.GUI_SYSTEM_CONNECTION_LOST_GIVING_UP_0));
560    }
561
562    /**
563     * Caches the given view in case it implements the I_CmsAppView interface and is cachable.<p>
564     *
565     * @param view the view to cache
566     */
567    private void cacheView(View view) {
568
569        if (!m_refreshing && (view instanceof I_CmsAppView) && ((I_CmsAppView)view).isCachable()) {
570            m_cachedViews.put(((I_CmsAppView)view).getName(), (I_CmsAppView)view);
571        }
572    }
573
574    /**
575     * Clears the cached views.<p>
576     */
577    private void clearCachedViews() {
578
579        m_cachedViews.clear();
580    }
581
582    /**
583     * Generates html for image from given path.<p>
584     *
585     * @param pic path to image
586     * @return html
587     */
588    private String getImgHTML(String pic) {
589
590        if (pic.isEmpty()) {
591            return "";
592        }
593        return "<img class=\"v-icon\" src=\"" + pic + "\">";
594
595    }
596
597    /**
598     * Initializes client polling to avoid session expiration.<p>
599     *
600     * @param component the view component
601     */
602    @SuppressWarnings("unused")
603    private void initializeClientPolling(Component component) {
604
605        if (component instanceof AbstractComponent) {
606            AbstractComponent acomp = (AbstractComponent)component;
607            for (Extension extension : acomp.getExtensions()) {
608                if (extension instanceof CmsPollServerExtension) {
609                    return;
610                }
611            }
612            new CmsPollServerExtension((AbstractComponent)component);
613        }
614    }
615
616    /**
617     * Navigates to the current URI fragment.<p>
618     */
619    private void navigateToFragment() {
620
621        String fragment = getPage().getUriFragment();
622        if (fragment != null) {
623            getNavigator().navigateTo(fragment);
624        } else {
625            CmsObject cms = getCmsObject();
626            String target = CmsLoginHelper.getStartView(cms);
627            if (target != null) {
628                if (target.startsWith("#")) {
629                    getNavigator().navigateTo(target.substring(1));
630                } else {
631                    Page.getCurrent().setLocation(OpenCms.getLinkManager().substituteLink(cms, target));
632                }
633            } else {
634                showHome();
635            }
636        }
637    }
638}