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 */ 016package org.ametys.cms.contenttype; 017 018import java.io.File; 019import java.io.IOException; 020import java.io.OutputStream; 021import java.net.MalformedURLException; 022import java.nio.file.Files; 023import java.nio.file.Paths; 024import java.util.List; 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.Properties; 028import java.util.Set; 029 030import javax.xml.transform.OutputKeys; 031import javax.xml.transform.TransformerConfigurationException; 032import javax.xml.transform.TransformerFactory; 033import javax.xml.transform.TransformerFactoryConfigurationError; 034import javax.xml.transform.sax.SAXTransformerFactory; 035import javax.xml.transform.sax.TransformerHandler; 036import javax.xml.transform.stream.StreamResult; 037 038import org.apache.avalon.framework.component.Component; 039import org.apache.avalon.framework.configuration.ConfigurationException; 040import org.apache.avalon.framework.configuration.DefaultConfigurationSerializer; 041import org.apache.avalon.framework.service.ServiceException; 042import org.apache.avalon.framework.service.ServiceManager; 043import org.apache.avalon.framework.service.Serviceable; 044import org.apache.cocoon.xml.AttributesImpl; 045import org.apache.cocoon.xml.XMLUtils; 046import org.apache.commons.io.FileUtils; 047import org.apache.commons.lang.StringUtils; 048import org.apache.excalibur.source.SourceResolver; 049import org.apache.excalibur.source.impl.FileSource; 050import org.apache.xml.serializer.OutputPropertiesFactory; 051import org.xml.sax.SAXException; 052 053import org.ametys.cms.repository.Content; 054import org.ametys.cms.repository.ContentDAO; 055import org.ametys.cms.repository.ContentQueryHelper; 056import org.ametys.cms.repository.ContentTypeOrMixinExpression; 057import org.ametys.plugins.repository.AmetysObjectIterable; 058import org.ametys.plugins.repository.AmetysObjectResolver; 059import org.ametys.plugins.repository.model.CompositeDefinition; 060import org.ametys.plugins.repository.model.RepeaterDefinition; 061import org.ametys.plugins.repository.query.expression.Expression; 062import org.ametys.plugins.repository.query.expression.Expression.Operator; 063import org.ametys.runtime.i18n.I18nizableText; 064import org.ametys.runtime.model.ElementDefinition; 065import org.ametys.runtime.model.Enumerator; 066import org.ametys.runtime.model.ModelItem; 067import org.ametys.runtime.model.ModelItemContainer; 068import org.ametys.runtime.model.ModelViewItem; 069import org.ametys.runtime.model.SimpleViewItemGroup; 070import org.ametys.runtime.model.StaticEnumerator; 071import org.ametys.runtime.model.View; 072import org.ametys.runtime.model.ViewItem; 073import org.ametys.runtime.model.ViewItemContainer; 074import org.ametys.runtime.parameter.DefaultValidator; 075import org.ametys.runtime.parameter.Validator; 076import org.ametys.runtime.plugin.component.AbstractLogEnabled; 077 078/** 079 * Helper for create, edit and remove a content type 080 */ 081public class EditContentTypeHelper extends AbstractLogEnabled implements Component, Serviceable 082{ 083 /** The Avalon role name */ 084 public static final String ROLE = EditContentTypeHelper.class.getName(); 085 086 /** The directory path of content types */ 087 private static final String __CONTENT_TYPES_DIR = "context://WEB-INF/param/content-types/"; 088 /** The prefix for id of automatic content types */ 089 private static final String __CONTENT_TYPE_PREFIX = "content-type."; 090 091 /** The namespace uri */ 092 private static final String __NAMESPACE_URI = "http://www.ametys.org/schema/cms"; 093 094 /** The source resolver */ 095 protected SourceResolver _sourceResolver; 096 097 /** The ametys object resolver instance */ 098 protected AmetysObjectResolver _ametysObjectResolver; 099 100 /** The content type extension point instance */ 101 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 102 103 /** DAO for contents */ 104 protected ContentDAO _contentDAO; 105 106 public void service(ServiceManager manager) throws ServiceException 107 { 108 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 109 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 110 _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 111 _contentDAO = (ContentDAO) manager.lookup(ContentDAO.ROLE); 112 } 113 114 /** 115 * Get the root directory for all custom content types. The directory is created if does not exist. 116 * @return the root directory for all custom content types. 117 * @throws MalformedURLException if failed to get the root directory 118 * @throws IOException if failed to get the root directory 119 */ 120 public File getContentTypeRootDirectory() throws MalformedURLException, IOException 121 { 122 FileSource rootSource = null; 123 try 124 { 125 rootSource = (FileSource) _sourceResolver.resolveURI(__CONTENT_TYPES_DIR); 126 File rootDir = rootSource.getFile(); 127 if (!rootDir.exists()) 128 { 129 rootDir.mkdir(); 130 } 131 return rootDir; 132 } 133 finally 134 { 135 _sourceResolver.release(rootSource); 136 } 137 } 138 139 /** 140 * Get the root directory for all archived content types. The directory is created if does not exist. 141 * @return the root directory for archived content types. 142 * @throws MalformedURLException if failed to get the root directory 143 * @throws IOException if failed to get the root directory 144 */ 145 public File getArchiveRootDirectory() throws MalformedURLException, IOException 146 { 147 File rootDir = getContentTypeRootDirectory(); 148 149 File archiveDir = new File(rootDir, "_archives"); 150 if (!archiveDir.exists()) 151 { 152 archiveDir.mkdir(); 153 } 154 155 return archiveDir; 156 } 157 158 /** 159 * Get the root archive directory for a given content type. The directory is created if does not exist. 160 * @param contentType The content type 161 * @return the root archive directory for this content type. 162 * @throws MalformedURLException if failed to get the archive directory 163 * @throws IOException if failed to get the archive directory 164 */ 165 public File getArchiveRootDirectory(ContentType contentType) throws MalformedURLException, IOException 166 { 167 File archiveDir = getArchiveRootDirectory(); 168 169 File cTypeDir = new File(archiveDir, contentType.getPluginName()); 170 if (!cTypeDir.exists()) 171 { 172 cTypeDir.mkdir(); 173 } 174 175 return cTypeDir; 176 } 177 178 /** 179 * Get the root directory for a given content type. The directory is created if does not exist. 180 * @param contentType The content type definition 181 * @param override true if this is a overriden content type. 182 * @return the root directory for this content type. 183 * @throws MalformedURLException if failed to get the archive directory 184 * @throws IOException if failed to get the archive directory 185 */ 186 public File getContentTypeDirectory(ContentTypeDefinition contentType, boolean override) throws MalformedURLException, IOException 187 { 188 File rootDir = getContentTypeRootDirectory(); 189 190 File cTypeDir = new File(rootDir, override ? "_override" : contentType.getPluginName()); 191 if (!cTypeDir.exists()) 192 { 193 cTypeDir.mkdir(); 194 } 195 196 return cTypeDir; 197 } 198 199 /** 200 * Get the root directory for a given content type. The directory is created if does not exist. 201 * @param contentType The content type 202 * @return the root directory for this content type. 203 * @throws MalformedURLException if failed to get the archive directory 204 * @throws IOException if failed to get the archive directory 205 */ 206 public File getContentTypeDirectory(ContentType contentType) throws MalformedURLException, IOException 207 { 208 File rootDir = getContentTypeRootDirectory(); 209 210 File cTypeDir = new File(rootDir, contentType.getPluginName()); 211 if (!cTypeDir.exists()) 212 { 213 cTypeDir.mkdir(); 214 } 215 216 return cTypeDir; 217 } 218 219 /** 220 * Get the XML definition file for a given content type. 221 * @param contentType The content type definition 222 * @param override true if this is a overriden content type. 223 * @return the XML definition file for this content type. 224 * @throws MalformedURLException if failed to get the archive directory 225 * @throws IOException if failed to get the archive directory 226 */ 227 public File getContentTypeFile(ContentTypeDefinition contentType, boolean override) throws MalformedURLException, IOException 228 { 229 File rootCTypeDir = getContentTypeDirectory(contentType, override); 230 231 String id = contentType.getId().replaceFirst(__CONTENT_TYPE_PREFIX, ""); 232 return new File(rootCTypeDir, id + ".xml"); 233 } 234 235 /** 236 * Get the XML definition file for a given content type. 237 * @param contentType The content type 238 * @return the XML definition file for this content type. 239 * @throws MalformedURLException if failed to get the archive directory 240 * @throws IOException if failed to get the archive directory 241 */ 242 public File getContentTypeFile(ContentType contentType) throws MalformedURLException, IOException 243 { 244 File rootCTypeDir = getContentTypeDirectory(contentType); 245 246 String id = contentType.getId().replaceFirst(__CONTENT_TYPE_PREFIX, ""); 247 return new File(rootCTypeDir, id + ".xml"); 248 } 249 250 /** 251 * Create a new content type. 252 * This method creates a new xml file into WEB-INF/param/content-types folder. 253 * @param contentTypeDef The content type definition 254 * @throws EditContentTypeException if failed to create the content type 255 */ 256 public void createContentType(ContentTypeDefinition contentTypeDef) throws EditContentTypeException 257 { 258 String id = contentTypeDef.getId().replaceFirst(__CONTENT_TYPE_PREFIX, ""); 259 String realId = __CONTENT_TYPE_PREFIX + id; 260 261 Set<String> extensionsIds = _contentTypeExtensionPoint.getExtensionsIds(); 262 if (!extensionsIds.contains(realId)) 263 { 264 if (StringUtils.isNotBlank(contentTypeDef.getPluginName())) 265 { 266 _saxContentType(contentTypeDef, false); 267 } 268 else 269 { 270 throw new EditContentTypeException("The content type '" + contentTypeDef.getId() + "' can't be created without plugin name."); 271 } 272 } 273 else 274 { 275 throw new EditContentTypeException("The content type '" + contentTypeDef.getId() + "' can't be created because it already exists."); 276 } 277 } 278 279 /** 280 * Modify a existing content type. 281 * This method edit the corresponding XML file of the content type in WEB-INF/param/content-types folder. 282 * @param contentTypeDef The content type definition 283 * @throws EditContentTypeException if failed to edit this content type 284 */ 285 public void editContentType(ContentTypeDefinition contentTypeDef) throws EditContentTypeException 286 { 287 // For the content type id we consider that it is the real id, for 288 // example for content type in web-inf the id is prefixed by 289 // "content-type." 290 Set<String> extensionsIds = _contentTypeExtensionPoint.getExtensionsIds(); 291 if (extensionsIds.contains(contentTypeDef.getId())) 292 { 293 boolean override = !_isAutomaticContentType(contentTypeDef.getId()); 294 _saxContentType(contentTypeDef, override); 295 } 296 else 297 { 298 throw new EditContentTypeException("The content type '" + contentTypeDef.getId() + "' can't be edited because this content type doesn't exist."); 299 } 300 } 301 302 private void _saxContentType(ContentTypeDefinition contentTypeDef, boolean override) throws EditContentTypeException 303 { 304 try 305 { 306 File cTypeFile = getContentTypeFile(contentTypeDef, override); 307 308 // Sax the content type in file 309 try (OutputStream os = Files.newOutputStream(Paths.get(cTypeFile.getAbsolutePath()))) 310 { 311 // Create a transformer for saving sax into a file 312 TransformerHandler handler = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler(); 313 314 StreamResult result = new StreamResult(os); 315 handler.setResult(result); 316 317 // Create the format of result 318 Properties format = new Properties(); 319 format.put(OutputKeys.METHOD, "xml"); 320 format.put(OutputKeys.INDENT, "yes"); 321 format.put(OutputKeys.ENCODING, "UTF-8"); 322 format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "4"); 323 handler.getTransformer().setOutputProperties(format); 324 325 try 326 { 327 _saxContentType(contentTypeDef, override, handler); 328 } 329 catch (SAXException | ConfigurationException e) 330 { 331 throw new EditContentTypeException("Error when trying to sax the content type '" + contentTypeDef.getId() + "'", e); 332 } 333 } 334 catch (IOException | TransformerConfigurationException | TransformerFactoryConfigurationError e) 335 { 336 throw new EditContentTypeException(e); 337 } 338 } 339 catch (IOException e) 340 { 341 throw new EditContentTypeException(e.getMessage(), e); 342 } 343 } 344 345 private void _saxContentType(ContentTypeDefinition contentTypeDef, boolean override, TransformerHandler handler) 346 throws SAXException, EditContentTypeException, ConfigurationException 347 { 348 handler.startDocument(); 349 handler.startPrefixMapping("cms", __NAMESPACE_URI); 350 351 if (override) 352 { 353 XMLUtils.startElement(handler, "cms:content-type"); 354 } 355 else 356 { 357 AttributesImpl attributes = new AttributesImpl(); 358 359 if (contentTypeDef.isAbstract()) 360 { 361 attributes.addCDATAAttribute("abstract", "true"); 362 } 363 if (contentTypeDef.getSupertypeIds() != null && contentTypeDef.getSupertypeIds().length != 0) 364 { 365 attributes.addCDATAAttribute("extends", String.join(",", contentTypeDef.getSupertypeIds())); 366 } 367 XMLUtils.startElement(handler, "cms:content-type", attributes); 368 369 _saxGeneralInformation(contentTypeDef, handler); 370 _saxIcons(contentTypeDef.getIconGlyph(), handler); 371 _saxTags(contentTypeDef.getTags(), handler); 372 _saxParentRef(contentTypeDef.getParentRef(), handler); 373 _saxRight(contentTypeDef.getRight(), handler); 374 } 375 376 _saxModelItems(contentTypeDef, handler); 377 _saxViews(contentTypeDef, handler); 378 379 XMLUtils.endElement(handler, "cms:content-type"); 380 handler.endDocument(); 381 } 382 383 private void _saxGeneralInformation(ContentTypeDefinition contentTypeDef, TransformerHandler handler) throws SAXException, EditContentTypeException 384 { 385 I18nizableText label = contentTypeDef.getLabel(); 386 if (label == null) 387 { 388 throw new EditContentTypeException("The content type '" + contentTypeDef.getId() + "' can't be created without label"); 389 } 390 else 391 { 392 _saxI18nizableText(handler, "cms:label", label); 393 } 394 395 _saxI18nizableText(handler, "cms:description", contentTypeDef.getDescription()); 396 397 I18nizableText defaultTitle = contentTypeDef.getDefaultTitle(); 398 if (defaultTitle == null) 399 { 400 throw new EditContentTypeException("The content type '" + contentTypeDef.getId() + "' can't be created without default title"); 401 } 402 else 403 { 404 _saxI18nizableText(handler, "cms:default-title", defaultTitle); 405 } 406 407 _saxI18nizableText(handler, "cms:category", contentTypeDef.getCategory()); 408 } 409 410 private void _saxIcons(String iconGlyph, TransformerHandler handler) throws SAXException 411 { 412 if (StringUtils.isNotBlank(iconGlyph)) 413 { 414 XMLUtils.startElement(handler, "cms:icons"); 415 XMLUtils.createElement(handler, "cms:glyph", iconGlyph); 416 XMLUtils.endElement(handler, "cms:icons"); 417 } 418 } 419 420 private void _saxTags(Set<String> tags, TransformerHandler handler) throws SAXException 421 { 422 if (tags != null && !tags.isEmpty()) 423 { 424 XMLUtils.startElement(handler, "cms:tags"); 425 426 for (String tag : tags) 427 { 428 XMLUtils.createElement(handler, "cms:tag", tag); 429 } 430 431 XMLUtils.endElement(handler, "cms:tags"); 432 } 433 } 434 435 private void _saxParentRef(String parentRef, TransformerHandler handler) throws SAXException 436 { 437 if (StringUtils.isNotBlank(parentRef)) 438 { 439 AttributesImpl attributes = new AttributesImpl(); 440 attributes.addCDATAAttribute("name", parentRef); 441 XMLUtils.createElement(handler, "cms:parent-ref", attributes); 442 } 443 } 444 445 private void _saxRight(String right, TransformerHandler handler) throws SAXException 446 { 447 if (StringUtils.isNotBlank(right)) 448 { 449 XMLUtils.createElement(handler, "cms:right", right); 450 } 451 } 452 453 private void _saxModelItems(ContentTypeDefinition contentTypeDef, TransformerHandler handler) throws SAXException, ConfigurationException 454 { 455 if (contentTypeDef.getModelItems() != null) 456 { 457 for (ModelItem modelItem : contentTypeDef.getModelItems()) 458 { 459 _saxModelItem(modelItem, handler); 460 } 461 } 462 } 463 464 @SuppressWarnings("unchecked") 465 private void _saxModelItem(ModelItem modelItem, TransformerHandler handler) throws SAXException, ConfigurationException 466 { 467 if (modelItem != null) 468 { 469 if (modelItem.getType() != null) 470 { 471 if (modelItem instanceof RepeaterDefinition) 472 { 473 _saxRepeater((RepeaterDefinition) modelItem, handler); 474 } 475 else if (modelItem instanceof CompositeDefinition) 476 { 477 if ("dc".equals(modelItem.getName())) // Dublin core exception 478 { 479 XMLUtils.createElement(handler, "cms:dublin-core"); 480 } 481 else 482 { 483 _saxComposite((CompositeDefinition) modelItem, handler); 484 } 485 } 486 else if (modelItem instanceof ElementDefinition) 487 { 488 _saxElementDefinition((ElementDefinition) modelItem, handler); 489 } 490 } 491 } 492 } 493 494 private void _saxRepeater(RepeaterDefinition repeater, TransformerHandler handler) throws SAXException, ConfigurationException 495 { 496 AttributesImpl attributes = new AttributesImpl(); 497 attributes.addCDATAAttribute("name", repeater.getName()); 498 499 String initialSize = Integer.toString(repeater.getInitialSize()); 500 String minSize = Integer.toString(repeater.getMinSize()); 501 String maxSize = Integer.toString(repeater.getMaxSize()); 502 if (StringUtils.isNotBlank(initialSize)) 503 { 504 attributes.addCDATAAttribute("initial-size", initialSize); 505 } 506 if (StringUtils.isNotBlank(minSize)) 507 { 508 attributes.addCDATAAttribute("min-size", minSize); 509 } 510 if (StringUtils.isNotBlank(maxSize)) 511 { 512 attributes.addCDATAAttribute("max-size", maxSize); 513 } 514 515 XMLUtils.startElement(handler, "cms:repeater", attributes); 516 517 _saxI18nizableText(handler, "cms:label", repeater.getLabel()); 518 _saxI18nizableText(handler, "cms:description", repeater.getDescription()); 519 _saxI18nizableText(handler, "cms:add-label", repeater.getAddLabel()); 520 _saxI18nizableText(handler, "cms:del-label", repeater.getDeleteLabel()); 521 _saxChildrenModelItems(repeater, handler); 522 523 XMLUtils.endElement(handler, "cms:repeater"); 524 } 525 526 private void _saxComposite(CompositeDefinition composite, TransformerHandler handler) throws SAXException, ConfigurationException 527 { 528 AttributesImpl attributes = new AttributesImpl(); 529 attributes.addCDATAAttribute("name", composite.getName()); 530 attributes.addCDATAAttribute("type", composite.getType().getId()); 531 532 XMLUtils.startElement(handler, "cms:metadata", attributes); 533 534 _saxI18nizableText(handler, "cms:label", composite.getLabel()); 535 _saxI18nizableText(handler, "cms:description", composite.getDescription()); 536 _saxChildrenModelItems(composite, handler); 537 538 XMLUtils.endElement(handler, "cms:metadata"); 539 } 540 541 private void _saxChildrenModelItems(ModelItemContainer modelItemContainer, TransformerHandler handler) throws ConfigurationException, SAXException 542 { 543 if (modelItemContainer.getModelItems() != null) 544 { 545 for (ModelItem modelItem : modelItemContainer.getModelItems()) 546 { 547 _saxModelItem(modelItem, handler); 548 } 549 } 550 } 551 552 private void _saxElementDefinition(ElementDefinition<? extends Object> elementDefinition, TransformerHandler handler) throws SAXException, ConfigurationException 553 { 554 AttributesImpl attributes = new AttributesImpl(); 555 attributes.addCDATAAttribute("name", elementDefinition.getName()); 556 attributes.addCDATAAttribute("multiple", Boolean.toString(elementDefinition.isMultiple())); 557 attributes.addCDATAAttribute("type", elementDefinition.getType().getId()); 558 559 if (elementDefinition instanceof ContentAttributeDefinition) 560 { 561 ContentAttributeDefinition contentAttributeDefinition = (ContentAttributeDefinition) elementDefinition; 562 String linkedContentTypeId = contentAttributeDefinition.getContentTypeId(); 563 if (linkedContentTypeId != null) 564 { 565 attributes.addCDATAAttribute("contentType", linkedContentTypeId); 566 if (contentAttributeDefinition.getInvertRelationPath() != null) 567 { 568 attributes.addCDATAAttribute("invert", contentAttributeDefinition.getInvertRelationPath()); 569 } 570 } 571 } 572 573 XMLUtils.startElement(handler, "cms:metadata", attributes); 574 575 _saxI18nizableText(handler, "cms:label", elementDefinition.getLabel()); 576 _saxI18nizableText(handler, "cms:description", elementDefinition.getDescription()); 577 _saxWidget(elementDefinition, handler); 578 _saxEnumeration(elementDefinition, handler); 579 _saxValidator(elementDefinition, handler); 580 581 XMLUtils.endElement(handler, "cms:metadata"); 582 } 583 584 private void _saxWidget(ElementDefinition<? extends Object> elementDefinition, TransformerHandler handler) throws SAXException 585 { 586 if (StringUtils.isNotBlank(elementDefinition.getWidget())) 587 { 588 XMLUtils.createElement(handler, "widget", elementDefinition.getWidget()); 589 } 590 591 if (elementDefinition.getWidgetParameters() != null) 592 { 593 XMLUtils.startElement(handler, "widget-params"); 594 595 for (Entry<String, I18nizableText> widgetParam : elementDefinition.getWidgetParameters().entrySet()) 596 { 597 if (widgetParam.getValue() != null) 598 { 599 AttributesImpl attributes = new AttributesImpl(); 600 attributes.addCDATAAttribute("name", widgetParam.getKey()); 601 _saxI18nizableText(handler, "param", widgetParam.getValue(), attributes); 602 } 603 } 604 605 XMLUtils.endElement(handler, "widget-params"); 606 } 607 } 608 609 private void _saxEnumeration(ElementDefinition elementDefinition, TransformerHandler handler) throws SAXException, ConfigurationException 610 { 611 Enumerator enumerator = elementDefinition.getEnumerator(); 612 if (enumerator != null && enumerator instanceof StaticEnumerator) 613 { 614 _saxDefaultEnumerator(elementDefinition, handler); 615 } 616 else if (StringUtils.isNotBlank(elementDefinition.getCustomEnumerator())) 617 { 618 _saxCustomEnumerator(elementDefinition, handler); 619 } 620 } 621 622 private void _saxDefaultEnumerator(ElementDefinition elementDefinition, TransformerHandler handler) 623 { 624 try 625 { 626 Map<Object, I18nizableText> entries = elementDefinition.getEnumerator().getTypedEntries(); 627 628 if (entries != null) 629 { 630 // Enumerated values 631 XMLUtils.startElement(handler, "enumeration"); 632 for (Entry<Object, I18nizableText> entry : entries.entrySet()) 633 { 634 if (entry != null) 635 { 636 if (entry.getValue() != null) 637 { 638 XMLUtils.startElement(handler, "entry"); 639 XMLUtils.createElement(handler, "value", entry.getKey().toString()); 640 _saxI18nizableText(handler, "label", entry.getValue()); 641 XMLUtils.endElement(handler, "entry"); 642 } 643 } 644 } 645 XMLUtils.endElement(handler, "enumeration"); 646 } 647 } 648 catch (Exception e) 649 { 650 // Silently ignore 651 } 652 } 653 654 private void _saxCustomEnumerator(ElementDefinition elementDefinition, TransformerHandler handler) throws SAXException, ConfigurationException 655 { 656 XMLUtils.startElement(handler, "enumeration"); 657 658 AttributesImpl attributes = new AttributesImpl(); 659 attributes.addCDATAAttribute("class", elementDefinition.getCustomEnumerator()); 660 XMLUtils.startElement(handler, "custom-enumerator", attributes); 661 662 if (elementDefinition.getEnumeratorConfiguration() != null) 663 { 664 DefaultConfigurationSerializer configurationSerializer = new DefaultConfigurationSerializer(); 665 configurationSerializer.serialize(handler, elementDefinition.getEnumeratorConfiguration()); 666 } 667 668 XMLUtils.endElement(handler, "custom-enumerator"); 669 670 XMLUtils.endElement(handler, "enumeration"); 671 } 672 673 private void _saxValidator(ElementDefinition elementDefinition, TransformerHandler handler) throws SAXException, ConfigurationException 674 { 675 Validator validator = elementDefinition.getValidator(); 676 if (validator != null && validator.getClass().equals(DefaultValidator.class)) 677 { 678 _saxDefaultValidator(elementDefinition, handler); 679 } 680 else if (StringUtils.isNotBlank(elementDefinition.getCustomValidator())) 681 { 682 _saxCustomValidator(elementDefinition, handler); 683 } 684 } 685 686 private void _saxDefaultValidator(ElementDefinition elementDefinition, TransformerHandler handler) throws SAXException 687 { 688 Validator validator = elementDefinition.getValidator(); 689 690 XMLUtils.startElement(handler, "validation"); 691 692 Map<String, Object> configuration = validator.getConfiguration(); 693 694 // Mandatory traitment 695 Object mandatory = configuration.get("mandatory"); 696 if (mandatory != null && mandatory.toString().equals("true")) 697 { 698 XMLUtils.createElement(handler, "mandatory"); 699 } 700 // Regexp traitment 701 Object regexp = configuration.get("regexp"); 702 if (regexp != null) 703 { 704 XMLUtils.createElement(handler, "regexp", regexp.toString()); 705 } 706 // Invalid text traitment 707 Object invalidTextAsObject = configuration.get("invalidText"); 708 if (invalidTextAsObject != null) 709 { 710 I18nizableText invalidTextAsI18nizableText = (I18nizableText) invalidTextAsObject; 711 _saxI18nizableText(handler, "invalidText", invalidTextAsI18nizableText); 712 } 713 714 XMLUtils.endElement(handler, "validation"); 715 } 716 717 private void _saxCustomValidator(ElementDefinition elementDefinition, TransformerHandler handler) throws SAXException, ConfigurationException 718 { 719 XMLUtils.startElement(handler, "validation"); 720 721 AttributesImpl attributes = new AttributesImpl(); 722 attributes.addCDATAAttribute("class", elementDefinition.getCustomValidator()); 723 XMLUtils.startElement(handler, "custom-validator", attributes); 724 725 if (elementDefinition.getValidatorConfiguration() != null) 726 { 727 DefaultConfigurationSerializer configurationSerializer = new DefaultConfigurationSerializer(); 728 configurationSerializer.serialize(handler, elementDefinition.getValidatorConfiguration()); 729 } 730 731 XMLUtils.endElement(handler, "custom-validator"); 732 733 XMLUtils.endElement(handler, "validation"); 734 } 735 736 private void _saxViews(ContentTypeDefinition contentType, TransformerHandler handler) throws SAXException 737 { 738 List<View> views = contentType.getViews(); 739 if (views != null) 740 { 741 for (View view : views) 742 { 743 _saxView(view, handler); 744 } 745 } 746 else 747 { 748 _saxDefaultViews(handler); 749 } 750 } 751 752 private void _saxDefaultViews(TransformerHandler handler) throws SAXException 753 { 754 _saxDefaultView("main", "view", handler); 755 _saxDefaultView("main", "edition", handler); 756 _saxDefaultView("abstract", "view", handler); 757 _saxDefaultView("link", "view", handler); 758 _saxDefaultView("details", "view", handler); 759 _saxDefaultView("index", "view", handler); 760 } 761 762 private void _saxDefaultView(String name, String view, TransformerHandler handler) throws SAXException 763 { 764 AttributesImpl attributes = new AttributesImpl(); 765 attributes.addCDATAAttribute("name", name); 766 attributes.addCDATAAttribute("type", view); 767 XMLUtils.createElement(handler, "cms:metadata-set", attributes); 768 } 769 770 private void _saxView(View view, TransformerHandler handler) throws SAXException 771 { 772 AttributesImpl attributes = new AttributesImpl(); 773 attributes.addCDATAAttribute("name", view.getName()); 774 attributes.addCDATAAttribute("type", "view"); 775 attributes.addCDATAAttribute("internal", Boolean.toString(view.isInternal())); 776 777 XMLUtils.startElement(handler, "cms:metadata-set", attributes); 778 779 _saxI18nizableText(handler, "cms:label", view.getLabel()); 780 _saxI18nizableText(handler, "cms:description", view.getDescription()); 781 for (ViewItem viewItem : view.getViewItems()) 782 { 783 _saxViewItem(viewItem, handler); 784 } 785 786 XMLUtils.endElement(handler, "cms:metadata-set"); 787 } 788 789 private void _saxViewItem(ViewItem viewItem, TransformerHandler handler) throws SAXException 790 { 791 if (viewItem instanceof ModelViewItem) 792 { 793 ModelViewItem modelViewItem = (ModelViewItem) viewItem; 794 795 if ("dc".equals(modelViewItem.getDefinition().getName())) // dublin core exception 796 { 797 XMLUtils.createElement(handler, "cms:dublin-core"); 798 } 799 else 800 { 801 AttributesImpl attributes = new AttributesImpl(); 802 attributes.addCDATAAttribute("name", modelViewItem.getDefinition().getName()); 803 804 XMLUtils.startElement(handler, "cms:metadata-ref", attributes); 805 if (modelViewItem instanceof ViewItemContainer) 806 { 807 for (ViewItem child : ((ViewItemContainer) modelViewItem).getViewItems()) 808 { 809 _saxViewItem(child, handler); 810 } 811 } 812 XMLUtils.endElement(handler, "cms:metadata-ref"); 813 } 814 } 815 else if (viewItem instanceof SimpleViewItemGroup) 816 { 817 SimpleViewItemGroup simpleViewItemGroup = (SimpleViewItemGroup) viewItem; 818 AttributesImpl attributes = new AttributesImpl(); 819 attributes.addCDATAAttribute("role", simpleViewItemGroup.getRole()); 820 821 XMLUtils.startElement(handler, "cms:fieldset", attributes); 822 823 _saxI18nizableText(handler, "cms:label", simpleViewItemGroup.getLabel()); 824 for (ViewItem child : simpleViewItemGroup.getViewItems()) 825 { 826 _saxViewItem(child, handler); 827 } 828 829 XMLUtils.endElement(handler, "cms:fieldset"); 830 } 831 } 832 833 private void _saxI18nizableText(TransformerHandler handler, String tagName, I18nizableText text) throws SAXException 834 { 835 _saxI18nizableText(handler, tagName, text, new AttributesImpl()); 836 } 837 838 private void _saxI18nizableText(TransformerHandler handler, String tagName, I18nizableText text, AttributesImpl attrs) throws SAXException 839 { 840 if (text != null) 841 { 842 attrs.addCDATAAttribute("i18n", Boolean.toString(text.isI18n())); 843 844 String value = ""; 845 846 if (text.isI18n()) 847 { 848 if (StringUtils.isNotBlank(text.getCatalogue())) 849 { 850 value = text.getCatalogue() + ":"; 851 } 852 value = value + text.getKey(); 853 } 854 else 855 { 856 value = text.getLabel(); 857 } 858 859 XMLUtils.createElement(handler, tagName, attrs, value); 860 } 861 } 862 863 /** 864 * Determines if the content type can be removed 865 * @param contentTypeId The id of content type 866 * @return true if the content type can be removed 867 */ 868 public boolean canRemoveContentType(String contentTypeId) 869 { 870 return _isAutomaticContentType(contentTypeId) 871 && _isLeafContentType(contentTypeId) 872 && !_isInUseContentType(contentTypeId); 873 } 874 875 /** 876 * Remove a existing content type defined in WEB-INF/param/content-types directory.<br> 877 * This method archived the content type into the WEB-INF/param/content-types/_archives/pluginName folder. 878 * @param contentTypeId the id of content type to remove 879 * @throws RemoveContentTypeException if the content type could not be removed 880 */ 881 public void removeContentType(String contentTypeId) throws RemoveContentTypeException 882 { 883 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 884 885 if (contentType == null) 886 { 887 throw new RemoveContentTypeException("The content type with id '" + contentTypeId + "' can't be removed because this content type doesn't exist."); 888 } 889 890 if (!_isAutomaticContentType(contentTypeId)) 891 { 892 throw new RemoveContentTypeException("The content type with id '" + contentTypeId + "' can't be removed because it is not a modifiable content type"); 893 } 894 895 if (!_isLeafContentType(contentTypeId)) 896 { 897 throw new RemoveContentTypeException("The content type with id '" + contentTypeId + "' can't be removed because it has one or more sub-types"); 898 } 899 900 if (_isInUseContentType(contentTypeId)) 901 { 902 throw new RemoveContentTypeException("The content type with id '" + contentTypeId + "' can't be removed because it was currently in use"); 903 } 904 905 // Archive the content type 906 _archiveContentType(contentType); 907 } 908 909 private void _archiveContentType(ContentType contentType) throws RemoveContentTypeException 910 { 911 try 912 { 913 File archiveRootDir = getArchiveRootDirectory(contentType); 914 915 String realId = contentType.getId().replaceFirst(__CONTENT_TYPE_PREFIX, ""); 916 File cTypeFile = getContentTypeFile(contentType); 917 918 File destFile = new File(archiveRootDir, realId + ".xml"); 919 920 int index = 2; 921 while (destFile.exists()) 922 { 923 destFile = new File(archiveRootDir, realId + "-" + index + ".xml"); 924 index++; 925 } 926 927 FileUtils.moveFile(cTypeFile, destFile); 928 } 929 catch (IOException e) 930 { 931 throw new RemoveContentTypeException("Fail to archive content type " + contentType.getId(), e); 932 } 933 } 934 935 private boolean _isInUseContentType(String contentTypeId) 936 { 937 Expression cTypeExpr = new ContentTypeOrMixinExpression(Operator.EQ, contentTypeId); 938 String xPathQuery = ContentQueryHelper.getContentXPathQuery(cTypeExpr); 939 AmetysObjectIterable<Content> contents = _ametysObjectResolver.query(xPathQuery); 940 return contents.iterator().hasNext(); 941 } 942 943 private boolean _isAutomaticContentType(String contentTypeId) 944 { 945 ContentType contentType = _contentTypeExtensionPoint.getExtension(contentTypeId); 946 return contentType instanceof AutomaticContentType; 947 } 948 949 private boolean _isLeafContentType(String contentTypeId) 950 { 951 return _contentTypeExtensionPoint.getDirectSubTypes(contentTypeId).size() == 0; 952 } 953 954}