001/* 002 * Copyright 2018 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.ametys.plugins.contenttypeseditor.edition; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022import java.net.MalformedURLException; 023import java.nio.charset.Charset; 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.LinkedHashMap; 029import java.util.List; 030import java.util.Map; 031import java.util.Map.Entry; 032import java.util.Properties; 033import java.util.Set; 034import java.util.stream.Collectors; 035 036import javax.xml.parsers.SAXParserFactory; 037import javax.xml.transform.OutputKeys; 038import javax.xml.transform.TransformerFactory; 039import javax.xml.transform.sax.SAXTransformerFactory; 040import javax.xml.transform.sax.TransformerHandler; 041import javax.xml.transform.stream.StreamResult; 042 043import org.apache.avalon.framework.component.Component; 044import org.apache.avalon.framework.configuration.Configuration; 045import org.apache.avalon.framework.configuration.ConfigurationException; 046import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 047import org.apache.avalon.framework.service.ServiceException; 048import org.apache.avalon.framework.service.ServiceManager; 049import org.apache.avalon.framework.service.Serviceable; 050import org.apache.cocoon.xml.AttributesImpl; 051import org.apache.cocoon.xml.XMLUtils; 052import org.apache.commons.io.IOUtils; 053import org.apache.commons.lang.StringUtils; 054import org.apache.excalibur.source.ModifiableSource; 055import org.apache.excalibur.source.Source; 056import org.apache.excalibur.source.SourceNotFoundException; 057import org.apache.excalibur.source.SourceResolver; 058import org.apache.excalibur.source.SourceUtil; 059import org.apache.xml.serializer.OutputPropertiesFactory; 060import org.xml.sax.SAXException; 061 062import org.ametys.cms.contenttype.AttributeDefinition; 063import org.ametys.cms.contenttype.ContentAttributeDefinition; 064import org.ametys.cms.contenttype.ContentType; 065import org.ametys.cms.contenttype.ContentTypeDefinition; 066import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 067import org.ametys.cms.contenttype.ContentTypesHelper; 068import org.ametys.cms.contenttype.EditContentTypeException; 069import org.ametys.cms.contenttype.EditContentTypeHelper; 070import org.ametys.cms.contenttype.RichTextAttributeDefinition; 071import org.ametys.cms.data.type.ModelItemTypeConstants; 072import org.ametys.cms.model.ContentRestrictedCompositeDefinition; 073import org.ametys.cms.model.ContentRestrictedRepeaterDefinition; 074import org.ametys.cms.repository.Content; 075import org.ametys.cms.repository.ContentAttributeTypeExtensionPoint; 076import org.ametys.core.util.I18nUtils; 077import org.ametys.core.util.I18nizableTextKeyComparator; 078import org.ametys.plugins.repository.model.RepeaterDefinition; 079import org.ametys.runtime.i18n.I18nizableText; 080import org.ametys.runtime.model.ElementDefinition; 081import org.ametys.runtime.model.Enumerator; 082import org.ametys.runtime.model.ModelItem; 083import org.ametys.runtime.model.ModelItemContainer; 084import org.ametys.runtime.model.ModelItemGroup; 085import org.ametys.runtime.model.ModelViewItemGroup; 086import org.ametys.runtime.model.SimpleViewItemGroup; 087import org.ametys.runtime.model.StaticEnumerator; 088import org.ametys.runtime.model.View; 089import org.ametys.runtime.model.ViewElement; 090import org.ametys.runtime.model.ViewItem; 091import org.ametys.runtime.model.ViewItemContainer; 092import org.ametys.runtime.parameter.DefaultValidator; 093import org.ametys.runtime.parameter.Validator; 094import org.ametys.runtime.plugin.component.AbstractLogEnabled; 095 096/** 097 * This component save a content type 098 */ 099public class SaveContentTypeComponent extends AbstractLogEnabled implements Component, Serviceable 100{ 101 /** The Avalon role name */ 102 public static final String ROLE = SaveContentTypeComponent.class.getName(); 103 104 /** The directory path of application i18n key */ 105 private static final String __I18N_CATALOG_DIR = "context://WEB-INF/i18n/"; 106 107 /** The path of rights on content types */ 108 private static final String __RIGHTS_FILE = "context://WEB-INF/param/rights.xml"; 109 110 /** The right category of a content type */ 111 private static final String __RIGHT_CATEGORY = "plugin.cms:PLUGINS_CMS_RIGHTS_CONTENT_CATEGORY"; 112 113 /** The url to save the right of content type */ 114 private static final String __SAVE_CONTENT_TYPE_RIGHT = "cocoon://_plugins/contenttypes-editor/save/content-type-right"; 115 116 /** The name of the default i18n catalogue */ 117 private static final String __DEFAULT_CATALOGUE = "application"; 118 119 /** The source resolver */ 120 protected SourceResolver _sourceResolver; 121 122 /** The content type extension point instance */ 123 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 124 /** The content types helper */ 125 protected ContentTypesHelper _contentTypesHelper; 126 127 /** The extension point for available attribute types */ 128 protected ContentAttributeTypeExtensionPoint _contentAttributeTypeExtensionPoint; 129 130 /** The content type state instance */ 131 protected ContentTypeStateComponent _contentTypeStateComponent; 132 133 /** The edit content type helper instance */ 134 protected EditContentTypeInformationHelper _editContentTypeInformationHelper; 135 136 /** The edit content type component instance */ 137 protected EditContentTypeHelper _editContentTypeHelper; 138 139 /** I18nUtils instance */ 140 protected I18nUtils _i18nUtils; 141 142 /** Representation of i18n catalog according to the language */ 143 private Map<String, I18nCatalog> _i18nCatalogs = new HashMap<>(); 144 145 private List<TranslatedValue> _translatedValues; 146 147 private int _fieldsetNumber; 148 149 public void service(ServiceManager manager) throws ServiceException 150 { 151 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 152 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 153 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 154 _contentAttributeTypeExtensionPoint = (ContentAttributeTypeExtensionPoint) manager.lookup(ContentAttributeTypeExtensionPoint.ROLE); 155 _contentTypeStateComponent = (ContentTypeStateComponent) manager.lookup(ContentTypeStateComponent.ROLE); 156 _editContentTypeInformationHelper = (EditContentTypeInformationHelper) manager.lookup(EditContentTypeInformationHelper.ROLE); 157 _editContentTypeHelper = (EditContentTypeHelper) manager.lookup(org.ametys.cms.contenttype.EditContentTypeHelper.ROLE); 158 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 159 } 160 161 /** 162 * Save content type 163 * 164 * @param contentTypeInfos all information about the content type to save 165 * @return True if the content type was successfully saved 166 */ 167 public boolean saveContentType(Map<String, Object> contentTypeInfos) 168 { 169 boolean success = true; 170 this._translatedValues = new ArrayList<>(); 171 172 String realContentTypeId = contentTypeInfos.get("id").toString(); 173 ContentTypeDefinition contentTypeDefinition = _getContentTypeDefinition(contentTypeInfos); 174 175 // Sax content type file 176 if (_contentTypeStateComponent.getContentTypeMarkedAsNew().contains(realContentTypeId)) 177 { 178 try 179 { 180 _editContentTypeHelper.createContentType(contentTypeDefinition); 181 182 // Sax right param 183 if (success) 184 { 185 try 186 { 187 // Adding right in param/rights.xml 188 if (contentTypeDefinition.getRight() != null) 189 { 190 String pluginName = contentTypeInfos.get("pluginName").toString(); 191 String idKey = _generateContentTypeIdKey(realContentTypeId); 192 String rightLabelKey = _generateI18nKey(pluginName, "right_create", idKey, "label"); 193 String rightDescriptionKey = _generateI18nKey(pluginName, "right_create", idKey, "description"); 194 _saxRightsParam(contentTypeDefinition.getRight(), rightLabelKey, rightDescriptionKey); 195 } 196 } 197 catch (Exception e) 198 { 199 getLogger().error("Error when trying to save the right of new content type '" + realContentTypeId + "'", e); 200 success = false; 201 } 202 } 203 } 204 catch (EditContentTypeException e) 205 { 206 success = false; 207 } 208 } 209 else 210 { 211 try 212 { 213 _editContentTypeHelper.editContentType(contentTypeDefinition); 214 } 215 catch (EditContentTypeException e) 216 { 217 success = false; 218 } 219 } 220 221 // Sax i18n catalog 222 if (success) 223 { 224 try 225 { 226 // Adding i18n key to catalog 227 Map<String, Map<String, String>> i18nMessageTranslations = _createNewI18nCatalogs(); 228 _saxCatalogs(i18nMessageTranslations); 229 } 230 catch (Exception e) 231 { 232 getLogger().error("Error when trying to save i18n key of new content type '" + realContentTypeId + "'", e); 233 success = false; 234 } 235 } 236 237 return success; 238 } 239 240 private ContentTypeDefinition _getContentTypeDefinition(Map<String, Object> contentTypeInfos) 241 { 242 String realContentTypeId = contentTypeInfos.get("id").toString(); 243 String contentTypeId = realContentTypeId.replaceFirst("content-type.", ""); 244 String pluginName = contentTypeInfos.get("pluginName").toString(); 245 246 ContentType existingContentType = _contentTypeExtensionPoint.getExtension(realContentTypeId); 247 248 ContentTypeDefinition contentType = new ContentTypeDefinition(contentTypeInfos.get("id").toString()); 249 contentType.setPluginName(pluginName); 250 _addGeneralInformation(contentType, existingContentType, contentTypeInfos, pluginName, contentTypeId); 251 _addModelItems(contentType, existingContentType, contentTypeInfos, pluginName, realContentTypeId); 252 _addViews(contentType, existingContentType, realContentTypeId, contentTypeInfos, pluginName, contentTypeId); 253 254 return contentType; 255 } 256 257 private void _addGeneralInformation(ContentTypeDefinition contentType, ContentType existingContentType, Map<String, Object> contentTypeInfos, String pluginName, String contentTypeId) 258 { 259 I18nizableText label = _getI18nizableText(existingContentType != null ? existingContentType.getLabel() : null, contentTypeInfos.get("label"), pluginName, "contenttype", contentTypeId, "label"); 260 contentType.setLabel(label); 261 262 I18nizableText defaultTitle = _getI18nizableText(existingContentType != null ? existingContentType.getDefaultTitle() : null, contentTypeInfos.get("defaultTitle"), pluginName, "contenttype", contentTypeId, "default_title"); 263 contentType.setDefaultTitle(defaultTitle); 264 265 I18nizableText description = _getI18nizableText(existingContentType != null ? existingContentType.getDescription() : null, contentTypeInfos.get("description"), pluginName, "contenttype", contentTypeId, "description"); 266 contentType.setDescription(description); 267 268 _addCategory(contentType, contentTypeInfos.get("category"), contentTypeInfos.get("newCategory"), pluginName); 269 270 _addIconGlyph(contentType, contentTypeInfos.get("iconGlyph")); 271 272 _addTags(contentType, contentTypeInfos.get("tags"), contentTypeInfos.get("private"), contentTypeInfos.get("referencetable"), contentTypeInfos.get("mixin")); 273 274 _addSupertypeIds(contentType, contentTypeInfos.get("superTypes")); 275 276 _addIsAbstract(contentType, contentTypeInfos.get("abstract")); 277 278 _addRight(contentType, contentTypeId, pluginName); 279 } 280 281 private void _addCategory(ContentTypeDefinition contentTypeDefinition, Object recoveredCategory, Object recoveredNewCategory, String pluginName) 282 { 283 if (recoveredCategory != null) 284 { 285 if (recoveredCategory instanceof Map) 286 { 287 Map categoryMap = (Map) recoveredCategory; 288 if (!categoryMap.isEmpty()) 289 { 290 Object isNewObject = categoryMap.get("isNew"); 291 if (isNewObject != null && isNewObject instanceof Boolean) 292 { 293 I18nizableText category = null; 294 boolean isNew = (Boolean) isNewObject; 295 if (isNew) 296 { 297 category = _getNewCategory(recoveredNewCategory, pluginName); 298 } 299 else 300 { 301 category = _getExistingCategory(categoryMap); 302 } 303 contentTypeDefinition.setCategory(category); 304 } 305 } 306 } 307 } 308 } 309 310 private I18nizableText _getNewCategory(Object recoveredNewCategory, String pluginName) 311 { 312 I18nizableText category = null; 313 314 if (recoveredNewCategory != null && recoveredNewCategory instanceof Map) 315 { 316 @SuppressWarnings("unchecked") 317 Map<String, Object> newCategory = (Map<String, Object>) recoveredNewCategory; 318 319 if (!newCategory.isEmpty()) 320 { 321 Object i18n = newCategory.get("isMultilingual"); 322 if (i18n instanceof Boolean) 323 { 324 boolean isI18n = (boolean) i18n; 325 if (isI18n) 326 { 327 Object recoveredI18nValues = newCategory.get("values"); 328 if (recoveredI18nValues != null && recoveredI18nValues instanceof Map) 329 { 330 @SuppressWarnings("unchecked") 331 Map<String, String> i18nValues = (Map<String, String>) recoveredI18nValues; 332 333 String categoryKey = _generateCategoryI18nKey(pluginName, newCategory); 334 category = new I18nizableText("plugin.contenttypes-editor", categoryKey); 335 336 // Add key and values to the TranslatedValue object 337 TranslatedValue translatedValue = new TranslatedValue(categoryKey, i18nValues); 338 _translatedValues.add(translatedValue); 339 } 340 } 341 else 342 { 343 Object value = newCategory.get("values"); 344 if (value instanceof String) 345 { 346 category = new I18nizableText((String) value); 347 } 348 } 349 _editContentTypeInformationHelper.addNewCategory(newCategory); 350 } 351 } 352 } 353 354 return category; 355 } 356 357 private I18nizableText _getExistingCategory(Map categoryMap) 358 { 359 I18nizableText category = null; 360 Object i18n = categoryMap.get("isMultilingual"); 361 if (i18n instanceof Boolean) 362 { 363 boolean isI18n = (boolean) i18n; 364 if (isI18n) 365 { 366 Object recoveredKey = categoryMap.get("key"); 367 Object recoveredCatalogue = categoryMap.get("catalogue"); 368 if (recoveredKey instanceof String && recoveredCatalogue instanceof String) 369 { 370 category = new I18nizableText((String) recoveredCatalogue, (String) recoveredKey); 371 } 372 } 373 else 374 { 375 Object value = categoryMap.get("values"); 376 if (value instanceof String) 377 { 378 category = new I18nizableText((String) value); 379 } 380 } 381 } 382 return category; 383 } 384 385 private void _addIconGlyph(ContentTypeDefinition contentTypeDefinition, Object recoverIconGlyph) 386 { 387 if (recoverIconGlyph instanceof String) 388 { 389 String icon = (String) recoverIconGlyph; 390 if (StringUtils.isNotBlank(icon)) 391 { 392 contentTypeDefinition.setIconGlyph(icon); 393 } 394 } 395 } 396 397 private void _addTags(ContentTypeDefinition contentTypeDefinition, Object recoverTags, Object recoverIsPrivate, Object recoverIsReferenceTable, Object recoverIsMixin) 398 { 399 Set<String> tags = new HashSet<>(); 400 if (recoverTags != null && recoverTags instanceof String) 401 { 402 String[] tagArray = ((String) recoverTags).split(","); 403 for (String tag : tagArray) 404 { 405 if (StringUtils.isNotBlank(tag)) 406 { 407 tags.add(tag); 408 } 409 } 410 } 411 if (recoverIsPrivate != null && recoverIsPrivate instanceof Boolean && (boolean) recoverIsPrivate) 412 { 413 tags.add("private"); 414 } 415 if (recoverIsReferenceTable != null && recoverIsReferenceTable instanceof Boolean && (boolean) recoverIsReferenceTable) 416 { 417 tags.add("reference-table"); 418 } 419 if (recoverIsMixin != null && recoverIsMixin instanceof Boolean && (boolean) recoverIsMixin) 420 { 421 tags.add("mixin"); 422 } 423 424 if (!tags.isEmpty()) 425 { 426 contentTypeDefinition.setTags(tags); 427 } 428 } 429 430 private void _addSupertypeIds(ContentTypeDefinition contentTypeDefinition, Object recoverSupertypes) 431 { 432 if (recoverSupertypes != null && recoverSupertypes instanceof List) 433 { 434 @SuppressWarnings("unchecked") 435 List<LinkedHashMap<String, String>> supertypes = (List<LinkedHashMap<String, String>>) recoverSupertypes; 436 List<String> supertypeIds = new ArrayList<>(); 437 supertypeIds = supertypes.stream().map(s -> s.get("id")).collect(Collectors.toList()); 438 contentTypeDefinition.setSupertypeIds(supertypeIds.toArray(new String[supertypeIds.size()])); 439 } 440 } 441 442 private void _addIsAbstract(ContentTypeDefinition contentTypeDefinition, Object recoverIsAbstract) 443 { 444 boolean isAbstract = recoverIsAbstract != null && recoverIsAbstract instanceof Boolean && (boolean) recoverIsAbstract; 445 contentTypeDefinition.setIsAbstract(isAbstract); 446 } 447 448 private void _addRight(ContentTypeDefinition contentTypeDefinition, String contentTypeId, String pluginName) 449 { 450 String right = pluginName + "_Right_" + _generateContentTypeIdKey(contentTypeId) + "_Create"; 451 contentTypeDefinition.setRight(right); 452 } 453 454 private void _addModelItems(ContentTypeDefinition contentType, ContentType existingContentType, Map<String, Object> contentTypeInfos, String pluginName, String realContentTypeId) 455 { 456 Boolean isTitleAttributePresent = false; 457 Object recoverMetadataList = contentTypeInfos.get("attributes"); 458 List<ModelItem> modelItems = new ArrayList<>(); 459 460 if (recoverMetadataList != null && recoverMetadataList instanceof List) 461 { 462 List recoverModelItems = (List) recoverMetadataList; 463 464 for (Object recoverModelItem : recoverModelItems) 465 { 466 if (recoverModelItem instanceof Map) 467 { 468 @SuppressWarnings("unchecked") 469 Map<String, Object> modelItemInfo = (Map<String, Object>) recoverModelItem; 470 isTitleAttributePresent = modelItemInfo.get("name").equals(Content.ATTRIBUTE_TITLE) || isTitleAttributePresent; 471 Object recoveredReferenceContentTypeId = modelItemInfo.get("referenceContentTypeId"); 472 if (recoveredReferenceContentTypeId != null 473 && recoveredReferenceContentTypeId instanceof String 474 && ((String) recoveredReferenceContentTypeId).equals(realContentTypeId)) 475 { 476 Object recoverName = modelItemInfo.get("name"); 477 ModelItem oldModelItem = null; 478 String contentTypeId = realContentTypeId.replaceFirst("content-type.", ""); 479 if (existingContentType != null && recoverName instanceof String && existingContentType.hasModelItem((String) recoverName)) 480 { 481 oldModelItem = existingContentType.getModelItem((String) recoverName); 482 ModelItem newModelItem = _getModelItem(modelItemInfo, oldModelItem, pluginName, contentTypeId); 483 List<String> overriddenAttributes = existingContentType.getOverriddenAttributes(); 484 485 // Check if there is a modification of the attribute to avoid useless overriding 486 if (!_areModelItemsEqual(oldModelItem, newModelItem, overriddenAttributes)) 487 { 488 modelItems.add(newModelItem); 489 } 490 } 491 else if (_contentTypeStateComponent.getContentTypeMarkedAsNew().contains(realContentTypeId)) 492 { 493 modelItems.add(_getModelItem(modelItemInfo, oldModelItem, pluginName, contentTypeId)); 494 } 495 } 496 } 497 } 498 } 499 if (!isTitleAttributePresent) 500 { 501 modelItems.add(_createDefaultTitleModelItem()); 502 } 503 contentType.setModelItems(modelItems); 504 } 505 506 private boolean _areModelItemsEqual(ModelItem oldModelItem, ModelItem newModelItem, List<String> overriddenAttributes) 507 { 508 // If the created MotelItem is a new one. It's not necessary to check the newModelItem nullity because we cannot edit a modelItem removed 509 if (oldModelItem == null) 510 { 511 return false; 512 } 513 // If the attribute is already overridden we have to override it again 514 if (overriddenAttributes.contains(oldModelItem.getName())) 515 { 516 return false; 517 } 518 519 // If the label or the description of the attributes changed we have to override it 520 I18nizableTextKeyComparator i18nKeyComparator = new I18nizableTextKeyComparator(); 521 if (i18nKeyComparator.compare(oldModelItem.getLabel(), newModelItem.getLabel()) != 0 522 || i18nKeyComparator.compare(oldModelItem.getDescription(), newModelItem.getDescription()) != 0) 523 { 524 return false; 525 } 526 527 // If the edited attribute is a group of modelItems 528 if (oldModelItem instanceof ModelItemGroup) 529 { 530 // Check if there is a modification in the group of ModelItems 531 return _areModelItemGroupsEqual((ModelItemGroup) oldModelItem, (ModelItemGroup) newModelItem, overriddenAttributes); 532 } 533 else 534 { 535 // Check if there is a modification in the Element 536 return _areElementDefinitionsEqual((ElementDefinition) oldModelItem, (ElementDefinition) newModelItem); 537 } 538 } 539 540 private boolean _areElementDefinitionsEqual(ElementDefinition oldElem, ElementDefinition newElem) 541 { 542 // If the widget, the validator or the enumerator changed then this element has changed 543 return StringUtils.equals(oldElem.getWidget(), newElem.getWidget()) 544 && _areValidatorsEqual(oldElem.getValidator(), newElem.getValidator()) 545 && _areEnumeratorsEqual(oldElem.getEnumerator(), newElem.getEnumerator()) 546 && oldElem.getWidgetParameters().equals(newElem.getWidgetParameters()); 547 } 548 549 private boolean _areModelItemGroupsEqual(ModelItemGroup oldGroup, ModelItemGroup newGroup, List<String> overriddenAttributes) 550 { 551 List<ModelItem> modelItemsTreated = new ArrayList<>(); 552 // Compare each attribute in the group 553 for (ModelItem modelItem : oldGroup.getModelItems()) 554 { 555 // Check if the modelItem is present in both groups 556 if (newGroup.getModelItem(modelItem.getName()) != null) 557 { 558 modelItemsTreated.add(modelItem); 559 // Recursive call of _areModelItemsEquals function to check each attribute 560 if (!_areModelItemsEqual(modelItem, newGroup.getModelItem(modelItem.getName()), overriddenAttributes)) 561 { 562 return false; 563 } 564 } 565 else 566 { 567 return false; 568 } 569 } 570 // Check if all modelItems of the new groups have been treated 571 for (ModelItem modelItem : newGroup.getModelItems()) 572 { 573 if (!modelItemsTreated.contains(modelItem)) 574 { 575 return false; 576 } 577 } 578 return true; 579 } 580 private boolean _areEnumeratorsEqual(Enumerator oldEnumerator, Enumerator newEnumerator) 581 { 582 // If the old, the new or if both enumerators are null we can know if they are different 583 if (oldEnumerator == null || newEnumerator == null) 584 { 585 return oldEnumerator == newEnumerator; 586 } 587 // If one enumerator is static and the other custom, we can override them 588 if (oldEnumerator instanceof StaticEnumerator && !(newEnumerator instanceof StaticEnumerator) 589 || oldEnumerator instanceof StaticEnumerator && !(oldEnumerator instanceof StaticEnumerator)) 590 { 591 return false; 592 } 593 // If both attribute's enumerator are static we can compare them 594 if (oldEnumerator instanceof StaticEnumerator && newEnumerator instanceof StaticEnumerator) 595 { 596 try 597 { 598 // Compare the values and label of both static enumerators 599 return oldEnumerator.getTypedEntries().equals(newEnumerator.getTypedEntries()); 600 } 601 catch (Exception e) 602 { 603 e.printStackTrace(); 604 } 605 } 606 // First compare their class names 607 if (oldEnumerator.getClass().getName().equals(newEnumerator.getClass().getName())) 608 { 609 // Compare the configuration in case of they have the same classname 610 return oldEnumerator.getConfiguration().equals(newEnumerator.getConfiguration()); 611 } 612 else 613 { 614 return false; 615 } 616 } 617 618 private boolean _areValidatorsEqual(Validator oldValidator, Validator newValidator) 619 { 620 // If the old, the new or if both validators are null we can know if they are different 621 if (oldValidator == null || newValidator == null) 622 { 623 return oldValidator == newValidator; 624 } 625 // Check if the validator's configuration has changed 626 return oldValidator.equals(newValidator); 627 } 628 629 /** 630 * Compare recovered values stored in a map with existing values stored in a 18nizableText 631 * @param newText values recovered after a user modification of the content 632 * @param existingI18nizableText existing values we need to compare to know if the content has changed 633 * @return the value 0 if there is no difference between old and new values, a value less than 0 if the key is already overridden 634 * or a value greater than 0 if the new value has changed from the old value. 635 */ 636 private int _compareValuesAnd18nizableText(Map<String, Object> newText, I18nizableText existingI18nizableText) 637 { 638 @SuppressWarnings("unchecked") 639 Map<String, Object> newValues = (Map<String, Object>) newText.get("values"); 640 641 // If the key is already overriden 642 if (existingI18nizableText.toString().matches("application(.*)")) 643 { 644 return -1; 645 } 646 for (Map.Entry<String, Object> entry : newValues.entrySet()) 647 { 648 // If the old and the new value are not equals 649 if (!this._i18nUtils.translate(existingI18nizableText, entry.getKey()).equals(entry.getValue())) 650 { 651 return 1; 652 } 653 } 654 return 0; 655 } 656 657 private ElementDefinition _createDefaultTitleModelItem() 658 { 659 ElementDefinition title = _contentTypesHelper.getTitleAttributeDefinition(); 660 title.setValidator(new DefaultValidator(null, true)); 661 return title; 662 } 663 664 @SuppressWarnings("static-access") 665 private ModelItem _getModelItem(Map<String, Object> modelItemInfo, ModelItem existingModelItem, String pluginName, String contentTypeId) 666 { 667 ModelItem modelItem = null; 668 669 Object recoverType = modelItemInfo.get("type"); 670 if (recoverType != null && recoverType instanceof String) 671 { 672 String typeId = (String) recoverType; 673 String name = _getString(modelItemInfo.get("name")); 674 675 if (ModelItemTypeConstants.REPEATER_TYPE_ID.equals(typeId)) 676 { 677 modelItem = _getRepeater(modelItemInfo, (RepeaterDefinition) existingModelItem, name, pluginName, contentTypeId); 678 _addChildren((ModelItemGroup) modelItem, existingModelItem, modelItemInfo, pluginName, contentTypeId); 679 } 680 else if (ModelItemTypeConstants.COMPOSITE_TYPE_ID.equals(typeId)) 681 { 682 modelItem = new ContentRestrictedCompositeDefinition(); 683 _addChildren((ModelItemGroup) modelItem, existingModelItem, modelItemInfo, pluginName, contentTypeId); 684 } 685 else 686 { 687 modelItem = _getAttributeDefinition(modelItemInfo, existingModelItem, pluginName, contentTypeId, name, typeId); 688 } 689 690 _addModelItemGeneralInformation(modelItem, existingModelItem, modelItemInfo, name, pluginName, contentTypeId, typeId); 691 692 } 693 return modelItem; 694 } 695 696 private ModelItem _getRepeater(Map<String, Object> repeaterInfo, RepeaterDefinition existingRepeaterDefinition, String name, String pluginName, String contentTypeId) 697 { 698 RepeaterDefinition repeater = new ContentRestrictedRepeaterDefinition(); 699 repeater.setInitialSize(_getInteger(repeaterInfo.get("initializeSize"))); 700 repeater.setMinSize(_getInteger(repeaterInfo.get("minSize"))); 701 repeater.setMaxSize(_getInteger(repeaterInfo.get("maxSize"))); 702 703 I18nizableText addLabel = _getI18nizableText(existingRepeaterDefinition != null ? existingRepeaterDefinition.getAddLabel() : null, repeaterInfo.get("addLabel"), pluginName, 704 "contenttype", contentTypeId, "metadata_" + name + "_add_label"); 705 repeater.setAddLabel(addLabel); 706 707 I18nizableText deleteLabel = _getI18nizableText(existingRepeaterDefinition != null ? existingRepeaterDefinition.getDeleteLabel() : null, repeaterInfo.get("deleteLabel"), 708 pluginName, "contenttype", contentTypeId, "metadata_" + name + "_delete_label"); 709 repeater.setDeleteLabel(deleteLabel); 710 711 return repeater; 712 } 713 714 private ModelItem _getAttributeDefinition(Map<String, Object> attributeInfo, ModelItem existingModelItem, String pluginName, String contentTypeId, String name, String typeId) 715 { 716 AttributeDefinition<? extends Object> attributeDefinition = _getAttributeDefinition(typeId); 717 718 boolean isMultiple = _getBoolean(attributeInfo.get("multiple")); 719 attributeDefinition.setMultiple(isMultiple); 720 721 _addWidget(attributeDefinition, attributeInfo, name, pluginName, contentTypeId); 722 _addEnumerator(attributeDefinition, existingModelItem, attributeInfo, name, pluginName, contentTypeId); 723 _addValidator(attributeDefinition, existingModelItem, attributeInfo, name, pluginName, contentTypeId); 724 725 if (ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(typeId)) 726 { 727 _addSpecificInfoForContentAttribute((ContentAttributeDefinition) attributeDefinition, attributeInfo); 728 } 729 730 return attributeDefinition; 731 } 732 733 private AttributeDefinition<? extends Object> _getAttributeDefinition(String typeId) 734 { 735 if (ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(typeId)) 736 { 737 return new ContentAttributeDefinition(_contentTypeExtensionPoint, _contentTypesHelper); 738 } 739 else if (ModelItemTypeConstants.RICH_TEXT_ELEMENT_TYPE_ID.equals(typeId)) 740 { 741 return new RichTextAttributeDefinition(); 742 } 743 else 744 { 745 return new AttributeDefinition<>(); 746 } 747 } 748 749 private void _addWidget(AttributeDefinition<? extends Object> attributeDefinition, Map<String, Object> attributeInfo, String name, String pluginName, String contentTypeId) 750 { 751 String widget = _getString(attributeInfo.get("widget")); 752 attributeDefinition.setWidget(widget); 753 754 Map<String, I18nizableText> widgetParams = null; 755 Object recoverWidgetParams = attributeInfo.get("widgetParams"); 756 if (recoverWidgetParams != null && recoverWidgetParams instanceof List) 757 { 758 widgetParams = new HashMap<>(); 759 List params = (List) recoverWidgetParams; 760 for (Object recoverWidgetParam : params) 761 { 762 if (recoverWidgetParam != null && recoverWidgetParam instanceof Map) 763 { 764 Map widgetParam = (Map) recoverWidgetParam; 765 String recoverLabel = _getString(widgetParam.get("label")); 766 I18nizableText values = _getI18nizableText(null, widgetParam.get("value"), pluginName, "contentttype", contentTypeId, 767 "metadata_" + name + "_" + recoverLabel + "_widget_param"); 768 widgetParams.put(recoverLabel, values); 769 } 770 } 771 } 772 attributeDefinition.setWidgetParameters(widgetParams); 773 } 774 775 private void _addEnumerator(AttributeDefinition<? extends Object> attributeDefinition, ModelItem existingModelItem, Map<String, Object> attributeInfo, String name, String pluginName, String contentTypeId) 776 { 777 String enumeratorName = _getString(attributeInfo.get("customEnumeratorClass")); 778 if (StringUtils.isNotBlank(enumeratorName)) 779 { 780 _addCustomEnumerator(attributeDefinition, attributeInfo, enumeratorName); 781 } 782 else if (attributeInfo.get("defaultEnumerator") != null) 783 { 784 _addDefaultEnumerator(attributeDefinition, existingModelItem, attributeInfo, name, pluginName, contentTypeId); 785 } 786 } 787 788 private void _addCustomEnumerator(AttributeDefinition<? extends Object> attributeDefinition, Map<String, Object> attributeInfo, String enumeratorName) 789 { 790 attributeDefinition.setCustomEnumerator(enumeratorName); 791 792 String customEnumeratorConfiguration = _getString(attributeInfo.get("customEnumeratorConfiguration")); 793 if (customEnumeratorConfiguration != null) 794 { 795 DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(); 796 try 797 { 798 Configuration configuration = builder.build(IOUtils.toInputStream(customEnumeratorConfiguration, Charset.forName("UTF-8"))); 799 attributeDefinition.setEnumeratorConfiguration(configuration); 800 } 801 catch (ConfigurationException | SAXException | IOException e) 802 { 803 getLogger().error("The XML configuration of custom enumerator is not valid", e); 804 } 805 } 806 } 807 808 @SuppressWarnings("unchecked") 809 private void _addDefaultEnumerator(AttributeDefinition attributeDefinition, ModelItem existingModelItem, Map<String, Object> attributeInfo, String name, String pluginName, String contentTypeId) 810 { 811 Object recoverEnumerator = attributeInfo.get("defaultEnumerator"); 812 if (recoverEnumerator instanceof List) 813 { 814 StaticEnumerator staticEnumerator = new StaticEnumerator<>(); 815 816 List enumerator = (List) recoverEnumerator; 817 for (Object recoverValue : enumerator) 818 { 819 if (recoverValue != null && recoverValue instanceof Map) 820 { 821 Map value = (Map) recoverValue; 822 Object recoverEnumLabel = value.get("label"); 823 if (recoverEnumLabel != null && recoverEnumLabel instanceof String) 824 { 825 Enumerator existingEnumerator = ((AttributeDefinition) existingModelItem).getEnumerator(); 826 I18nizableText existingEnumValue = null; 827 if (existingEnumerator != null) 828 { 829 try 830 { 831 existingEnumValue = existingEnumerator.getEntry(recoverEnumLabel); 832 } 833 catch (Exception e) 834 { 835 e.printStackTrace(); 836 } 837 } 838 String enumLabel = (String) recoverEnumLabel; 839 Object recoveredEnumValue = value.get("value"); 840 I18nizableText enumValue = _getI18nizableText(existingEnumValue, recoveredEnumValue, pluginName, "contenttype", contentTypeId, 841 "metadata_" + name + "_" + enumLabel + "_enumeration"); 842 staticEnumerator.add(enumValue, enumLabel); 843 } 844 } 845 } 846 attributeDefinition.setEnumerator(staticEnumerator); 847 } 848 } 849 850 private void _addValidator(AttributeDefinition<? extends Object> attributeDefinition, ModelItem existingModelItem, Map<String, Object> attributeInfo, String name, String pluginName, String contentTypeId) 851 { 852 String validatorName = _getString(attributeInfo.get("customValidatorClass")); 853 if (StringUtils.isNotBlank(validatorName)) 854 { 855 _addCustomValidator(attributeDefinition, attributeInfo, validatorName); 856 } 857 else if (attributeInfo.get("defaultValidator") != null) 858 { 859 _addDefaultValidator(attributeDefinition, existingModelItem, attributeInfo, name, pluginName, contentTypeId); 860 } 861 } 862 863 private void _addCustomValidator(AttributeDefinition<? extends Object> attributeDefinition, Map<String, Object> attributeInfo, String validatorName) 864 { 865 attributeDefinition.setCustomValidator(validatorName); 866 867 String customValidatorConfiguration = _getString(attributeInfo.get("customValidatorConfiguration")); 868 if (customValidatorConfiguration != null) 869 { 870 DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(); 871 try 872 { 873 Configuration configuration = builder.build(IOUtils.toInputStream(customValidatorConfiguration, Charset.forName("UTF-8"))); 874 attributeDefinition.setValidatorConfiguration(configuration); 875 } 876 catch (ConfigurationException | SAXException | IOException e) 877 { 878 getLogger().error("The XML configuration of custom validator is not valid", e); 879 } 880 } 881 } 882 883 private void _addDefaultValidator(AttributeDefinition<? extends Object> attributeDefinition, ModelItem existingModelItem, Map<String, Object> attributeInfo, String name, String pluginName, String contentTypeId) 884 { 885 Object recoverValidator = attributeInfo.get("defaultValidator"); 886 if (recoverValidator instanceof Map) 887 { 888 Map validator = (Map) recoverValidator; 889 boolean isMandatory = _getBoolean(validator.get("mandatory")); 890 String regexp = _getString(validator.get("regexp")); 891 892 I18nizableText invalidText = null; 893 I18nizableText existingInvalidText = null; 894 Object recoverInvalidText = validator.get("invalidText"); 895 if (recoverInvalidText != null && recoverInvalidText instanceof Map) 896 { 897 if (existingModelItem != null && existingModelItem instanceof AttributeDefinition) 898 { 899 Validator existingValidator = ((AttributeDefinition) existingModelItem).getValidator(); 900 if (existingValidator != null) 901 { 902 Map<String, Object> existingConfiguration = existingValidator.getConfiguration(); 903 if (existingConfiguration.get("invalidText") != null) 904 { 905 existingInvalidText = (I18nizableText) existingConfiguration.get("invalidText"); 906 } 907 } 908 } 909 invalidText = _getI18nizableText(existingInvalidText, recoverInvalidText, pluginName, "contenttype", contentTypeId, "metadata_" + name + "validator_invalid_text"); 910 } 911 DefaultValidator defaultValidator = new DefaultValidator(regexp, invalidText, isMandatory); 912 attributeDefinition.setValidator(defaultValidator); 913 } 914 } 915 916 private void _addSpecificInfoForContentAttribute(ContentAttributeDefinition attributeDefinition, Map<String, Object> attributeInfo) 917 { 918 Object recoverLinkedContentType = attributeInfo.get("linkedContentType"); 919 if (recoverLinkedContentType != null && recoverLinkedContentType instanceof Map) 920 { 921 Map linkedContentType = (Map) recoverLinkedContentType; 922 Object recoverId = linkedContentType.get("id"); 923 if (recoverId != null && recoverId instanceof String) 924 { 925 attributeDefinition.setContentTypeId((String) recoverId); 926 927 String invert = _getString(attributeInfo.get("invertRelationPath")); 928 attributeDefinition.setInvertRelationPath(invert); 929 } 930 } 931 } 932 933 private void _addModelItemGeneralInformation(ModelItem modelItem, ModelItem existingModelItem, Map<String, Object> modelItemInfo, String name, String pluginName, String contentTypeId, String typeId) 934 { 935 if (modelItem != null) 936 { 937 modelItem.setName(name); 938 939 I18nizableText label = _getI18nizableText(existingModelItem != null ? existingModelItem.getLabel() : null, modelItemInfo.get("label"), pluginName, 940 "contenttype", contentTypeId, "metadata_" + name + "_label"); 941 modelItem.setLabel(label); 942 943 I18nizableText description = _getI18nizableText(existingModelItem != null ? existingModelItem.getDescription() : null, modelItemInfo.get("description"), 944 pluginName, "contenttype", contentTypeId, "metadata_" + name + "_description"); 945 modelItem.setDescription(description); 946 modelItem.setType(_contentAttributeTypeExtensionPoint.getExtension(typeId)); 947 948 if (existingModelItem != null) 949 { 950 modelItem.setModel(existingModelItem.getModel()); 951 } 952 953 if (modelItem instanceof ElementDefinition) 954 { 955 ((ElementDefinition) modelItem).setPluginName(pluginName); 956 } 957 } 958 } 959 960 private void _addChildren(ModelItemGroup modelItemGroup, ModelItem existingModelItem, Map<String, Object> modelItemInfo, String pluginName, String contentTypeId) 961 { 962 Object recoverChildren = modelItemInfo.get("children"); 963 if (recoverChildren != null && recoverChildren instanceof List) 964 { 965 for (Object recoverChild : (List) recoverChildren) 966 { 967 if (recoverChild != null && recoverChild instanceof Map) 968 { 969 @SuppressWarnings("unchecked") 970 Map<String, Object> child = (Map<String, Object>) recoverChild; 971 972 Object recoverName = child.get("name"); 973 ModelItem existingChildModelItem = null; 974 975 if (existingModelItem != null && existingModelItem instanceof ModelItemContainer 976 && recoverName != null && recoverName instanceof String 977 && ((ModelItemContainer) existingModelItem).hasModelItem((String) recoverName)) 978 { 979 existingChildModelItem = ((ModelItemContainer) existingModelItem).getModelItem((String) recoverName); 980 } 981 982 ModelItem childModelItem = _getModelItem(child, existingChildModelItem, pluginName, contentTypeId); 983 modelItemGroup.addChild(childModelItem); 984 } 985 } 986 } 987 } 988 989 private void _addViews(ContentTypeDefinition contentTypeDefinition, ContentType existingContentType, String realContentTypeId, Map<String, Object> contentTypeInfos, String pluginName, 990 String contentTypeId) 991 { 992 Object recoverViews = contentTypeInfos.get("views"); 993 if (recoverViews != null && recoverViews instanceof List) 994 { 995 List<View> views = new ArrayList<>(); 996 997 _fieldsetNumber = 1; 998 for (Object recoverView : (List) recoverViews) 999 { 1000 if (recoverView != null && recoverView instanceof Map) 1001 { 1002 @SuppressWarnings("unchecked") 1003 Map<String, Object> recoverViewInfo = (Map<String, Object>) recoverView; 1004 Object recoverViewName = recoverViewInfo.get("name"); 1005 View newView = _getView(contentTypeDefinition, recoverViewInfo, (String) recoverViewName, pluginName, contentTypeId); 1006 if (_contentTypeStateComponent.getContentTypeMarkedAsNew().contains(realContentTypeId)) 1007 { 1008 views.add(newView); 1009 } 1010 else if (existingContentType != null && recoverViewName != null && recoverViewName instanceof String) 1011 { 1012 View oldView = existingContentType.getView((String) recoverViewName); 1013 List<String> overriddenViews = existingContentType.getOverriddenViews(); 1014 if (!oldView.equals(newView, true) || overriddenViews.contains(oldView.getName())) 1015 { 1016 views.add(newView); 1017 } 1018 } 1019 } 1020 } 1021 contentTypeDefinition.setViews(views); 1022 } 1023 } 1024 1025 private View _getView(ContentTypeDefinition contentTypeDefinition, Map<String, Object> viewInfo, String viewName, String pluginName, String contentTypeId) 1026 { 1027 String dataType = _getString(viewInfo.get("dataType")); 1028 if (StringUtils.isNotBlank(dataType) && dataType.equals("metadata_set")) 1029 { 1030 View view = new View(); 1031 view.setName(viewName); 1032 1033 ContentType existingContentType = _contentTypeExtensionPoint.getExtension(contentTypeDefinition.getId()); 1034 View existingView = null; 1035 if (existingContentType != null) 1036 { 1037 existingView = existingContentType.getView(viewName); 1038 } 1039 1040 Object recoverLabel = viewInfo.get("label"); 1041 I18nizableText label = _getI18nizableText(existingView != null ? existingView.getLabel() : null, recoverLabel, pluginName, "contenttype", contentTypeId, 1042 "metadatasets_" + viewName + "_view_label"); 1043 view.setLabel(label); 1044 1045 Object recoverDescription = viewInfo.get("description"); 1046 I18nizableText description = _getI18nizableText(existingView != null ? existingView.getDescription() : null, recoverDescription, pluginName, "contenttype", contentTypeId, 1047 "metadatasets_" + viewName + "_view_description"); 1048 view.setDescription(description); 1049 1050 Object recoverIconGlyph = viewInfo.get("iconGlyph"); 1051 view.setIconGlyph((String) recoverIconGlyph); 1052 1053 Object recoverIconDecarator = viewInfo.get("iconDecorator"); 1054 view.setIconDecorator((String) recoverIconDecarator); 1055 1056 if (viewInfo.containsKey("isInternal")) 1057 { 1058 Object recoverIsInternal = viewInfo.get("isInternal"); 1059 view.setInternal((boolean) recoverIsInternal); 1060 } 1061 1062 _addChildrenToViewItemContainer(view, contentTypeDefinition, viewInfo, pluginName, contentTypeId); 1063 1064 return view; 1065 } 1066 else 1067 { 1068 return null; 1069 } 1070 } 1071 1072 private void _addChildrenToViewItemContainer(ViewItemContainer viewItemContainer, ContentTypeDefinition contentTypeDefinition, Map<String, Object> viewItemContainerInfo, String pluginName, String contentTypeId) 1073 { 1074 Object recoverChildren = viewItemContainerInfo.get("children"); 1075 if (recoverChildren != null && recoverChildren instanceof List) 1076 { 1077 for (Object recoverChild : (List) recoverChildren) 1078 { 1079 if (recoverChild != null && recoverChild instanceof Map) 1080 { 1081 @SuppressWarnings("unchecked") 1082 Map<String, Object> childInfo = (Map<String, Object>) recoverChild; 1083 Object recoverChildViewItemName = childInfo.get("name"); 1084 Object recoverChildViewItemPath = childInfo.get("path"); 1085 if (recoverChildViewItemName != null && recoverChildViewItemName instanceof String) 1086 { 1087 ViewItem viewItem = _getViewItem(contentTypeDefinition, childInfo, (String) recoverChildViewItemName, (String) recoverChildViewItemPath, pluginName, contentTypeId); 1088 viewItemContainer.addViewItem(viewItem); 1089 } 1090 } 1091 } 1092 } 1093 } 1094 1095 private ViewItem _getViewItem(ContentTypeDefinition contentTypeDefinition, Map<String, Object> viewItemInfo, String viewItemName, String viewItemPath, String pluginName, String contentTypeId) 1096 { 1097 ViewItem viewItem = null; 1098 1099 String dataType = _getString(viewItemInfo.get("dataType")); 1100 if (StringUtils.isNotBlank(dataType)) 1101 { 1102 if (dataType.equals("fieldset")) 1103 { 1104 viewItem = _getSimpleViewItemGroup(contentTypeDefinition, viewItemInfo, viewItemName, pluginName, contentTypeId); 1105 } 1106 else if (dataType.equals("metadata_ref")) 1107 { 1108 ModelItem modelItem = _getModelItemFromContentTypeDefinition(contentTypeDefinition.getModelItems(), viewItemPath); 1109 if (modelItem == null) 1110 { 1111 ContentType existingContentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 1112 modelItem = _getModelItemFromContentTypeDefinition(existingContentType.getModelItems(), viewItemPath); 1113 } 1114 if (modelItem != null) 1115 { 1116 if (modelItem instanceof ElementDefinition) 1117 { 1118 viewItem = new ViewElement(); 1119 ((ViewElement) viewItem).setDefinition((ElementDefinition) modelItem); 1120 } 1121 else if (modelItem instanceof ModelItemGroup) 1122 { 1123 viewItem = new ModelViewItemGroup(); 1124 ((ModelViewItemGroup) viewItem).setDefinition((ModelItemGroup) modelItem); 1125 _addChildrenToViewItemContainer((ModelViewItemGroup) viewItem, contentTypeDefinition, viewItemInfo, pluginName, contentTypeId); 1126 } 1127 } 1128 } 1129 1130 } 1131 return viewItem; 1132 } 1133 1134 private SimpleViewItemGroup _getSimpleViewItemGroup(ContentTypeDefinition contentTypeDefinition, Map<String, Object> viewItemInfo, String viewItemName, String pluginName, String contentTypeId) 1135 { 1136 SimpleViewItemGroup fieldSet = new SimpleViewItemGroup(); 1137 1138 Object recoverLabel = viewItemInfo.get("label"); 1139 I18nizableText label = _getI18nizableText(null, recoverLabel, pluginName, "contenttype", contentTypeId, 1140 "metadatasets_" + viewItemName + "_view_fieldset_" + _fieldsetNumber + "_label"); 1141 fieldSet.setLabel(label); 1142 1143 _addChildrenToViewItemContainer(fieldSet, contentTypeDefinition, viewItemInfo, pluginName, contentTypeId); 1144 1145 return fieldSet; 1146 } 1147 1148 private ModelItem _getModelItemFromContentTypeDefinition(Collection<? extends ModelItem> modelItems, String modelItemName) 1149 { 1150 for (ModelItem modelItem : modelItems) 1151 { 1152 if (modelItemName.equals(modelItem.getPath())) 1153 { 1154 return modelItem; 1155 } 1156 else if (modelItem instanceof ModelItemContainer) 1157 { 1158 ModelItem child = _getModelItemFromContentTypeDefinition(((ModelItemContainer) modelItem).getModelItems(), modelItemName); 1159 if (child != null) 1160 { 1161 return child; 1162 } 1163 } 1164 } 1165 1166 return null; 1167 } 1168 1169 private boolean _getBoolean(Object recoverValue) 1170 { 1171 return recoverValue instanceof Boolean && (boolean) recoverValue; 1172 } 1173 1174 private String _getString(Object recoverValue) 1175 { 1176 String value = null; 1177 if (recoverValue != null && recoverValue instanceof String) 1178 { 1179 value = (String) recoverValue; 1180 } 1181 return value; 1182 } 1183 1184 private int _getInteger(Object recoverValue) 1185 { 1186 int value = 0; 1187 if (recoverValue != null && recoverValue instanceof Integer) 1188 { 1189 value = (int) recoverValue; 1190 } 1191 return value; 1192 } 1193 1194 private I18nizableText _getI18nizableText(I18nizableText i18nizableText, Object recoverValue, String pluginName, String type, String contentTypeId, String label) 1195 { 1196 I18nizableText text = new I18nizableText(""); 1197 if (recoverValue != null && recoverValue instanceof LinkedHashMap) 1198 { 1199 String key = _generateI18nKey(pluginName, type, contentTypeId, label); 1200 text = _getI18nizableText(i18nizableText, recoverValue, key); 1201 } 1202 return text; 1203 } 1204 1205 private I18nizableText _getI18nizableText(I18nizableText existingI18nizableText, Object recoverValue, String key) 1206 { 1207 I18nizableText text = new I18nizableText(""); 1208 1209 if (recoverValue != null && recoverValue instanceof Map) 1210 { 1211 Map value = (Map) recoverValue; 1212 Object isMultilingual = value.get("isMultilingual"); 1213 if (isMultilingual != null && isMultilingual instanceof Boolean) 1214 { 1215 boolean isI18n = (Boolean) isMultilingual; 1216 if (isI18n) 1217 { 1218 Object recoveredI18nValues = value.get("values"); 1219 if (recoveredI18nValues != null && recoveredI18nValues instanceof Map) 1220 { 1221 @SuppressWarnings("unchecked") 1222 Map<String, String> i18nValues = (Map<String, String>) recoveredI18nValues; 1223 @SuppressWarnings("unchecked") 1224 Map<String, Object> newLabel = (Map<String, Object>) recoverValue; 1225 if (!i18nValues.isEmpty() && existingI18nizableText != null) 1226 { 1227 // There is an existing key for the data 1228 if (existingI18nizableText.isI18n() && _compareValuesAnd18nizableText(newLabel, existingI18nizableText) == 0) 1229 { 1230 text = existingI18nizableText; 1231 } 1232 else 1233 { 1234 text = new I18nizableText(__DEFAULT_CATALOGUE, key); 1235 // Add key and values to the TranslatedValue object 1236 TranslatedValue translatedValue = new TranslatedValue(key, i18nValues); 1237 _translatedValues.add(translatedValue); 1238 } 1239 } 1240 else if (i18nValues.isEmpty()) 1241 { 1242 text = existingI18nizableText; 1243 } 1244 } 1245 } 1246 else 1247 { 1248 Object recoverLabel = value.get("values"); 1249 if (recoverLabel instanceof String) 1250 { 1251 String label = (String) recoverLabel; 1252 if (StringUtils.isNotBlank(label)) 1253 { 1254 text = new I18nizableText(label); 1255 } 1256 } 1257 } 1258 } 1259 } 1260 return text; 1261 } 1262 1263 private String _generateContentTypeIdKey(String contentTypeId) 1264 { 1265 String key = contentTypeId.replaceFirst("content-type.", ""); 1266 key = key.replaceAll("\\s", "_"); 1267 key = key.replaceAll("[^\\p{L}&&[^0-9]]", "_"); 1268 return key; 1269 } 1270 1271 @SuppressWarnings("unchecked") 1272 private String _generateCategoryI18nKey(String pluginName, Map<String, Object> recoveredCategory) 1273 { 1274 String key = ""; 1275 Map<String, String> values = (Map<String, String>) recoveredCategory.get("values"); 1276 if (values != null && !values.isEmpty()) 1277 { 1278 String categoryLabel = ""; 1279 if (values.containsKey("en")) 1280 { 1281 categoryLabel = values.get("en"); 1282 1283 } 1284 else 1285 { 1286 List<String> valuesAsList = new ArrayList(values.values()); 1287 categoryLabel = valuesAsList.get(0); 1288 } 1289 key = "plugins_" + pluginName + "_" + "content_createcontentmenu_group" + "_" + _generateContentTypeIdKey(categoryLabel); 1290 key = key.toUpperCase(); 1291 } 1292 return key; 1293 } 1294 1295 private String _generateI18nKey(String pluginName, String type, String contentTypeId, String label) 1296 { 1297 String id = _generateContentTypeIdKey(contentTypeId); 1298 String key = "plugins_" + pluginName + "_" + type + "_" + id + "_" + label; 1299 key = key.toUpperCase(); 1300 return key; 1301 } 1302 1303 private Map<String, Map<String, String>> _createNewI18nCatalogs() 1304 { 1305 Map<String, Map<String, String>> i18nMessageTranslations = new HashMap<>(); 1306 1307 for (TranslatedValue translatedValue : _translatedValues) 1308 { 1309 if (translatedValue.getTranslations() != null) 1310 { 1311 for (Entry<String, String> translation : translatedValue.getTranslations().entrySet()) 1312 { 1313 String i18nKey = translatedValue.getKey(); 1314 if (StringUtils.isNotBlank(i18nKey) && StringUtils.isNotBlank(translation.getValue())) 1315 { 1316 String language = translation.getKey(); 1317 if (i18nMessageTranslations.containsKey(language)) 1318 { 1319 Map<String, String> map = i18nMessageTranslations.get(language); 1320 map.put(i18nKey, translation.getValue()); 1321 } 1322 else 1323 { 1324 Map<String, String> map = new HashMap<>(); 1325 map.put(i18nKey, translation.getValue()); 1326 i18nMessageTranslations.put(language, map); 1327 } 1328 } 1329 } 1330 } 1331 } 1332 return i18nMessageTranslations; 1333 } 1334 1335 private void _saxCatalogs(Map<String, Map<String, String>> newI18nMessages) throws Exception 1336 { 1337 // Determine the default catalog language 1338 String defaultLanguage = ""; 1339 String defaultI18nCatalogPath = __I18N_CATALOG_DIR + "application.xml"; 1340 Source defaultI18nCatalogSource = null; 1341 try 1342 { 1343 defaultI18nCatalogSource = _sourceResolver.resolveURI(defaultI18nCatalogPath); 1344 if (defaultI18nCatalogSource.exists()) 1345 { 1346 // Parse default catalog application to know the default 1347 // language 1348 Configuration configuration = new DefaultConfigurationBuilder(true).build(defaultI18nCatalogSource.getInputStream(), ""); 1349 defaultLanguage = configuration.getAttribute("xml:lang"); 1350 } 1351 } 1352 catch (SourceNotFoundException e) 1353 { 1354 // Silently ignore 1355 } 1356 finally 1357 { 1358 _sourceResolver.release(defaultI18nCatalogSource); 1359 } 1360 1361 for (Entry<String, Map<String, String>> i18nMessageTranslation : newI18nMessages.entrySet()) 1362 { 1363 Map<String, String> i18nMessages = new HashMap<>(); 1364 String language = i18nMessageTranslation.getKey(); 1365 String catalogPath; 1366 if (language.equals(defaultLanguage)) 1367 { 1368 catalogPath = __I18N_CATALOG_DIR + "application.xml"; 1369 i18nMessages = _readI18nCatalog(catalogPath); 1370 } 1371 else 1372 { 1373 catalogPath = __I18N_CATALOG_DIR + "application_" + language + ".xml"; 1374 i18nMessages = _readI18nCatalog(catalogPath); 1375 } 1376 Map<String, String> updatedI18nMessages = _updateI18nMessages(i18nMessages, i18nMessageTranslation.getValue()); 1377 _saveI18nCalatog(updatedI18nMessages, catalogPath, language); 1378 } 1379 } 1380 1381 private Map<String, String> _readI18nCatalog(String path) throws Exception 1382 { 1383 Map<String, String> i18nMessages = new LinkedHashMap<>(); 1384 Source i18nCatalogSource = null; 1385 1386 try 1387 { 1388 i18nCatalogSource = _sourceResolver.resolveURI(path); 1389 if (i18nCatalogSource.exists()) 1390 { 1391 if (_i18nCatalogs.containsKey(path)) 1392 { 1393 I18nCatalog i18nCatalog = _i18nCatalogs.get(path); 1394 if (i18nCatalog.getLastModified() < i18nCatalogSource.getLastModified()) 1395 { 1396 SAXParserFactory.newInstance().newSAXParser().parse(i18nCatalogSource.getInputStream(), new I18nMessageHandler(i18nMessages)); 1397 i18nCatalog.setI18nMessages(i18nMessages); 1398 } 1399 else 1400 { 1401 i18nMessages = i18nCatalog.getI18nMessages(); 1402 } 1403 } 1404 else 1405 { 1406 SAXParserFactory.newInstance().newSAXParser().parse(i18nCatalogSource.getInputStream(), new I18nMessageHandler(i18nMessages)); 1407 I18nCatalog i18nCatalog = new I18nCatalog(i18nMessages); 1408 _i18nCatalogs.put(path, i18nCatalog); 1409 } 1410 } 1411 } 1412 catch (SourceNotFoundException e) 1413 { 1414 // Silently ignore 1415 } 1416 finally 1417 { 1418 _sourceResolver.release(i18nCatalogSource); 1419 } 1420 1421 return i18nMessages; 1422 } 1423 1424 private Map<String, String> _updateI18nMessages(Map<String, String> i18nMessages, Map<String, String> newI18nMessages) 1425 { 1426 for (Entry<String, String> newI18nMessage : newI18nMessages.entrySet()) 1427 { 1428 String key = newI18nMessage.getKey(); 1429 String value = newI18nMessage.getValue(); 1430 if (i18nMessages.containsKey(key)) 1431 { 1432 i18nMessages.replace(key, value); 1433 } 1434 else 1435 { 1436 i18nMessages.put(key, value); 1437 } 1438 } 1439 return i18nMessages; 1440 } 1441 1442 private void _saveI18nCalatog(Map<String, String> i18nMessages, String catalogPath, String language) throws Exception 1443 { 1444 ModifiableSource defaultI18nCatalogSource = null; 1445 try 1446 { 1447 defaultI18nCatalogSource = (ModifiableSource) _sourceResolver.resolveURI(catalogPath); 1448 try (OutputStream os = defaultI18nCatalogSource.getOutputStream()) 1449 { 1450 // create a transformer for saving sax into a file 1451 TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler(); 1452 1453 StreamResult result = new StreamResult(os); 1454 th.setResult(result); 1455 1456 // create the format of result 1457 Properties format = new Properties(); 1458 format.put(OutputKeys.METHOD, "xml"); 1459 format.put(OutputKeys.INDENT, "yes"); 1460 format.put(OutputKeys.ENCODING, "UTF-8"); 1461 format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "2"); 1462 th.getTransformer().setOutputProperties(format); 1463 1464 // sax the config into the transformer 1465 _saxI18nCatalog(th, i18nMessages, language); 1466 1467 } 1468 catch (Exception e) 1469 { 1470 throw new Exception("An error occured while saving the catalog values.", e); 1471 } 1472 _i18nCatalogs.get(catalogPath).setLastModified(defaultI18nCatalogSource.getLastModified()); 1473 } 1474 catch (SourceNotFoundException e) 1475 { 1476 // Silently ignore 1477 } 1478 finally 1479 { 1480 _sourceResolver.release(defaultI18nCatalogSource); 1481 } 1482 1483 } 1484 1485 private void _saxI18nCatalog(TransformerHandler handler, Map<String, String> i18nMessages, String language) throws SAXException 1486 { 1487 handler.startDocument(); 1488 AttributesImpl attribute = new AttributesImpl(); 1489 attribute.addCDATAAttribute("xml:lang", language); 1490 XMLUtils.startElement(handler, "catalogue", attribute); 1491 for (Entry<String, String> i18Message : i18nMessages.entrySet()) 1492 { 1493 attribute = new AttributesImpl(); 1494 attribute.addCDATAAttribute("key", i18Message.getKey()); 1495 XMLUtils.createElement(handler, "message", attribute, i18Message.getValue()); 1496 } 1497 XMLUtils.endElement(handler, "catalogue"); 1498 handler.endDocument(); 1499 } 1500 1501 private void _saxRightsParam(String id, String label, String description) throws MalformedURLException, IOException 1502 { 1503 // For sax right params, a pipeline is used because it's more simple to copy a part of the existing right file to add or modify a right 1504 String uri = __SAVE_CONTENT_TYPE_RIGHT + "?id=" + id + "&label=" + label + "&description=" + description + "&category=" + __RIGHT_CATEGORY + "&file-path=" + __RIGHTS_FILE; 1505 Source source = null; 1506 ModifiableSource fileSource = null; 1507 1508 try 1509 { 1510 source = _sourceResolver.resolveURI(uri); 1511 fileSource = (ModifiableSource) _sourceResolver.resolveURI(__RIGHTS_FILE); 1512 try (OutputStream resultOs = fileSource.getOutputStream(); InputStream sourceIs = source.getInputStream()) 1513 { 1514 SourceUtil.copy(sourceIs, resultOs); 1515 } 1516 } 1517 finally 1518 { 1519 _sourceResolver.release(source); 1520 _sourceResolver.release(fileSource); 1521 } 1522 } 1523}