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 GmbH & Co. KG, 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.workplace.editors; 029 030import org.opencms.cache.CmsVfsMemoryObjectCache; 031import org.opencms.configuration.CmsParameterConfiguration; 032import org.opencms.db.CmsUserSettings; 033import org.opencms.file.CmsFile; 034import org.opencms.file.CmsObject; 035import org.opencms.file.CmsRequestContext; 036import org.opencms.file.CmsResource; 037import org.opencms.file.CmsResourceFilter; 038import org.opencms.file.CmsVfsResourceNotFoundException; 039import org.opencms.file.types.CmsResourceTypeXmlPage; 040import org.opencms.file.types.I_CmsResourceType; 041import org.opencms.main.CmsException; 042import org.opencms.main.CmsLog; 043import org.opencms.main.OpenCms; 044import org.opencms.util.CmsStringUtil; 045import org.opencms.workplace.explorer.CmsExplorerTypeSettings; 046import org.opencms.xml.content.CmsXmlContent; 047import org.opencms.xml.content.CmsXmlContentFactory; 048 049import java.io.ByteArrayInputStream; 050import java.util.ArrayList; 051import java.util.HashMap; 052import java.util.Iterator; 053import java.util.List; 054import java.util.Map; 055import java.util.SortedMap; 056import java.util.TreeMap; 057 058import org.apache.commons.logging.Log; 059 060/** 061 * The editor manager stores information about all available configured editors in OpenCms.<p> 062 * 063 * This class provides methods and constants to select the right editor according to: 064 * <ul> 065 * <li>the user preferences</li> 066 * <li>the users current browser</li> 067 * <li>the resource type</li> 068 * <li>the editor rankings</li> 069 * </ul> 070 * <p> 071 * 072 * @since 6.0.0 073 */ 074public class CmsWorkplaceEditorManager { 075 076 /** The filename of the editor configuration XML file. */ 077 public static final String EDITOR_CONFIGURATION_FILENAME = "editor_configuration.xml"; 078 079 /** The filename of the editor JSP. */ 080 public static final String EDITOR_FILENAME = "editor.jsp"; 081 082 /** The log object for this class. */ 083 private static final Log LOG = CmsLog.getLog(CmsWorkplaceEditorManager.class); 084 085 /** The editor configurations. */ 086 private List<CmsWorkplaceEditorConfiguration> m_editorConfigurations; 087 088 /** The preferred editor configurations. */ 089 private Map<String, CmsWorkplaceEditorConfiguration> m_preferredEditors; 090 091 /** 092 * Creates a new editor manager.<p> 093 * 094 * @param cms an OpenCms context object that must have been initialized with "Admin" permissions 095 */ 096 public CmsWorkplaceEditorManager(CmsObject cms) { 097 098 // get all subfolders of the workplace editor folder 099 List<CmsResource> editorFolders; 100 try { 101 editorFolders = cms.getSubFolders(CmsEditor.PATH_EDITORS); 102 } catch (CmsException e) { 103 LOG.error(Messages.get().getBundle().key(Messages.LOG_READ_EDITIR_FOLDER_FAILED_1, CmsEditor.PATH_EDITORS)); 104 // can not throw exception here since then OpenCms would not even start in shell mode (runlevel 2) 105 editorFolders = new ArrayList<CmsResource>(); 106 } 107 108 m_editorConfigurations = new ArrayList<CmsWorkplaceEditorConfiguration>(editorFolders.size()); 109 110 // try to read the configuration files and create configuration objects for valid configurations 111 Iterator<CmsResource> i = editorFolders.iterator(); 112 while (i.hasNext()) { 113 CmsResource currentFolder = i.next(); 114 String folderName = CmsEditor.PATH_EDITORS + currentFolder.getName(); 115 if (!folderName.endsWith("/")) { 116 folderName += "/"; 117 } 118 CmsFile configFile = null; 119 try { 120 configFile = cms.readFile( 121 folderName + EDITOR_CONFIGURATION_FILENAME, 122 CmsResourceFilter.IGNORE_EXPIRATION); 123 } catch (CmsException e) { 124 // no configuration file present, ignore this folder 125 if (LOG.isInfoEnabled()) { 126 LOG.info(e.getLocalizedMessage(), e); 127 } 128 continue; 129 } 130 // get the file contents 131 byte[] xmlData = configFile.getContents(); 132 CmsWorkplaceEditorConfiguration editorConfig = new CmsWorkplaceEditorConfiguration( 133 xmlData, 134 folderName + EDITOR_FILENAME, 135 currentFolder.getName()); 136 if (editorConfig.isValidConfiguration()) { 137 m_editorConfigurations.add(editorConfig); 138 } 139 } 140 m_preferredEditors = new HashMap<String, CmsWorkplaceEditorConfiguration>(m_editorConfigurations.size()); 141 } 142 143 /** 144 * Checks whether GWT widgets are available for all fields of a content.<p> 145 * 146 * @param cms the current CMS context 147 * @param resource the resource to check 148 * 149 * @return false if for some fields the new Acacia widgets are not available 150 */ 151 public static boolean checkAcaciaEditorAvailable(CmsObject cms, CmsResource resource) { 152 153 boolean result = false; 154 if (resource == null) { 155 try { 156 // we want a stack trace 157 throw new Exception(); 158 } catch (Exception e) { 159 LOG.error("Can't check widget availability because resource is null!", e); 160 } 161 } else { 162 try { 163 CmsFile file = (resource instanceof CmsFile) ? (CmsFile)resource : cms.readFile(resource); 164 if (file.getContents().length > 0) { 165 CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file); 166 if (!content.getContentDefinition().getContentHandler().isAcaciaEditorDisabled()) { 167 result = true; 168 } 169 } 170 } catch (CmsException e) { 171 LOG.info( 172 "error thrown in checkAcaciaEditorAvailable for " + resource + " : " + e.getLocalizedMessage(), 173 e); 174 result = true; 175 } 176 } 177 return result; 178 } 179 180 /** 181 * Returns a map of configurable editors for the workplace preferences dialog.<p> 182 * 183 * This map has the resource type name as key, the value is a sorted map with 184 * the ranking as key and a CmsWorkplaceEditorConfiguration object as value.<p> 185 * 186 * @return configurable editors for the workplace preferences dialog 187 */ 188 public Map<String, SortedMap<Float, CmsWorkplaceEditorConfiguration>> getConfigurableEditors() { 189 190 Map<String, SortedMap<Float, CmsWorkplaceEditorConfiguration>> configurableEditors = new HashMap<String, SortedMap<Float, CmsWorkplaceEditorConfiguration>>(); 191 Iterator<CmsWorkplaceEditorConfiguration> i = m_editorConfigurations.iterator(); 192 while (i.hasNext()) { 193 CmsWorkplaceEditorConfiguration currentConfig = i.next(); 194 // get all resource types specified for the current editor configuration 195 Iterator<String> k = currentConfig.getResourceTypes().keySet().iterator(); 196 while (k.hasNext()) { 197 // key is the current resource type of the configuration 198 String key = k.next(); 199 200 // check if the current resource type is only a reference to another resource type 201 CmsExplorerTypeSettings settings = OpenCms.getWorkplaceManager().getExplorerTypeSetting(key); 202 if ((settings == null) || CmsStringUtil.isNotEmpty(settings.getReference())) { 203 // skip this resource type 204 continue; 205 } 206 207 if ((currentConfig.getMappingForResourceType(key) == null) 208 || currentConfig.getMappingForResourceType(key).equals(key)) { 209 // editor is configurable for specified resource type 210 SortedMap<Float, CmsWorkplaceEditorConfiguration> editorConfigs = configurableEditors.get(key); 211 if (editorConfigs == null) { 212 // no configuration map present for resource type, create one 213 editorConfigs = new TreeMap<Float, CmsWorkplaceEditorConfiguration>(); 214 } 215 // put the current editor configuration to the resource map with ranking value as key 216 editorConfigs.put(Float.valueOf(currentConfig.getRankingForResourceType(key)), currentConfig); 217 // put the resource map to the result map with resource type as key 218 configurableEditors.put(key, editorConfigs); 219 } 220 } 221 } 222 return configurableEditors; 223 } 224 225 /** 226 * Gets the editor configuration with the given name.<p> 227 * 228 * @param name the name of the editor configuration 229 * 230 * @return the editor configuration 231 */ 232 public CmsWorkplaceEditorConfiguration getEditorConfiguration(String name) { 233 234 for (CmsWorkplaceEditorConfiguration config : m_editorConfigurations) { 235 if (name.equals(config.getName())) { 236 return config; 237 } 238 } 239 return null; 240 } 241 242 /** 243 * Gets the value of a global editor configuration parameter. 244 * 245 * @param cms the CMS context 246 * @param editor the editor name 247 * @param param the name of the parameter 248 * 249 * @return the editor parameter value 250 */ 251 public String getEditorParameter(CmsObject cms, String editor, String param) { 252 253 String path = OpenCms.getSystemInfo().getConfigFilePath(cms, "editors/" + editor + ".properties"); 254 CmsVfsMemoryObjectCache cache = CmsVfsMemoryObjectCache.getVfsMemoryObjectCache(); 255 CmsParameterConfiguration config = (CmsParameterConfiguration)cache.getCachedObject(cms, path); 256 if (config == null) { 257 try { 258 CmsFile file = cms.readFile(path); 259 try (ByteArrayInputStream input = new ByteArrayInputStream(file.getContents())) { 260 config = new CmsParameterConfiguration(input); // Uses ISO-8859-1, should be OK for config parameters 261 cache.putCachedObject(cms, path, config); 262 } 263 } catch (CmsVfsResourceNotFoundException e) { 264 return null; 265 } catch (Exception e) { 266 LOG.error(e.getLocalizedMessage(), e); 267 return null; 268 } 269 } 270 return config.getString(param, null); 271 } 272 273 /** 274 * Returns the editor URI for the current resource type.<p> 275 * 276 * @param context the request context 277 * @param userAgent the user agent String that identifies the browser 278 * @return a valid editor URI for the resource type or null, if no editor matches 279 */ 280 public String getWidgetEditor(CmsRequestContext context, String userAgent) { 281 282 // step 1: check if the user specified a preferred editor for the resource type xmlpage 283 CmsUserSettings settings = new CmsUserSettings(context.getCurrentUser()); 284 String resourceType = CmsResourceTypeXmlPage.getStaticTypeName(); 285 String preferredEditorSetting = settings.getPreferredEditor(resourceType); 286 if (preferredEditorSetting == null) { 287 // no preferred editor setting found for this resource type, look for mapped resource type preferred editor 288 Iterator<CmsWorkplaceEditorConfiguration> i = m_editorConfigurations.iterator(); 289 while (i.hasNext()) { 290 CmsWorkplaceEditorConfiguration currentConfig = i.next(); 291 String mapping = currentConfig.getMappingForResourceType(resourceType); 292 if (mapping != null) { 293 preferredEditorSetting = settings.getPreferredEditor(mapping); 294 } 295 if (preferredEditorSetting != null) { 296 break; 297 } 298 } 299 } 300 if (preferredEditorSetting != null) { 301 CmsWorkplaceEditorConfiguration preferredConf = filterPreferredEditor(preferredEditorSetting); 302 if ((preferredConf != null) && preferredConf.isWidgetEditor() && preferredConf.matchesBrowser(userAgent)) { 303 // return preferred editor only if it matches the current users browser 304 return preferredConf.getWidgetEditor(); 305 } 306 } 307 308 // step 2: filter editors for the given resoure type 309 SortedMap<Float, CmsWorkplaceEditorConfiguration> filteredEditors = filterEditorsForResourceType(resourceType); 310 311 // step 3: check if one of the editors matches the current users browser 312 while (filteredEditors.size() > 0) { 313 // check editor configuration with highest ranking 314 Float key = filteredEditors.lastKey(); 315 CmsWorkplaceEditorConfiguration conf = filteredEditors.get(key); 316 if (conf.isWidgetEditor() && conf.matchesBrowser(userAgent)) { 317 return conf.getWidgetEditor(); 318 } 319 filteredEditors.remove(key); 320 } 321 322 // no valid editor found 323 return null; 324 } 325 326 /** 327 * Checks if there is an editor which can process the given resource.<p> 328 * 329 * @param res the resource 330 * 331 * @return true if the given resource can be edited with one of the configured editors 332 */ 333 public boolean isEditorAvailableForResource(CmsResource res) { 334 335 I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(res); 336 String typeName = type.getTypeName(); 337 for (CmsWorkplaceEditorConfiguration editorConfig : m_editorConfigurations) { 338 if (editorConfig.matchesResourceType(typeName)) { 339 return true; 340 } 341 } 342 return false; 343 } 344 345 /** 346 * Returns the default editor URI for the current resource type.<p> 347 * 348 * @param context the request context 349 * @param resourceType the current resource type 350 * @param userAgent the user agent String that identifies the browser 351 * @return a valid default editor URI for the resource type or null, if no editor matches 352 */ 353 protected String getDefaultEditorUri(CmsRequestContext context, String resourceType, String userAgent) { 354 355 SortedMap<Float, CmsWorkplaceEditorConfiguration> filteredEditors = filterEditorsForResourceType(resourceType); 356 while (filteredEditors.size() > 0) { 357 // get the configuration with the lowest key value from the map 358 Float key = filteredEditors.firstKey(); 359 CmsWorkplaceEditorConfiguration conf = filteredEditors.get(key); 360 // match the found configuration with the current users browser 361 if (conf.matchesBrowser(userAgent)) { 362 return conf.getEditorUri(); 363 } 364 filteredEditors.remove(key); 365 } 366 if (context == null) { 367 // this is just so that all parameters are used, signature should be identical to getEditorUri(...) 368 return null; 369 } 370 // no valid default editor found 371 return null; 372 } 373 374 /** 375 * Returns the editor configuration objects.<p> 376 * 377 * @return the editor configuration objects 378 */ 379 protected List<CmsWorkplaceEditorConfiguration> getEditorConfigurations() { 380 381 return m_editorConfigurations; 382 } 383 384 /** 385 * Returns the editor URI for the current resource type.<p> 386 * 387 * @param context the request context 388 * @param resourceType the current resource type 389 * @param userAgent the user agent String that identifies the browser 390 * @return a valid editor URI for the resource type or null, if no editor matches 391 */ 392 protected String getEditorUri(CmsRequestContext context, String resourceType, String userAgent) { 393 394 // step 1: check if the user specified a preferred editor for the given resource type 395 CmsUserSettings settings = new CmsUserSettings(context.getCurrentUser()); 396 String preferredEditorSetting = settings.getPreferredEditor(resourceType); 397 if (preferredEditorSetting == null) { 398 // no preferred editor setting found for this resource type, look for mapped resource type preferred editor 399 Iterator<CmsWorkplaceEditorConfiguration> i = m_editorConfigurations.iterator(); 400 while (i.hasNext()) { 401 CmsWorkplaceEditorConfiguration currentConfig = i.next(); 402 String mapping = currentConfig.getMappingForResourceType(resourceType); 403 if (mapping != null) { 404 preferredEditorSetting = settings.getPreferredEditor(mapping); 405 } 406 if (preferredEditorSetting != null) { 407 break; 408 } 409 } 410 } 411 if (preferredEditorSetting != null) { 412 CmsWorkplaceEditorConfiguration preferredConf = filterPreferredEditor(preferredEditorSetting); 413 if ((preferredConf != null) && preferredConf.matchesBrowser(userAgent)) { 414 // return preferred editor only if it matches the current users browser 415 return preferredConf.getEditorUri(); 416 } 417 } 418 419 // step 2: filter editors for the given resoure type 420 SortedMap<Float, CmsWorkplaceEditorConfiguration> filteredEditors = filterEditorsForResourceType(resourceType); 421 422 // step 3: check if one of the editors matches the current users browser 423 while (filteredEditors.size() > 0) { 424 // check editor configuration with highest ranking 425 Float key = filteredEditors.lastKey(); 426 CmsWorkplaceEditorConfiguration conf = filteredEditors.get(key); 427 if (conf.matchesBrowser(userAgent)) { 428 return conf.getEditorUri(); 429 } 430 filteredEditors.remove(key); 431 } 432 433 // no valid editor found 434 return null; 435 } 436 437 /** 438 * Filters the matching editors for the given resource type from the list of all available editors.<p> 439 * 440 * @param resourceType the resource type to filter 441 * @return a map of filtered editor configurations sorted asceding by the ranking for the current resource type, with the (Float) ranking as key 442 */ 443 private SortedMap<Float, CmsWorkplaceEditorConfiguration> filterEditorsForResourceType(String resourceType) { 444 445 SortedMap<Float, CmsWorkplaceEditorConfiguration> filteredEditors = new TreeMap<Float, CmsWorkplaceEditorConfiguration>(); 446 Iterator<CmsWorkplaceEditorConfiguration> i = m_editorConfigurations.iterator(); 447 while (i.hasNext()) { 448 CmsWorkplaceEditorConfiguration currentConfig = i.next(); 449 if (currentConfig.matchesResourceType(resourceType)) { 450 float key = currentConfig.getRankingForResourceType(resourceType); 451 if (key >= 0) { 452 filteredEditors.put(Float.valueOf(key), currentConfig); 453 } 454 } 455 } 456 return filteredEditors; 457 } 458 459 /** 460 * Filters the preferred editor from the list of all available editors.<p> 461 * 462 * @param preferredEditor the preferred editor identification String 463 * @return the preferred editor configuration object or null, if none is found 464 */ 465 private CmsWorkplaceEditorConfiguration filterPreferredEditor(String preferredEditor) { 466 467 if (m_preferredEditors.size() == 0) { 468 Iterator<CmsWorkplaceEditorConfiguration> i = m_editorConfigurations.iterator(); 469 while (i.hasNext()) { 470 CmsWorkplaceEditorConfiguration currentConfig = i.next(); 471 m_preferredEditors.put(currentConfig.getEditorUri(), currentConfig); 472 } 473 } 474 return m_preferredEditors.get(preferredEditor); 475 } 476 477}