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}