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