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}