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.xml.containerpage; 029 030import org.opencms.ade.containerpage.shared.CmsFormatterConfig; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsResource; 033import org.opencms.file.types.CmsResourceTypeJsp; 034import org.opencms.main.CmsException; 035import org.opencms.main.CmsLog; 036import org.opencms.main.OpenCms; 037import org.opencms.security.CmsRole; 038import org.opencms.util.CmsStringUtil; 039import org.opencms.util.CmsUUID; 040 041import java.util.ArrayList; 042import java.util.Arrays; 043import java.util.Collection; 044import java.util.Collections; 045import java.util.Comparator; 046import java.util.LinkedHashMap; 047import java.util.List; 048import java.util.Map; 049import java.util.Set; 050 051import org.apache.commons.logging.Log; 052 053import com.google.common.base.Optional; 054import com.google.common.base.Predicate; 055import com.google.common.base.Predicates; 056import com.google.common.collect.Collections2; 057import com.google.common.collect.ComparisonChain; 058import com.google.common.collect.Iterables; 059import com.google.common.collect.Maps; 060import com.google.common.collect.Sets; 061 062/** 063 * Represents a formatter configuration.<p> 064 * 065 * A formatter configuration can be either defined in the XML schema XSD of a XML content, 066 * or in a special sitemap configuration file.<p> 067 * 068 * @since 8.0.0 069 */ 070public final class CmsFormatterConfiguration { 071 072 /** 073 * This class is used to sort lists of formatter beans in order of importance.<p> 074 */ 075 public static class FormatterComparator implements Comparator<I_CmsFormatterBean> { 076 077 /** 078 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) 079 */ 080 public int compare(I_CmsFormatterBean first, I_CmsFormatterBean second) { 081 082 return ComparisonChain.start().compare(second.getRank(), first.getRank()).compare( 083 second.isTypeFormatter() ? 1 : 0, 084 first.isTypeFormatter() ? 1 : 0).compare(second.getMinWidth(), first.getMinWidth()).result(); 085 } 086 } 087 088 /** 089 * Predicate which checks whether the given formatter is a detail formatter.<p> 090 */ 091 public static class IsDetail implements Predicate<I_CmsFormatterBean> { 092 093 /** 094 * @see com.google.common.base.Predicate#apply(java.lang.Object) 095 */ 096 public boolean apply(I_CmsFormatterBean formatter) { 097 098 return formatter.isDetailFormatter(); 099 } 100 } 101 102 /** 103 * Predicate which checks whether the given formatter is a display formatter.<p> 104 */ 105 public static class IsDisplay implements Predicate<I_CmsFormatterBean> { 106 107 /** 108 * @see com.google.common.base.Predicate#apply(java.lang.Object) 109 */ 110 public boolean apply(I_CmsFormatterBean formatter) { 111 112 return formatter.isDisplayFormatter(); 113 } 114 } 115 116 /** 117 * Predicate to check whether the formatter is from a schema.<p> 118 */ 119 public static class IsSchemaFormatter implements Predicate<I_CmsFormatterBean> { 120 121 /** 122 * @see com.google.common.base.Predicate#apply(java.lang.Object) 123 */ 124 public boolean apply(I_CmsFormatterBean formatter) { 125 126 return !formatter.isFromFormatterConfigFile(); 127 128 } 129 } 130 131 /** 132 * Predicate which checks whether a formatter matches the given container type or width.<p> 133 */ 134 private static class MatchesTypeOrWidth implements Predicate<I_CmsFormatterBean> { 135 136 /** The set of container types to match. */ 137 private Set<String> m_types = Sets.newHashSet(); 138 139 /** The container width. */ 140 private int m_width; 141 142 /** 143 * Creates a new matcher instance.<p> 144 * 145 * @param type the container type 146 * @param width the container width 147 */ 148 public MatchesTypeOrWidth(String type, int width) { 149 150 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(type)) { 151 // split with comma and optionally spaces to the left/right of the comma as separator 152 m_types.addAll(Arrays.asList(type.trim().split(" *, *"))); 153 } 154 m_width = width; 155 } 156 157 /** 158 * @see com.google.common.base.Predicate#apply(java.lang.Object) 159 */ 160 public boolean apply(I_CmsFormatterBean formatter) { 161 162 return matchFormatter(formatter, m_types, m_width); 163 } 164 } 165 166 /** The empty formatter configuration. */ 167 public static final CmsFormatterConfiguration EMPTY_CONFIGURATION = new CmsFormatterConfiguration(null, null); 168 169 /** The log instance for this class. */ 170 public static final Log LOG = CmsLog.getLog(CmsFormatterConfiguration.class); 171 172 /** The container width to match all width configured formatters. */ 173 public static final int MATCH_ALL_CONTAINER_WIDTH = -2; 174 175 /** CmsObject used to read the JSP resources configured in the XSD schema. */ 176 private static CmsObject m_adminCms; 177 178 /** All formatters that have been added to this configuration. */ 179 private List<I_CmsFormatterBean> m_allFormatters; 180 181 /** The available display formatters. */ 182 private List<I_CmsFormatterBean> m_displayFormatters; 183 184 /** Cache for the searchContent option. */ 185 private Map<CmsUUID, Boolean> m_searchContent = Maps.newHashMap(); 186 187 /** 188 * Creates a new formatter configuration based on the given list of formatters.<p> 189 * 190 * @param cms the current users OpenCms context 191 * @param formatters the list of configured formatters 192 */ 193 private CmsFormatterConfiguration(CmsObject cms, List<I_CmsFormatterBean> formatters) { 194 195 if (formatters == null) { 196 // this is needed for the empty configuration 197 m_allFormatters = Collections.emptyList(); 198 } else { 199 m_allFormatters = new ArrayList<I_CmsFormatterBean>(formatters); 200 } 201 init(cms, m_adminCms); 202 } 203 204 /** 205 * Returns the formatter configuration for the current project based on the given list of formatters.<p> 206 * 207 * @param cms the current users OpenCms context, required to know which project to read the JSP from 208 * @param formatters the list of configured formatters 209 * 210 * @return the formatter configuration for the current project based on the given list of formatters 211 */ 212 public static CmsFormatterConfiguration create(CmsObject cms, List<I_CmsFormatterBean> formatters) { 213 214 if ((formatters != null) && (formatters.size() > 0) && (cms != null)) { 215 return new CmsFormatterConfiguration(cms, formatters); 216 } else { 217 return EMPTY_CONFIGURATION; 218 } 219 } 220 221 /** 222 * Initialize the formatter configuration.<p> 223 * 224 * @param cms an initialized admin OpenCms user context 225 * 226 * @throws CmsException in case the initialization fails 227 */ 228 public static void initialize(CmsObject cms) throws CmsException { 229 230 OpenCms.getRoleManager().checkRole(cms, CmsRole.ADMINISTRATOR); 231 try { 232 // store the Admin cms to index Cms resources 233 m_adminCms = OpenCms.initCmsObject(cms); 234 m_adminCms.getRequestContext().setSiteRoot(""); 235 } catch (CmsException e) { 236 // this should never happen 237 } 238 } 239 240 /** 241 * Checks whether the given formatter bean matches the container types, width and nested flag.<p> 242 * 243 * @param formatter the formatter bean 244 * @param types the container types 245 * @param width the container width 246 * 247 * @return <code>true</code> in case the formatter matches 248 */ 249 public static boolean matchFormatter(I_CmsFormatterBean formatter, Set<String> types, int width) { 250 251 if (formatter.isMatchAll()) { 252 return true; 253 } 254 if (formatter.isTypeFormatter()) { 255 return !Sets.intersection(types, formatter.getContainerTypes()).isEmpty(); 256 } else { 257 return (width == MATCH_ALL_CONTAINER_WIDTH) 258 || ((formatter.getMinWidth() <= width) && (width <= formatter.getMaxWidth())); 259 } 260 } 261 262 /** 263 * Checks whether the given formatter bean matches the container types, width and nested flag.<p> 264 * 265 * @param formatter the formatter bean 266 * @param types the container types 267 * @param width the container width 268 * 269 * @return <code>true</code> in case the formatter matches 270 */ 271 public static boolean matchFormatter(I_CmsFormatterBean formatter, String types, int width) { 272 273 return new MatchesTypeOrWidth(types, width).apply(formatter); 274 } 275 276 /** 277 * Gets a list of all defined formatters.<p> 278 * 279 * @return the list of all formatters 280 */ 281 public List<I_CmsFormatterBean> getAllFormatters() { 282 283 return new ArrayList<I_CmsFormatterBean>(m_allFormatters); 284 } 285 286 /** 287 * Gets the formatters which are available for the given container type and width.<p> 288 * 289 * @param containerTypes the container types (comma separated) 290 * @param containerWidth the container width 291 * 292 * @return the list of available formatters 293 */ 294 public List<I_CmsFormatterBean> getAllMatchingFormatters(String containerTypes, int containerWidth) { 295 296 return new ArrayList<I_CmsFormatterBean>( 297 Collections2.filter(m_allFormatters, new MatchesTypeOrWidth(containerTypes, containerWidth))); 298 299 } 300 301 /** 302 * Selects the best matching formatter for the provided type and width from this configuration.<p> 303 * 304 * This method first tries to find the formatter for the provided container type. 305 * If this fails, it returns the width based formatter that matched the container width.<p> 306 * 307 * @param containerTypes the container types (comma separated) 308 * @param containerWidth the container width 309 * 310 * @return the matching formatter, or <code>null</code> if none was found 311 */ 312 public I_CmsFormatterBean getDefaultFormatter(final String containerTypes, final int containerWidth) { 313 314 Optional<I_CmsFormatterBean> result = Iterables.tryFind( 315 m_allFormatters, 316 new MatchesTypeOrWidth(containerTypes, containerWidth)); 317 return result.orNull(); 318 } 319 320 /** 321 * Selects the best matching schema formatter for the provided type and width from this configuration.<p> 322 * 323 * @param containerTypes the container types (comma separated) 324 * @param containerWidth the container width 325 * 326 * @return the matching formatter, or <code>null</code> if none was found 327 */ 328 public I_CmsFormatterBean getDefaultSchemaFormatter(final String containerTypes, final int containerWidth) { 329 330 Optional<I_CmsFormatterBean> result = Iterables.tryFind( 331 m_allFormatters, 332 Predicates.and(new IsSchemaFormatter(), new MatchesTypeOrWidth(containerTypes, containerWidth))); 333 return result.orNull(); 334 } 335 336 /** 337 * Gets the detail formatter to use for the given type and container width.<p> 338 * 339 * @param types the container types (comma separated) 340 * @param containerWidth the container width 341 * 342 * @return the detail formatter to use 343 */ 344 public I_CmsFormatterBean getDetailFormatter(String types, int containerWidth) { 345 346 // detail formatters must still match the type or width 347 Predicate<I_CmsFormatterBean> checkValidDetailFormatter = Predicates.and( 348 new MatchesTypeOrWidth(types, containerWidth), 349 new IsDetail()); 350 Optional<I_CmsFormatterBean> result = Iterables.tryFind(m_allFormatters, checkValidDetailFormatter); 351 return result.orNull(); 352 } 353 354 /** 355 * Gets all detail formatters.<p> 356 * 357 * @return the detail formatters 358 */ 359 public Collection<I_CmsFormatterBean> getDetailFormatters() { 360 361 return Collections.<I_CmsFormatterBean> unmodifiableCollection( 362 Collections2.filter(m_allFormatters, new IsDetail())); 363 } 364 365 /** 366 * Returns the display formatter for this type.<p> 367 * 368 * @return the display formatter 369 */ 370 public I_CmsFormatterBean getDisplayFormatter() { 371 372 if (!getDisplayFormatters().isEmpty()) { 373 return getDisplayFormatters().get(0); 374 } 375 return null; 376 } 377 378 /** 379 * Returns the available display formatters.<p> 380 * 381 * @return the display formatters 382 */ 383 public List<I_CmsFormatterBean> getDisplayFormatters() { 384 385 if (m_displayFormatters == null) { 386 List<I_CmsFormatterBean> formatters = new ArrayList<I_CmsFormatterBean>( 387 Collections2.filter(m_allFormatters, new IsDisplay())); 388 if (formatters.size() > 1) { 389 Collections.sort(formatters, new Comparator<I_CmsFormatterBean>() { 390 391 public int compare(I_CmsFormatterBean o1, I_CmsFormatterBean o2) { 392 393 return o1.getRank() == o2.getRank() ? 0 : (o1.getRank() < o2.getRank() ? -1 : 1); 394 } 395 }); 396 } 397 m_displayFormatters = Collections.unmodifiableList(formatters); 398 } 399 return m_displayFormatters; 400 } 401 402 /** 403 * Returns the formatters available for selection for the given container type and width.<p> 404 * 405 * @param containerTypes the container types (comma separated) 406 * @param containerWidth the container width 407 * 408 * @return the list of available formatters 409 */ 410 public Map<String, I_CmsFormatterBean> getFormatterSelection(String containerTypes, int containerWidth) { 411 412 Map<String, I_CmsFormatterBean> result = new LinkedHashMap<String, I_CmsFormatterBean>(); 413 for (I_CmsFormatterBean formatter : Collections2.filter( 414 m_allFormatters, 415 new MatchesTypeOrWidth(containerTypes, containerWidth))) { 416 if (formatter.isFromFormatterConfigFile()) { 417 result.put(formatter.getId(), formatter); 418 } else { 419 result.put( 420 CmsFormatterConfig.SCHEMA_FORMATTER_ID + formatter.getJspStructureId().toString(), 421 formatter); 422 } 423 } 424 return result; 425 } 426 427 public Map<String, I_CmsFormatterBean> getFormatterSelectionByKeyOrId(String containerTypes, int containerWidth) { 428 429 Map<String, I_CmsFormatterBean> result = new LinkedHashMap<String, I_CmsFormatterBean>(); 430 for (I_CmsFormatterBean formatter : Collections2.filter( 431 m_allFormatters, 432 new MatchesTypeOrWidth(containerTypes, containerWidth))) { 433 if (formatter.isFromFormatterConfigFile()) { 434 result.put(formatter.getId(), formatter); 435 if (formatter.getKey() != null) { 436 result.put(formatter.getKey(), formatter); 437 } 438 } else { 439 result.put( 440 CmsFormatterConfig.SCHEMA_FORMATTER_ID + formatter.getJspStructureId().toString(), 441 formatter); 442 } 443 } 444 return result; 445 446 } 447 448 /** 449 * Gets the list of formatters for the given key or id (also supports schema_formatter ids). 450 * 451 * @param key a formatter key or id 452 * @return the list of formatters for the given key 453 */ 454 public List<I_CmsFormatterBean> getFormattersForKey(String key) { 455 456 if (key == null) { 457 return new ArrayList<>(); 458 } 459 List<I_CmsFormatterBean> result = new ArrayList<>(); 460 461 if (key.startsWith(CmsFormatterConfig.SCHEMA_FORMATTER_ID)) { 462 String idStr = key.substring(CmsFormatterConfig.SCHEMA_FORMATTER_ID.length()); 463 try { 464 CmsUUID id = new CmsUUID(idStr); 465 for (I_CmsFormatterBean formatter : m_allFormatters) { 466 if (!formatter.isFromFormatterConfigFile() && formatter.getJspStructureId().equals(id)) { 467 result.add(formatter); 468 } 469 } 470 } catch (NumberFormatException e) { 471 // ignore 472 } 473 } 474 for (I_CmsFormatterBean formatter : m_allFormatters) { 475 if ((formatter.getKey() != null) && key.equals(formatter.getKey())) { 476 result.add(formatter); 477 } else if ((formatter.getId() != null) && key.equals(formatter.getId().toString())) { 478 result.add(formatter); 479 } 480 } 481 return result; 482 } 483 484 /** 485 * Returns the formatter from this configuration that is to be used for the preview in the ADE gallery GUI, 486 * or <code>null</code> if there is no preview formatter configured.<p> 487 * 488 * @return the formatter from this configuration that is to be used for the preview in the ADE gallery GUI, 489 * or <code>null</code> if there is no preview formatter configured 490 */ 491 public I_CmsFormatterBean getPreviewFormatter() { 492 493 Optional<I_CmsFormatterBean> result; 494 result = Iterables.tryFind(m_allFormatters, new Predicate<I_CmsFormatterBean>() { 495 496 public boolean apply(I_CmsFormatterBean formatter) { 497 498 return formatter.isPreviewFormatter(); 499 } 500 }); 501 if (!result.isPresent()) { 502 result = Iterables.tryFind(m_allFormatters, new Predicate<I_CmsFormatterBean>() { 503 504 public boolean apply(I_CmsFormatterBean formatter) { 505 506 if (formatter.isTypeFormatter()) { 507 return formatter.getContainerTypes().contains(CmsFormatterBean.PREVIEW_TYPE); 508 } else { 509 return (formatter.getMinWidth() <= CmsFormatterBean.PREVIEW_WIDTH) 510 && (CmsFormatterBean.PREVIEW_WIDTH <= formatter.getMaxWidth()); 511 } 512 } 513 }); 514 } 515 if (!result.isPresent()) { 516 result = Iterables.tryFind(m_allFormatters, new Predicate<I_CmsFormatterBean>() { 517 518 public boolean apply(I_CmsFormatterBean formatter) { 519 520 return !formatter.isTypeFormatter() && (formatter.getMaxWidth() >= CmsFormatterBean.PREVIEW_WIDTH); 521 522 } 523 }); 524 } 525 if (!result.isPresent() && !m_allFormatters.isEmpty()) { 526 result = Optional.fromNullable(m_allFormatters.iterator().next()); 527 } 528 return result.orNull(); 529 } 530 531 /** 532 * Returns the provided <code>true</code> in case this configuration has a formatter 533 * for the given type / width parameters.<p> 534 * 535 * @param containerTypes the container types (comma separated) 536 * @param containerWidth the container width 537 * 538 * @return the provided <code>true</code> in case this configuration has a formatter 539 * for the given type / width parameters. 540 */ 541 public boolean hasFormatter(String containerTypes, int containerWidth) { 542 543 return getDefaultFormatter(containerTypes, containerWidth) != null; 544 } 545 546 /** 547 * Returns <code>true</code> in case there is at least one usable formatter configured in this configuration.<p> 548 * 549 * @return <code>true</code> in case there is at least one usable formatter configured in this configuration 550 */ 551 public boolean hasFormatters() { 552 553 return !m_allFormatters.isEmpty(); 554 } 555 556 /** 557 * Returns <code>true</code> in case this configuration contains a formatter with the 558 * provided structure id that has been configured for including the formatted content in the online search.<p> 559 * 560 * @param formatterStructureId the formatter structure id 561 * 562 * @return <code>true</code> in case this configuration contains a formatter with the 563 * provided structure id that has been configured for including the formatted content in the online search 564 */ 565 public boolean isSearchContent(CmsUUID formatterStructureId) { 566 567 if (EMPTY_CONFIGURATION == this) { 568 // don't search if this is just the empty configuration 569 return false; 570 } 571 // lookup the cache 572 Boolean result = m_searchContent.get(formatterStructureId); 573 if (result == null) { 574 // result so far unknown 575 for (I_CmsFormatterBean formatter : m_allFormatters) { 576 if (formatter.getJspStructureId().equals(formatterStructureId)) { 577 // found the match 578 result = Boolean.valueOf(formatter.isSearchContent()); 579 // first match rules 580 break; 581 } 582 } 583 if (result == null) { 584 // no match found, in this case dont search the content 585 result = Boolean.FALSE; 586 } 587 // store result in the cache 588 m_searchContent.put(formatterStructureId, result); 589 } 590 591 return result.booleanValue(); 592 } 593 594 /** 595 * Initializes all formatters of this configuration.<p> 596 * 597 * It is also checked if the configured JSP root path exists, if not the formatter is removed 598 * as it is unusable.<p> 599 * 600 * @param userCms the current users OpenCms context, used for selecting the right project 601 * @param adminCms the Admin user context to use for reading the JSP resources 602 */ 603 private void init(CmsObject userCms, CmsObject adminCms) { 604 605 List<I_CmsFormatterBean> filteredFormatters = new ArrayList<I_CmsFormatterBean>(); 606 for (I_CmsFormatterBean formatter : m_allFormatters) { 607 608 if (formatter.getJspStructureId() == null) { 609 // a formatter may have been re-used so the structure id is already available 610 CmsResource res = null; 611 // first we make sure that the JSP exists at all (and also we read the UUID that way) 612 try { 613 // first get a cms copy so we can mess up the context without modifying the original 614 CmsObject cmsCopy = OpenCms.initCmsObject(adminCms); 615 cmsCopy.getRequestContext().setCurrentProject(userCms.getRequestContext().getCurrentProject()); 616 // switch to the root site 617 cmsCopy.getRequestContext().setSiteRoot(""); 618 // now read the JSP 619 res = cmsCopy.readResource(formatter.getJspRootPath()); 620 } catch (CmsException e) { 621 //if this happens the result is null and we write a LOG error 622 } 623 if ((res == null) || !CmsResourceTypeJsp.isJsp(res)) { 624 // the formatter must exist and it must be a JSP 625 LOG.error( 626 Messages.get().getBundle().key( 627 Messages.ERR_FORMATTER_JSP_DONT_EXIST_1, 628 formatter.getJspRootPath())); 629 } else { 630 formatter.setJspStructureId(res.getStructureId()); 631 // res may still be null in case of failure 632 } 633 } 634 635 if (formatter.getJspStructureId() != null) { 636 filteredFormatters.add(formatter); 637 } else { 638 LOG.warn("Invalid formatter: " + formatter.getJspRootPath()); 639 } 640 } 641 Collections.sort(filteredFormatters, new FormatterComparator()); 642 m_allFormatters = Collections.unmodifiableList(filteredFormatters); 643 } 644 645}