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.file.CmsProject; 032import org.opencms.i18n.CmsEncoder; 033import org.opencms.main.CmsException; 034import org.opencms.main.OpenCms; 035import org.opencms.ui.A_CmsUI; 036import org.opencms.ui.components.CmsBreadCrumb; 037import org.opencms.ui.components.CmsToolLayout; 038import org.opencms.util.CmsStringUtil; 039 040import java.util.HashMap; 041import java.util.Iterator; 042import java.util.LinkedHashMap; 043import java.util.List; 044import java.util.Map; 045 046import com.vaadin.server.Resource; 047import com.vaadin.ui.Button; 048import com.vaadin.ui.Button.ClickEvent; 049import com.vaadin.ui.Button.ClickListener; 050import com.vaadin.ui.Component; 051import com.vaadin.ui.HorizontalLayout; 052import com.vaadin.ui.Label; 053import com.vaadin.ui.UI; 054 055/** 056 * Super class for workplace apps to help implementing the app navigation and layout.<p> 057 */ 058public abstract class A_CmsWorkplaceApp implements I_CmsWorkplaceApp { 059 060 /** 061 * An app navigation entry.<p> 062 */ 063 public static class NavEntry { 064 065 /** The entry description. */ 066 private String m_description; 067 068 /** The entry icon. */ 069 private Resource m_icon; 070 071 /** The localized entry name. */ 072 private String m_name; 073 074 /** The target state. */ 075 private String m_targetState; 076 077 /** 078 * Constructor.<p> 079 * 080 * @param name the entry name 081 * @param description the description 082 * @param icon the icon 083 * @param targetState the target state 084 */ 085 public NavEntry(String name, String description, Resource icon, String targetState) { 086 087 m_name = name; 088 m_description = description; 089 m_icon = icon; 090 m_targetState = targetState; 091 } 092 093 /** 094 * Returns the description.<p> 095 * 096 * @return the description 097 */ 098 public String getDescription() { 099 100 return m_description; 101 } 102 103 /** 104 * Returns the icon.<p> 105 * 106 * @return the icon 107 */ 108 public Resource getIcon() { 109 110 return m_icon; 111 } 112 113 /** 114 * Returns the entry name.<p> 115 * 116 * @return the entry name 117 */ 118 public String getName() { 119 120 return m_name; 121 } 122 123 /** 124 * Returns the target state.<p> 125 * 126 * @return the target state 127 */ 128 public String getTargetState() { 129 130 return m_targetState; 131 } 132 } 133 134 /** State parameter value separator. */ 135 public static final String PARAM_ASSIGN = "::"; 136 137 /** State parameter separator. */ 138 public static final String PARAM_SEPARATOR = "!!"; 139 140 /** The app info layout containing the bread crumb navigation as first component. */ 141 protected HorizontalLayout m_infoLayout; 142 143 /** The root layout. */ 144 protected CmsToolLayout m_rootLayout; 145 146 /** The app UI context. */ 147 protected I_CmsAppUIContext m_uiContext; 148 149 /** The bread crumb navigation. */ 150 private CmsBreadCrumb m_breadCrumb; 151 152 /** 153 * Constructor.<p> 154 */ 155 protected A_CmsWorkplaceApp() { 156 157 m_rootLayout = new CmsToolLayout(); 158 m_rootLayout.setSizeFull(); 159 } 160 161 /** 162 * Adds a parameter value to the given state.<p> 163 * 164 * @param state the state 165 * @param paramName the parameter name 166 * @param value the parameter value 167 * 168 * @return the state 169 */ 170 public static String addParamToState(String state, String paramName, String value) { 171 172 return state + PARAM_SEPARATOR + paramName + PARAM_ASSIGN + CmsEncoder.encode(value, CmsEncoder.ENCODING_UTF_8); 173 } 174 175 /** 176 * Parses the requested parameter from the given state.<p> 177 * 178 * @param state the state 179 * @param paramName the parameter name 180 * 181 * @return the parameter value 182 */ 183 public static String getParamFromState(String state, String paramName) { 184 185 String prefix = PARAM_SEPARATOR + paramName + PARAM_ASSIGN; 186 if (state.contains(prefix)) { 187 String result = state.substring(state.indexOf(prefix) + prefix.length()); 188 if (result.contains(PARAM_SEPARATOR)) { 189 result = result.substring(0, result.indexOf(PARAM_SEPARATOR)); 190 } 191 return CmsEncoder.decode(result, CmsEncoder.ENCODING_UTF_8); 192 } 193 return null; 194 } 195 196 /** 197 * Returns the parameters contained in the state string.<p> 198 * 199 * @param state the state 200 * 201 * @return the parameters 202 */ 203 public static Map<String, String> getParamsFromState(String state) { 204 205 Map<String, String> result = new HashMap<String, String>(); 206 int separatorIndex = state.indexOf(PARAM_SEPARATOR); 207 while (separatorIndex >= 0) { 208 state = state.substring(separatorIndex + 2); 209 int assignIndex = state.indexOf(PARAM_ASSIGN); 210 if (assignIndex > 0) { 211 String key = state.substring(0, assignIndex); 212 state = state.substring(assignIndex + 2); 213 separatorIndex = state.indexOf(PARAM_SEPARATOR); 214 String value = null; 215 if (separatorIndex < 0) { 216 value = state; 217 } else if (separatorIndex > 0) { 218 value = state.substring(0, separatorIndex); 219 } 220 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)) { 221 result.put(key, CmsEncoder.decode(value, CmsEncoder.ENCODING_UTF_8)); 222 } 223 } else { 224 separatorIndex = -1; 225 } 226 } 227 return result; 228 } 229 230 /** 231 * Removes all parameter from given state.<p> 232 * 233 * @param state state to be cleaned from parameter 234 * @return given state without parameter 235 */ 236 public static String removeParamsFromState(String state) { 237 238 return state.split(PARAM_SEPARATOR)[0]; 239 } 240 241 /** 242 * Gets an offline version of the cms object.<p> 243 * 244 * @param cms initial CmsObject 245 * @return CmsObject adjusted to offline project (cloned) 246 */ 247 public CmsObject getOfflineCmsObject(CmsObject cms) { 248 249 CmsObject res = null; 250 try { 251 if (!cms.getRequestContext().getCurrentProject().isOnlineProject()) { 252 return OpenCms.initCmsObject(cms); 253 } 254 res = OpenCms.initCmsObject(cms); 255 List<CmsProject> projects = OpenCms.getOrgUnitManager().getAllAccessibleProjects(res, "/", true); 256 Iterator<CmsProject> projIterator = projects.iterator(); 257 boolean offFound = false; 258 while (projIterator.hasNext() & !offFound) { 259 CmsProject offP = projIterator.next(); 260 if (!offP.isOnlineProject()) { 261 res.getRequestContext().setCurrentProject(offP); 262 offFound = true; 263 } 264 } 265 } catch (CmsException e) { 266 return cms; 267 } 268 return res; 269 } 270 271 /** 272 * @see org.opencms.ui.apps.I_CmsWorkplaceApp#initUI(org.opencms.ui.apps.I_CmsAppUIContext) 273 */ 274 public void initUI(I_CmsAppUIContext context) { 275 276 m_uiContext = context; 277 m_uiContext.showInfoArea(true); 278 m_breadCrumb = new CmsBreadCrumb(); 279 m_infoLayout = new HorizontalLayout(); 280 m_infoLayout.setSizeFull(); 281 m_infoLayout.setSpacing(true); 282 m_infoLayout.setMargin(true); 283 m_uiContext.setAppInfo(m_infoLayout); 284 m_infoLayout.addComponent(m_breadCrumb); 285 m_infoLayout.setExpandRatio(m_breadCrumb, 2); 286 m_uiContext.setAppContent(m_rootLayout); 287 } 288 289 /** 290 * @see org.opencms.ui.apps.I_CmsWorkplaceApp#onStateChange(java.lang.String) 291 */ 292 public void onStateChange(String state) { 293 294 openSubView(state, false); 295 } 296 297 /** 298 * Opens the requested sub view.<p> 299 * 300 * @param state the state 301 * @param updateState <code>true</code> to update the state URL token 302 */ 303 public void openSubView(String state, boolean updateState) { 304 305 if (updateState) { 306 CmsAppWorkplaceUi.get().changeCurrentAppState(state); 307 } 308 Component comp = getComponentForState(state); 309 if (comp != null) { 310 comp.setSizeFull(); 311 m_rootLayout.setMainContent(comp); 312 } else { 313 m_rootLayout.setMainContent(new Label("Malformed path, tool not available for path: " + state)); 314 } 315 updateSubNav(getSubNavEntries(state)); 316 updateBreadCrumb(getBreadCrumbForState(state)); 317 } 318 319 /** 320 * Adds a navigation entry.<p> 321 * 322 * @param navEntry the navigation entry 323 */ 324 protected void addSubNavEntry(final NavEntry navEntry) { 325 326 Button button = m_rootLayout.addSubNavEntry(navEntry); 327 button.addClickListener(new ClickListener() { 328 329 private static final long serialVersionUID = 1L; 330 331 public void buttonClick(ClickEvent event) { 332 333 openSubView(navEntry.getTargetState(), true); 334 } 335 }); 336 } 337 338 /** 339 * Returns the current bread crumb entries in an ordered map.<p> 340 * 341 * @param state the current state 342 * 343 * @return bread crumb entry name by state, in case the state is empty, the entry will be disabled 344 */ 345 protected abstract LinkedHashMap<String, String> getBreadCrumbForState(String state); 346 347 /** 348 * Returns the app component for the given state.<p> 349 * 350 * @param state the state to render 351 * 352 * @return the app component 353 */ 354 protected abstract Component getComponentForState(String state); 355 356 /** 357 * Returns the last path level.<p> 358 * 359 * @param path the path 360 * 361 * @return the last path level 362 */ 363 protected String getLastPathLevel(String path) { 364 365 path = path.trim(); 366 if (path.endsWith("/")) { 367 path = path.substring(0, path.length() - 1); 368 } 369 if (path.contains("/")) { 370 path = path.substring(path.lastIndexOf("/")); 371 } 372 return path; 373 } 374 375 /** 376 * Returns the sub navigation entries.<p> 377 * 378 * @param state the state 379 * 380 * @return the sub navigation entries 381 */ 382 protected abstract List<NavEntry> getSubNavEntries(String state); 383 384 /** 385 * Method to set bread crumb entries.<p> 386 * 387 * @param entries to be set 388 */ 389 protected void setBreadCrumbEntries(LinkedHashMap<String, String> entries) { 390 391 m_breadCrumb.setEntries(entries); 392 } 393 394 /** 395 * Updates the bread crumb navigation.<p> 396 * 397 * @param breadCrumbEntries the bread crumb entries 398 */ 399 protected void updateBreadCrumb(Map<String, String> breadCrumbEntries) { 400 401 LinkedHashMap<String, String> entries = new LinkedHashMap<String, String>(); 402 I_CmsWorkplaceAppConfiguration launchpadConfig = OpenCms.getWorkplaceAppManager().getAppConfiguration( 403 CmsAppHierarchyConfiguration.APP_ID); 404 if (launchpadConfig.getVisibility(A_CmsUI.getCmsObject()).isActive()) { 405 entries.put(CmsAppHierarchyConfiguration.APP_ID, launchpadConfig.getName(UI.getCurrent().getLocale())); 406 } 407 if ((breadCrumbEntries != null) && !breadCrumbEntries.isEmpty()) { 408 entries.putAll(breadCrumbEntries); 409 } else { 410 entries.put( 411 "", 412 OpenCms.getWorkplaceAppManager().getAppConfiguration(m_uiContext.getAppId()).getName( 413 UI.getCurrent().getLocale())); 414 } 415 m_breadCrumb.setEntries(entries); 416 } 417 418 /** 419 * Updates the sub navigation with the given entries.<p> 420 * 421 * @param subEntries the sub navigation entries 422 */ 423 protected void updateSubNav(List<NavEntry> subEntries) { 424 425 m_rootLayout.clearSubNav(); 426 if ((subEntries == null) || subEntries.isEmpty()) { 427 m_rootLayout.setSubNavVisible(false); 428 } else { 429 m_rootLayout.setSubNavVisible(true); 430 for (NavEntry entry : subEntries) { 431 addSubNavEntry(entry); 432 } 433 } 434 } 435}