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