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.jsp.util; 029 030import org.opencms.acacia.shared.CmsTabInfo; 031import org.opencms.ade.contenteditor.CmsContentTypeVisitor; 032import org.opencms.ade.contenteditor.CmsWidgetUtil; 033import org.opencms.ade.contenteditor.CmsWidgetUtil.WidgetInfo; 034import org.opencms.file.CmsObject; 035import org.opencms.i18n.CmsMessages; 036import org.opencms.i18n.CmsMultiMessages; 037import org.opencms.main.CmsLog; 038import org.opencms.main.OpenCms; 039import org.opencms.util.CmsMacroResolver; 040import org.opencms.util.CmsStringUtil; 041import org.opencms.widgets.A_CmsWidget; 042import org.opencms.widgets.CmsSelectWidgetOption; 043import org.opencms.xml.CmsXmlContentDefinition; 044import org.opencms.xml.content.CmsDefaultXmlContentHandler; 045import org.opencms.xml.content.I_CmsXmlContentHandler; 046import org.opencms.xml.types.CmsXmlNestedContentDefinition; 047import org.opencms.xml.types.I_CmsXmlSchemaType; 048 049import java.util.ArrayList; 050import java.util.Collection; 051import java.util.LinkedHashMap; 052import java.util.List; 053import java.util.Locale; 054import java.util.Set; 055import java.util.stream.Collectors; 056 057import org.apache.commons.logging.Log; 058 059/** 060 * Bean for accessing XML content schema information from JSPs. 061 */ 062public class CmsSchemaInfo { 063 064 /** 065 * Represents information about a single field in a content schema. 066 */ 067 public class Field { 068 069 /** The nested fields. */ 070 private LinkedHashMap<String, Field> m_children = new LinkedHashMap<>(); 071 072 /** The content definition (may be null). */ 073 private CmsXmlContentDefinition m_contentDefinition; 074 075 /** The field. */ 076 private I_CmsXmlSchemaType m_field; 077 078 /** The path of the field. */ 079 private String m_path = ""; 080 081 /** The widget information. */ 082 private WidgetInfo m_widgetInfo; 083 084 /** 085 * Creates a new instance for the root level of a schema. 086 * 087 * @param contentDef the content definition 088 */ 089 public Field(CmsXmlContentDefinition contentDef) { 090 091 m_contentDefinition = contentDef; 092 m_path = ""; 093 processContentDefinition(m_contentDefinition); 094 } 095 096 /** 097 * Creates a new instance. 098 * 099 * @param field the schema type for the field 100 * @param path the path of the field 101 */ 102 public Field(I_CmsXmlSchemaType field, String path) { 103 104 m_path = path; 105 m_field = field; 106 if (field instanceof CmsXmlNestedContentDefinition) { 107 CmsXmlNestedContentDefinition nestedDef = (CmsXmlNestedContentDefinition)field; 108 m_contentDefinition = nestedDef.getNestedContentDefinition(); 109 processContentDefinition(m_contentDefinition); 110 } 111 } 112 113 /** 114 * Gets the nested fields, if any. 115 * 116 * 117 * @return the nested fields 118 */ 119 public Collection<Field> getChildren() { 120 121 return m_children.values(); 122 } 123 124 /** 125 * Gets the content definition. 126 * 127 * @return the content definition 128 */ 129 public CmsXmlContentDefinition getContentDefinition() { 130 131 return m_contentDefinition; 132 } 133 134 /** 135 * Gets the default value. 136 * 137 * @return the default value 138 */ 139 @SuppressWarnings("synthetic-access") 140 public String getDefaultValue() { 141 142 if (m_field == null) { 143 // root node 144 return null; 145 } 146 return m_root.getContentDefinition().getContentHandler().getDefault(m_cms, null, m_field, m_path, m_locale); 147 } 148 149 /** 150 * Gets the description. 151 * 152 * @return the description 153 */ 154 public String getDescription() { 155 156 return getDescription(false); 157 } 158 159 /** 160 * Gets the description key. 161 * 162 * @return the description key 163 */ 164 public String getDescriptionKey() { 165 166 return getDescription(true); 167 } 168 169 /** 170 * Gets the display name. 171 * 172 * @return the display name 173 */ 174 public String getDisplayName() { 175 176 return getDisplayName(false); 177 } 178 179 /** 180 * Gets the display name key. 181 * 182 * @return the display name key 183 */ 184 public String getDisplayNameKey() { 185 186 return getDisplayName(true); 187 } 188 189 /** 190 * Checks if this is a choice type. 191 * 192 * @return true if this is a choice type 193 */ 194 public boolean getIsChoice() { 195 196 return m_field.isChoiceType(); 197 } 198 199 /** 200 * Checks if this is a nested content type. 201 * 202 * @return true if this is a nested content type 203 */ 204 public boolean getIsNestedContent() { 205 206 return m_contentDefinition != null; 207 } 208 209 /** 210 * Gets the maximum number of occurrences. 211 * 212 * @return the maximum number of occurrences 213 */ 214 public int getMaxOccurs() { 215 216 return m_field.getMaxOccurs(); 217 } 218 219 /** 220 * Gets the minimum number of occurrences. 221 * 222 * @return the minimum number of occurrences 223 */ 224 public int getMinOccurs() { 225 226 return m_field.getMinOccurs(); 227 } 228 229 /** 230 * Gets the field name. 231 * 232 * @return the name 233 */ 234 public String getName() { 235 236 if (m_field == null) { 237 return null; 238 } 239 return m_field.getName(); 240 } 241 242 /** 243 * Tries to interpret the widget configuration as a select option configuration and returns the list of select options if this succeeds, and null otherwise. 244 * 245 * @return the list of parsed select options, or null if the widget configuration couldn't be interpreted that way 246 */ 247 @SuppressWarnings({"synthetic-access"}) 248 public List<CmsSelectWidgetOption> getParsedSelectOptions() { 249 250 String widgetConfig = getWidgetConfig(); 251 if (CmsStringUtil.isEmptyOrWhitespaceOnly(widgetConfig)) { 252 // passing an empty/null configuration to parseOptions would result in an empty list, not null, and we want null here 253 return null; 254 } 255 try { 256 List<CmsSelectWidgetOption> options = org.opencms.widgets.CmsSelectWidgetOption.parseOptions( 257 widgetConfig); 258 List<CmsSelectWidgetOption> result = new ArrayList<>(); 259 Set<String> values = options.stream().map(option -> option.getValue()).collect(Collectors.toSet()); 260 String defaultValue = getDefaultValue(); 261 Locale locale = m_cms.getRequestContext().getLocale(); 262 if (CmsStringUtil.isEmptyOrWhitespaceOnly(defaultValue) || !values.contains(defaultValue)) { 263 CmsSelectWidgetOption noValue = new CmsSelectWidgetOption( 264 "", 265 true, 266 org.opencms.gwt.Messages.get().getBundle(locale).key( 267 org.opencms.gwt.Messages.GUI_SELECTBOX_EMPTY_SELECTION_0)); 268 result.add(noValue); 269 } 270 271 result.addAll(options); 272 return result; 273 } catch (Exception e) { 274 LOG.info(e.getLocalizedMessage(), e); 275 return null; 276 } 277 } 278 279 /** 280 * Gets the field type. 281 * 282 * @return the type 283 */ 284 public String getType() { 285 286 return m_field.getTypeName(); 287 } 288 289 /** 290 * Gets the validation error message. 291 * 292 * @return the validation error message 293 */ 294 @SuppressWarnings("synthetic-access") 295 public String getValidationError() { 296 297 if (m_field == null) { 298 return null; 299 } 300 I_CmsXmlContentHandler handler = m_field.getContentDefinition().getContentHandler(); 301 if (!(handler instanceof CmsDefaultXmlContentHandler)) { 302 return null; 303 } 304 CmsDefaultXmlContentHandler defaultHandler = (CmsDefaultXmlContentHandler)handler; 305 return defaultHandler.getValidationWarningOrErrorMessage(m_cms, m_locale, m_field.getName(), false, false); 306 } 307 308 /** 309 * Gets the validation error localization key. 310 * 311 * @return the validation error localization key 312 */ 313 @SuppressWarnings("synthetic-access") 314 public String getValidationErrorKey() { 315 316 if (m_field == null) { 317 return null; 318 } 319 I_CmsXmlContentHandler handler = m_field.getContentDefinition().getContentHandler(); 320 if (!(handler instanceof CmsDefaultXmlContentHandler)) { 321 return null; 322 } 323 CmsDefaultXmlContentHandler defaultHandler = (CmsDefaultXmlContentHandler)handler; 324 return defaultHandler.getValidationWarningOrErrorMessage(m_cms, m_locale, m_field.getName(), false, true); 325 } 326 327 /** 328 * Gets the validation warning message. 329 * 330 * @return the validation warning message 331 */ 332 @SuppressWarnings({"synthetic-access"}) 333 public String getValidationWarning() { 334 335 if (m_field == null) { 336 return null; 337 } 338 I_CmsXmlContentHandler handler = m_field.getContentDefinition().getContentHandler(); 339 if (!(handler instanceof CmsDefaultXmlContentHandler)) { 340 return null; 341 } 342 CmsDefaultXmlContentHandler defaultHandler = (CmsDefaultXmlContentHandler)handler; 343 return defaultHandler.getValidationWarningOrErrorMessage(m_cms, m_locale, m_field.getName(), true, false); 344 } 345 346 /** 347 * Gets the validation warning message key. 348 * 349 * @return the validation warning message key 350 */ 351 @SuppressWarnings("synthetic-access") 352 public String getValidationWarningKey() { 353 354 if (m_field == null) { 355 return null; 356 } 357 I_CmsXmlContentHandler handler = m_field.getContentDefinition().getContentHandler(); 358 if (!(handler instanceof CmsDefaultXmlContentHandler)) { 359 return null; 360 } 361 CmsDefaultXmlContentHandler defaultHandler = (CmsDefaultXmlContentHandler)handler; 362 return defaultHandler.getValidationWarningOrErrorMessage(m_cms, m_locale, m_field.getName(), true, true); 363 } 364 365 /** 366 * Gets the widget. 367 * 368 * @return the widget 369 */ 370 @SuppressWarnings("synthetic-access") 371 public String getWidget() { 372 373 if (m_contentDefinition != null) { 374 return null; 375 } 376 WidgetInfo widgetInfo = getWidgetInfo(); 377 if (widgetInfo != null) { 378 return widgetInfo.getWidget().getClass().getName(); 379 } else { 380 LOG.error("Widget info not defined for " + m_path + " in " + m_root.getContentDefinition()); 381 return null; 382 } 383 } 384 385 /** 386 * Gets the widget configuration. 387 * 388 * @return the widget configuration 389 */ 390 @SuppressWarnings("synthetic-access") 391 public String getWidgetConfig() { 392 393 if (m_contentDefinition != null) { 394 return null; 395 } 396 WidgetInfo widgetInfo = getWidgetInfo(); 397 if (widgetInfo != null) { 398 return widgetInfo.getWidget().getConfiguration(); 399 } else { 400 LOG.error("Widget info not defined for " + m_path + " in " + m_root.getContentDefinition()); 401 return null; 402 } 403 404 } 405 406 /** 407 * Gets the description or description key. 408 * 409 * @param keyOnly true if only the localization key should be returned rather than the localized string 410 * @return the description or localization key 411 */ 412 @SuppressWarnings("synthetic-access") 413 private String getDescription(boolean keyOnly) { 414 415 if (m_field == null) { 416 return null; 417 } 418 StringBuffer result = new StringBuffer(64); 419 I_CmsXmlContentHandler handler = m_field.getContentDefinition().getContentHandler(); 420 if (handler instanceof CmsDefaultXmlContentHandler) { 421 CmsDefaultXmlContentHandler defaultHandler = (CmsDefaultXmlContentHandler)handler; 422 String help = defaultHandler.getFieldHelp().get(m_field.getName()); 423 if (help != null) { 424 CmsMacroResolver resolver = new CmsMacroResolver(); 425 if (keyOnly) { 426 resolver = new CmsKeyDummyMacroResolver(resolver); 427 } 428 resolver.setCmsObject(m_cms); 429 resolver.setKeepEmptyMacros(true); 430 resolver.setMessages(m_messages); 431 432 String val = resolver.resolveMacros(help); 433 if (keyOnly) { 434 return CmsKeyDummyMacroResolver.getKey(val); 435 } else { 436 return val; 437 } 438 } 439 } 440 result.append(A_CmsWidget.LABEL_PREFIX); 441 result.append(getTypeKey(m_field)); 442 result.append(A_CmsWidget.HELP_POSTFIX); 443 if (keyOnly) { 444 return result.toString(); 445 } else { 446 return m_messages.keyDefault(result.toString(), null); 447 } 448 } 449 450 /** 451 * Gets the display name or localization key. 452 * 453 * @param keyOnly true if only the localization key should be returned rather than the localized display name 454 * @return the display name or localization key 455 */ 456 @SuppressWarnings("synthetic-access") 457 private String getDisplayName(boolean keyOnly) { 458 459 if (m_field == null) { 460 return null; 461 } 462 I_CmsXmlContentHandler handler = m_field.getContentDefinition().getContentHandler(); 463 if (handler instanceof CmsDefaultXmlContentHandler) { 464 CmsDefaultXmlContentHandler defaultHandler = (CmsDefaultXmlContentHandler)handler; 465 String label = defaultHandler.getFieldLabels().get(m_field.getName()); 466 if (label != null) { 467 CmsMacroResolver resolver = new CmsMacroResolver(); 468 if (keyOnly) { 469 resolver = new CmsKeyDummyMacroResolver(resolver); 470 } 471 resolver.setCmsObject(m_cms); 472 resolver.setKeepEmptyMacros(true); 473 resolver.setMessages(m_messages); 474 String val = resolver.resolveMacros(label); 475 if (keyOnly) { 476 return CmsKeyDummyMacroResolver.getKey(val); 477 } else { 478 return val; 479 } 480 } 481 } 482 StringBuffer result = new StringBuffer(64); 483 result.append(A_CmsWidget.LABEL_PREFIX); 484 result.append(getTypeKey(m_field)); 485 if (keyOnly) { 486 return result.toString(); 487 } else { 488 return m_messages.keyDefault(result.toString(), m_field.getName()); 489 } 490 491 } 492 493 /** 494 * Returns the schema type message key.<p> 495 * 496 * @param value the schema type 497 * 498 * @return the schema type message key 499 */ 500 private String getTypeKey(I_CmsXmlSchemaType value) { 501 502 StringBuffer result = new StringBuffer(64); 503 result.append(value.getContentDefinition().getInnerName()); 504 result.append('.'); 505 result.append(value.getName()); 506 return result.toString(); 507 } 508 509 /** 510 * Gets the widget info. 511 * 512 * @return the widget info 513 */ 514 @SuppressWarnings("synthetic-access") 515 private WidgetInfo getWidgetInfo() { 516 517 if (m_widgetInfo == null) { 518 m_widgetInfo = CmsWidgetUtil.collectWidgetInfo(m_cms, m_root.getContentDefinition(), m_path, null); 519 } 520 return m_widgetInfo; 521 522 } 523 524 /** 525 * Process content definition. 526 * 527 * @param contentDef the content definition 528 */ 529 private void processContentDefinition(CmsXmlContentDefinition contentDef) { 530 531 List<I_CmsXmlSchemaType> fields = contentDef.getTypeSequence(); 532 for (I_CmsXmlSchemaType field : fields) { 533 String name = field.getName(); 534 m_children.put(name, new Field(field, combinePaths(m_path, name))); 535 } 536 } 537 538 } 539 540 /** 541 * Represents the a single editor tab and its fields. 542 */ 543 public class Tab { 544 545 /** The display name. */ 546 private String m_displayName; 547 548 /** The display name key. */ 549 private String m_displayNameKey; 550 551 /** The description. */ 552 private String m_description; 553 554 /** The description key. */ 555 private String m_descriptionKey; 556 557 /** The fields. */ 558 private List<Field> m_fields = new ArrayList<>(); 559 560 /** 561 * Adds a field. 562 * 563 * @param field the field to add 564 */ 565 public void add(Field field) { 566 567 m_fields.add(field); 568 } 569 570 /** 571 * Gets the description. 572 * 573 * @return the description 574 */ 575 public String getDescription() { 576 577 return m_description; 578 } 579 580 /** 581 * Gets the description key. 582 * 583 * @return the description key 584 */ 585 public String getDescriptionKey() { 586 587 return m_descriptionKey; 588 } 589 590 /** 591 * Gets the display name. 592 * 593 * @return the display name 594 */ 595 public String getDisplayName() { 596 597 return m_displayName; 598 } 599 600 /** 601 * Gets the display name key. 602 * 603 * @return the display name key 604 */ 605 public String getDisplayNameKey() { 606 607 return m_displayNameKey; 608 } 609 610 /** 611 * Gets the fields. 612 * 613 * @return the fields 614 */ 615 public List<Field> getFields() { 616 617 return m_fields; 618 } 619 620 /** 621 * Sets the description. 622 * 623 * @param description the new description 624 */ 625 public void setDescription(String description) { 626 627 m_description = description; 628 } 629 630 /** 631 * Sets the description key. 632 * 633 * @param descriptionKey the new description key 634 */ 635 public void setDescriptionKey(String descriptionKey) { 636 637 m_descriptionKey = descriptionKey; 638 } 639 640 /** 641 * Sets the display name. 642 * 643 * @param displayName the new display name 644 */ 645 public void setDisplayName(String displayName) { 646 647 m_displayName = displayName; 648 } 649 650 /** 651 * Sets the display name key. 652 * 653 * @param displayNameKey the new display name key 654 */ 655 public void setDisplayNameKey(String displayNameKey) { 656 657 m_displayNameKey = displayNameKey; 658 } 659 660 } 661 662 /** The logger instance for this class. */ 663 private static final Log LOG = CmsLog.getLog(CmsSchemaInfo.class); 664 665 /** The CMS context. */ 666 private CmsObject m_cms; 667 668 /** The locale. */ 669 private Locale m_locale; 670 671 /** The messages. */ 672 private CmsMultiMessages m_messages; 673 674 /** The root field instanec representing the whole schema. */ 675 private Field m_root; 676 677 /** The tabs. */ 678 private List<Tab> m_tabs; 679 680 /** 681 * Creates a new instance. 682 * 683 * @param cms the CMS context 684 * @param contentDef the content definition 685 */ 686 public CmsSchemaInfo(CmsObject cms, CmsXmlContentDefinition contentDef) { 687 688 m_cms = cms; 689 m_locale = cms.getRequestContext().getLocale(); 690 m_root = new Field(contentDef); 691 CmsMessages messages = null; 692 m_messages = new CmsMultiMessages(m_locale); 693 m_messages.setFallbackHandler(contentDef.getContentHandler().getMessageKeyHandler()); 694 695 try { 696 messages = OpenCms.getWorkplaceManager().getMessages(m_locale); 697 if (messages != null) { 698 m_messages.addMessages(messages); 699 } 700 messages = contentDef.getContentHandler().getMessages(m_locale); 701 if (messages != null) { 702 m_messages.addMessages(messages); 703 } 704 } catch (Exception e) { 705 LOG.debug(e.getMessage(), e); 706 } 707 initTabs(); 708 709 } 710 711 /** 712 * Combine schema paths. 713 * 714 * @param a the first path 715 * @param b the second path 716 * @return the combined paths 717 */ 718 static String combinePaths(String a, String b) { 719 720 if ("".equals(a)) { 721 return b; 722 } 723 return a + "/" + b; 724 } 725 726 /** 727 * Gets the root node. 728 * 729 * @return the root node 730 */ 731 public Field getRoot() { 732 733 return m_root; 734 } 735 736 /** 737 * Gets the tabs. 738 * 739 * @return the tabs 740 */ 741 public List<Tab> getTabs() { 742 743 return m_tabs; 744 } 745 746 /** 747 * Checks for tabs. 748 * 749 * @return true, if there are tabs 750 */ 751 public boolean hasTabs() { 752 753 return m_tabs.get(0).getDisplayName() == null; 754 } 755 756 /** 757 * Initializes the tabs. 758 */ 759 private void initTabs() { 760 761 List<CmsTabInfo> tabs = CmsContentTypeVisitor.collectTabInfos(m_cms, m_root.getContentDefinition(), m_messages); 762 763 List<Tab> result = new ArrayList<>(); 764 if ((tabs == null) || (tabs.size() == 0)) { 765 Tab defaultTab = new Tab(); 766 result.add(defaultTab); 767 defaultTab.getFields().addAll(m_root.getChildren()); 768 } else { 769 int index = 0; 770 for (Field node : m_root.getChildren()) { 771 if ((index < tabs.size()) && node.getName().equals(tabs.get(index).getStartName())) { 772 Tab tab = new Tab(); 773 774 tab.setDisplayName(tabs.get(index).getTabName()); 775 tab.setDisplayNameKey(tabs.get(index).getTabNameKey()); 776 tab.setDescriptionKey(tabs.get(index).getDescriptionKey()); 777 tab.setDescription(tabs.get(index).getDescription()); 778 result.add(tab); 779 index += 1; 780 } 781 if (result.size() > 0) { 782 result.get(result.size() - 1).add(node); 783 } 784 } 785 } 786 m_tabs = result; 787 } 788}