001/* 002 * Copyright 2019 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.content; 017 018import java.time.LocalDate; 019import java.time.ZonedDateTime; 020import java.time.format.DateTimeFormatter; 021import java.util.Collections; 022import java.util.Comparator; 023import java.util.Date; 024import java.util.List; 025import java.util.Locale; 026import java.util.Set; 027 028import javax.jcr.Node; 029import javax.jcr.RepositoryException; 030 031import org.apache.avalon.framework.component.Component; 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034import org.apache.avalon.framework.service.Serviceable; 035import org.apache.cocoon.xml.AttributesImpl; 036import org.apache.cocoon.xml.XMLUtils; 037import org.apache.commons.lang.ArrayUtils; 038import org.apache.commons.lang3.StringUtils; 039import org.xml.sax.ContentHandler; 040import org.xml.sax.SAXException; 041 042import org.ametys.cms.contenttype.ContentTypesHelper; 043import org.ametys.cms.languages.Language; 044import org.ametys.cms.languages.LanguagesManager; 045import org.ametys.cms.repository.Content; 046import org.ametys.cms.repository.ReactionableObject; 047import org.ametys.cms.repository.ReactionableObjectHelper; 048import org.ametys.cms.repository.ReportableObject; 049import org.ametys.cms.repository.ReportableObjectHelper; 050import org.ametys.cms.repository.WorkflowAwareContent; 051import org.ametys.cms.repository.comment.Comment; 052import org.ametys.cms.repository.comment.CommentableContent; 053import org.ametys.cms.repository.comment.CommentsDAO; 054import org.ametys.core.user.UserIdentity; 055import org.ametys.core.util.DateUtils; 056import org.ametys.plugins.repository.AmetysRepositoryException; 057import org.ametys.plugins.repository.data.external.ExternalizableDataProviderExtensionPoint; 058import org.ametys.plugins.repository.dublincore.DublinCoreAwareAmetysObject; 059import org.ametys.plugins.repository.jcr.JCRAmetysObject; 060import org.ametys.plugins.repository.model.RepositoryDataContext; 061import org.ametys.plugins.repository.version.VersionAwareAmetysObject; 062import org.ametys.plugins.workflow.store.AmetysStep; 063import org.ametys.plugins.workflow.support.WorkflowHelper; 064import org.ametys.plugins.workflow.support.WorkflowProvider; 065import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 066import org.ametys.runtime.i18n.I18nizableText; 067import org.ametys.runtime.model.View; 068 069import com.opensymphony.workflow.WorkflowException; 070import com.opensymphony.workflow.spi.Step; 071 072/** 073 * Component responsible for generating SAX events representing a {@link Content}. 074 */ 075public class ContentSaxer implements Serviceable, Component 076{ 077 /** Avalon role. */ 078 public static final String CMS_CONTENT_SAXER_ROLE = ContentSaxer.class.getName(); 079 080 private WorkflowProvider _workflowProvider; 081 private WorkflowHelper _worklflowHelper; 082 private ContentTypesHelper _contentTypesHelper; 083 private LanguagesManager _languageManager; 084 private ExternalizableDataProviderExtensionPoint _externalizableDataProviderEP; 085 private CommentsDAO _commentsDAO; 086 private ReactionableObjectHelper _reactionableHelper; 087 088 @Override 089 public void service(ServiceManager manager) throws ServiceException 090 { 091 _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE); 092 _worklflowHelper = (WorkflowHelper) manager.lookup(WorkflowHelper.ROLE); 093 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 094 _languageManager = (LanguagesManager) manager.lookup(LanguagesManager.ROLE); 095 _externalizableDataProviderEP = (ExternalizableDataProviderExtensionPoint) manager.lookup(ExternalizableDataProviderExtensionPoint.ROLE); 096 _commentsDAO = (CommentsDAO) manager.lookup(CommentsDAO.ROLE); 097 _reactionableHelper = (ReactionableObjectHelper) manager.lookup(ReactionableObjectHelper.ROLE); 098 } 099 100 /** 101 * Generates SAX events representing a {@link Content}. 102 * <br>When called with a non null tag name, a surrounding element will be generated, 103 * along with XML attributes representing the content's metadata (creation/modification/validation dates and authors, ...). 104 * @param content the {@link Content}. 105 * @param contentHandler the ContentHandler receving SAX events. 106 * @param locale the {@link Locale} to use for eg. multilingual attributes. 107 * @param view the View or null to select all attributes. 108 * @param tagName the surrounding tag name or null to SAX events without root tag. 109 * @param saxWorkflowStep if true, also produces SAX events for the current workflow step. 110 * @param saxWorkflowInfo if true, also produces SAX events for detailed information about the current workflow step. 111 * @param saxLanguageInfo if true, also produces SAX events for detailed information about the content language. 112 * @param attributesTagName the name of the tag surrounding attributes. Used for legacy purposes. 113 * @throws SAXException if an error occurs during the SAX events generation. 114 */ 115 public void saxContent(Content content, ContentHandler contentHandler, Locale locale, View view, String tagName, boolean saxWorkflowStep, boolean saxWorkflowInfo, boolean saxLanguageInfo, String attributesTagName) throws SAXException 116 { 117 saxContent(content, contentHandler, locale, view, tagName, saxWorkflowStep, saxWorkflowInfo, saxLanguageInfo, attributesTagName, false); 118 } 119 120 /** 121 * Generates SAX events representing a {@link Content}. 122 * <br>When called with a non null tag name, a surrounding element will be generated, 123 * along with XML attributes representing the content's metadata (creation/modification/validation dates and authors, ...). 124 * @param content the {@link Content}. 125 * @param contentHandler the ContentHandler receving SAX events. 126 * @param locale the {@link Locale} to use for eg. multilingual attributes. 127 * @param view the View or null to select all attributes. 128 * @param tagName the surrounding tag name or null to SAX events without root tag. 129 * @param saxWorkflowStep if true, also produces SAX events for the current workflow step. 130 * @param saxWorkflowInfo if true, also produces SAX events for detailed information about the current workflow step. 131 * @param saxLanguageInfo if true, also produces SAX events for detailed information about the content language. 132 * @param attributesTagName the name of the tag surrounding attributes. Used for legacy purposes. 133 * @param isEdition <code>true</code> if SAX events are generated in edition mode, <code>false</code> otherwise 134 * @throws SAXException if an error occurs during the SAX events generation. 135 */ 136 public void saxContent(Content content, ContentHandler contentHandler, Locale locale, View view, String tagName, boolean saxWorkflowStep, boolean saxWorkflowInfo, boolean saxLanguageInfo, String attributesTagName, boolean isEdition) throws SAXException 137 { 138 if (StringUtils.isNotEmpty(tagName)) 139 { 140 saxRootTag(content, contentHandler, locale, tagName); 141 } 142 143 saxBody(content, contentHandler, locale, view, tagName, saxWorkflowStep, saxWorkflowInfo, saxLanguageInfo, attributesTagName, isEdition); 144 145 if (StringUtils.isNotEmpty(tagName)) 146 { 147 XMLUtils.endElement(contentHandler, tagName); 148 } 149 } 150 151 /** 152 * Generates SAX events for the content data. 153 * @param content the {@link Content}. 154 * @param contentHandler the ContentHandler receving SAX events. 155 * @param locale the {@link Locale} to use for eg. multilingual attributes. 156 * @param view the View or null to select all attributes. 157 * @param tagName the surrounding tag name or null to SAX events without root tag. 158 * @param saxWorkflowStep if true, also produces SAX events for the current workflow step. 159 * @param saxWorkflowInfo if true, also produces SAX events for detailed information about the current workflow step. 160 * @param saxLanguageInfo if true, also produces SAX events for detailed information about the content language. 161 * @param attributesTagName the name of the tag surrounding attributes. Used for legacy purposes. 162 * @param isEdition <code>true</code> if SAX events are generated in edition mode, <code>false</code> otherwise 163 * @throws SAXException if an error occurs during the SAX events generation. 164 */ 165 protected void saxBody(Content content, ContentHandler contentHandler, Locale locale, View view, String tagName, boolean saxWorkflowStep, boolean saxWorkflowInfo, boolean saxLanguageInfo, String attributesTagName, boolean isEdition) throws SAXException 166 { 167 saxContentTypes(content, contentHandler, true); 168 saxAttributes(content, contentHandler, locale, view, tagName, attributesTagName, isEdition); 169 170 if (saxWorkflowStep || saxWorkflowInfo) 171 { 172 saxWorkflowStep(content, contentHandler, saxWorkflowInfo); 173 } 174 175 if (saxLanguageInfo) 176 { 177 saxLanguage(content, contentHandler); 178 } 179 180 saxDublinCoreMetadata(content, contentHandler); 181 182 if (content instanceof CommentableContent) 183 { 184 saxContentComments((CommentableContent) content, contentHandler); 185 } 186 187 if (content instanceof ReactionableObject) 188 { 189 _reactionableHelper.saxReactions((ReactionableObject) content, contentHandler); 190 } 191 192 if (content instanceof ReportableObject) 193 { 194 ReportableObjectHelper.saxReports((ReportableObject) content, contentHandler); 195 } 196 } 197 198 /** 199 * Generates a surrounding tag, with content metadata. 200 * @param content the {@link Content}. 201 * @param contentHandler the ContentHandler receving SAX events. 202 * @param locale the {@link Locale} to use for eg. multilingual attributes. 203 * @param tagName the surrounding tag name or null to SAX events without root tag. 204 * @throws SAXException if an error occurs during the SAX events generation. 205 */ 206 protected void saxRootTag(Content content, ContentHandler contentHandler, Locale locale, String tagName) throws SAXException 207 { 208 AttributesImpl attrs = new AttributesImpl(); 209 210 if (content instanceof JCRAmetysObject) 211 { 212 _addJcrAttributes((JCRAmetysObject) content, attrs); 213 } 214 215 attrs.addCDATAAttribute("id", content.getId()); 216 attrs.addCDATAAttribute("name", content.getName()); 217 attrs.addCDATAAttribute("title", content.getTitle(locale)); 218 if (content.getLanguage() != null) 219 { 220 attrs.addCDATAAttribute("language", content.getLanguage()); 221 } 222 attrs.addCDATAAttribute("createdAt", DateUtils.zonedDateTimeToString(content.getCreationDate())); 223 attrs.addCDATAAttribute("creator", UserIdentity.userIdentityToString(content.getCreator())); 224 attrs.addCDATAAttribute("lastModifiedAt", DateUtils.zonedDateTimeToString(content.getLastModified())); 225 226 ZonedDateTime lastValidatedAt = content.getLastValidationDate(); 227 if (lastValidatedAt != null) 228 { 229 attrs.addCDATAAttribute("lastValidatedAt", DateUtils.zonedDateTimeToString(lastValidatedAt)); 230 } 231 232 attrs.addCDATAAttribute("lastContributor", UserIdentity.userIdentityToString(content.getLastContributor())); 233 attrs.addCDATAAttribute("commentable", Boolean.toString(content instanceof CommentableContent)); 234 235 addAttributeIfNotNull (attrs, "iconGlyph", _contentTypesHelper.getIconGlyph(content)); 236 addAttributeIfNotNull (attrs, "iconDecorator", _contentTypesHelper.getIconDecorator(content)); 237 238 addAttributeIfNotNull (attrs, "smallIcon", _contentTypesHelper.getSmallIcon(content)); 239 addAttributeIfNotNull (attrs, "mediumIcon", _contentTypesHelper.getMediumIcon(content)); 240 addAttributeIfNotNull (attrs, "largeIcon", _contentTypesHelper.getLargeIcon(content)); 241 242 XMLUtils.startElement(contentHandler, tagName, attrs); 243 } 244 245 private void _addJcrAttributes(JCRAmetysObject content, AttributesImpl attrs) 246 { 247 Node node = content.getNode(); 248 try 249 { 250 attrs.addCDATAAttribute("uuid", node.getIdentifier()); 251 } 252 catch (RepositoryException e) 253 { 254 throw new IllegalArgumentException("Unable to get jcr UUID for content '" + content.getId() + "'", e); 255 } 256 257 try 258 { 259 attrs.addCDATAAttribute("primaryType", node.getPrimaryNodeType().getName()); 260 } 261 catch (RepositoryException e) 262 { 263 throw new IllegalArgumentException("Unable to get jcr Primary Type for content '" + content.getId() + "'", e); 264 } 265 } 266 267 /** 268 * Generates SAX events for {@link Content#getTypes content types}, and possibly {@link Content#getMixinTypes mixin types} 269 * @param content the {@link Content}. 270 * @param contentHandler the ContentHandler receving SAX events. 271 * @param saxMixins if true, also produces SAX events for {@link Content#getMixinTypes mixin types}. 272 * @throws SAXException if an error occurs during the SAX events generation. 273 */ 274 protected void saxContentTypes(Content content, ContentHandler contentHandler, boolean saxMixins) throws SAXException 275 { 276 _saxContentTypes(content, contentHandler); 277 if (saxMixins) 278 { 279 _saxMixins(content, contentHandler); 280 } 281 } 282 283 private void _saxContentTypes(Content content, ContentHandler contentHandler) throws SAXException 284 { 285 String contentTypesTagName = "contentTypes"; 286 String singleContentTypeTagName = "contentType"; 287 XMLUtils.startElement(contentHandler, contentTypesTagName); 288 for (String contentType : content.getTypes()) 289 { 290 XMLUtils.createElement(contentHandler, singleContentTypeTagName, contentType); 291 } 292 XMLUtils.endElement(contentHandler, contentTypesTagName); 293 } 294 295 private void _saxMixins(Content content, ContentHandler contentHandler) throws SAXException 296 { 297 String mixinsTagName = "mixins"; 298 String singleMixinTagName = "mixin"; 299 XMLUtils.startElement(contentHandler, mixinsTagName); 300 for (String mixinType : content.getMixinTypes()) 301 { 302 XMLUtils.createElement(contentHandler, singleMixinTagName, mixinType); 303 } 304 XMLUtils.endElement(contentHandler, mixinsTagName); 305 } 306 307 /** 308 * Generates SAX events for actual content's data. 309 * @param content the {@link Content}. 310 * @param contentHandler the ContentHandler receving SAX events. 311 * @param locale the {@link Locale} to use for eg. multilingual attributes. 312 * @param view the View or null to select all attributes. 313 * @param tagName the surrounding tag name or null to SAX events without root tag. 314 * @param attributesTagName the name of the tag surrounding attributes. Used for legacy purposes. 315 * @param isEdition <code>true</code> if SAX events are generated in edition mode, <code>false</code> otherwise 316 * @throws SAXException if an error occurs during the SAX events generation. 317 */ 318 protected void saxAttributes(Content content, ContentHandler contentHandler, Locale locale, View view, String tagName, String attributesTagName, boolean isEdition) throws SAXException 319 { 320 XMLUtils.startElement(contentHandler, attributesTagName); 321 322 RepositoryDataContext context = RepositoryDataContext.newInstance(); 323 context.withLocale(locale); 324 325 if (view == null) 326 { 327 content.dataToSAX(contentHandler, context.withEmptyValues(false)); 328 } 329 else 330 { 331 if (isEdition) 332 { 333 Set<String> externalizableData = _externalizableDataProviderEP.getExternalizableDataPaths(content); 334 content.dataToSAXForEdition(contentHandler, view, context.withExternalizableData(externalizableData)); 335 } 336 else 337 { 338 content.dataToSAX(contentHandler, view, context.withEmptyValues(false)); 339 } 340 } 341 342 XMLUtils.endElement(contentHandler, attributesTagName); 343 } 344 345 /** 346 * Generates SAX events representing the current workflow step. 347 * @param content the {@link Content}. 348 * @param contentHandler the ContentHandler receiving SAX events. 349 * @param saxWorkflowInfo if true, also produces SAX events for detailed information about the current workflow step. 350 * @throws SAXException if an error occurs during the SAX events generation. 351 */ 352 protected void saxWorkflowStep(Content content, ContentHandler contentHandler, boolean saxWorkflowInfo) throws SAXException 353 { 354 if (content instanceof WorkflowAwareContent) 355 { 356 WorkflowAwareContent waContent = (WorkflowAwareContent) content; 357 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent); 358 359 try 360 { 361 long workflowId = waContent.getWorkflowId(); 362 String workflowName = workflow.getWorkflowName(workflowId); 363 364 Step currentStep = getCurrentStep(waContent, workflow); 365 366 int currentStepId = currentStep.getStepId(); 367 368 I18nizableText workflowStepName = new I18nizableText("application", _worklflowHelper.getStepName(workflowName, currentStepId)); 369 370 AttributesImpl atts = new AttributesImpl(); 371 atts.addAttribute("", "id", "id", "CDATA", String.valueOf(currentStepId)); 372 atts.addAttribute("", "workflowName", "workflowName", "CDATA", String.valueOf(workflowName)); 373 374 if (saxWorkflowInfo) 375 { 376 if ("application".equals(workflowStepName.getCatalogue())) 377 { 378 atts.addAttribute("", "icon-small", "icon-small", "CDATA", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-small.png"); 379 atts.addAttribute("", "icon-medium", "icon-medium", "CDATA", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-medium.png"); 380 atts.addAttribute("", "icon-large", "icon-large", "CDATA", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-large.png"); 381 } 382 else 383 { 384 String pluginName = workflowStepName.getCatalogue().substring("plugin.".length()); 385 atts.addAttribute("", "icon-small", "icon-small", "CDATA", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-small.png"); 386 atts.addAttribute("", "icon-medium", "icon-medium", "CDATA", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-medium.png"); 387 atts.addAttribute("", "icon-large", "icon-large", "CDATA", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-large.png"); 388 } 389 } 390 391 XMLUtils.startElement(contentHandler, "workflow-step", atts); 392 393 if (saxWorkflowInfo) 394 { 395 workflowStepName.toSAX(contentHandler); 396 } 397 398 XMLUtils.endElement(contentHandler, "workflow-step"); 399 } 400 catch (AmetysRepositoryException e) 401 { 402 // Current step id was not positioned 403 } 404 catch (WorkflowException e) 405 { 406 // Ignore, just don't SAX the workflow step. 407 } 408 } 409 } 410 411 /** 412 * Get the current workflow step of the content. 413 * @param content the {@link Content}. 414 * @param workflow the associated workflow. 415 * @return the current step 416 * @throws WorkflowException if somethng got wrong processing workflow data. 417 */ 418 protected Step getCurrentStep(WorkflowAwareContent content, AmetysObjectWorkflow workflow) throws WorkflowException 419 { 420 long workflowId = content.getWorkflowId(); 421 422 Step currentStep = (Step) workflow.getCurrentSteps(workflowId).get(0); 423 424 if (content instanceof VersionAwareAmetysObject) 425 { 426 VersionAwareAmetysObject vaContent = (VersionAwareAmetysObject) content; 427 String currentRevision = vaContent.getRevision(); 428 429 if (currentRevision != null) 430 { 431 432 String[] allRevisions = vaContent.getAllRevisions(); 433 int currentRevIndex = ArrayUtils.indexOf(allRevisions, currentRevision); 434 435 if (currentRevIndex > -1 && currentRevIndex < (allRevisions.length - 1)) 436 { 437 String nextRevision = allRevisions[currentRevIndex + 1]; 438 439 Date currentRevTimestamp = vaContent.getRevisionTimestamp(); 440 Date nextRevTimestamp = vaContent.getRevisionTimestamp(nextRevision); 441 442 // Get all steps between the two revisions. 443 List<Step> steps = _worklflowHelper.getStepsBetween(workflow, workflowId, currentRevTimestamp, nextRevTimestamp); 444 445 // In the old workflow structure 446 // We take the second, which is current revision's last step. 447 if (steps.size() > 0 && steps.get(0) instanceof AmetysStep) 448 { 449 AmetysStep amStep = (AmetysStep) steps.get(0); 450 if (amStep.getProperty("actionFinishDate") != null) 451 { 452 // New workflow structure detected: cut the first workflow step 453 // in the list, as it belongs to the next version. 454 steps = steps.subList(1, steps.size()); 455 } 456 } 457 458 // Order by step descendant. 459 Collections.sort(steps, new Comparator<Step>() 460 { 461 public int compare(Step step1, Step step2) 462 { 463 return -Long.valueOf(step1.getId()).compareTo(step2.getId()); 464 } 465 }); 466 467 // The first step in the list is the current version's last workflow step. 468 if (steps.size() > 0) 469 { 470 currentStep = steps.get(0); 471 } 472 } 473 } 474 } 475 476 return currentStep; 477 } 478 479 /** 480 * Generates SAX events for the content's language. 481 * @param content the {@link Content}. 482 * @param contentHandler the ContentHandler receving SAX events. 483 * @throws SAXException if an error occurs during the SAX events generation. 484 */ 485 protected void saxLanguage(Content content, ContentHandler contentHandler) throws SAXException 486 { 487 String code = content.getLanguage(); 488 if (code != null) 489 { 490 Language language = _languageManager.getLanguage(code); 491 492 AttributesImpl atts = new AttributesImpl(); 493 atts.addCDATAAttribute("code", code); 494 495 if (language != null) 496 { 497 atts.addCDATAAttribute("icon-small", language.getSmallIcon()); 498 atts.addCDATAAttribute("icon-medium", language.getMediumIcon()); 499 atts.addCDATAAttribute("icon-large", language.getLargeIcon()); 500 } 501 502 XMLUtils.startElement(contentHandler, "content-language", atts); 503 if (language != null) 504 { 505 language.getLabel().toSAX(contentHandler); 506 } 507 XMLUtils.endElement(contentHandler, "content-language"); 508 } 509 } 510 511 /** 512 * Generates SAX events for the DC metadata. 513 * @param dcObject the {@link Content}. 514 * @param contentHandler the ContentHandler receving SAX events. 515 * @throws SAXException if an error occurs during the SAX events generation. 516 */ 517 protected void saxDublinCoreMetadata(DublinCoreAwareAmetysObject dcObject, ContentHandler contentHandler) throws SAXException 518 { 519 XMLUtils.startElement(contentHandler, "dublin-core-metadata"); 520 saxIfNotNull("title", dcObject.getDCTitle(), contentHandler); 521 saxIfNotNull("creator", dcObject.getDCCreator(), contentHandler); 522 saxIfNotNull("subject", dcObject.getDCSubject(), contentHandler); 523 saxIfNotNull("description", dcObject.getDCDescription(), contentHandler); 524 saxIfNotNull("publisher", dcObject.getDCPublisher(), contentHandler); 525 saxIfNotNull("contributor", dcObject.getDCContributor(), contentHandler); 526 saxIfNotNull("date", dcObject.getDCDate(), contentHandler); 527 saxIfNotNull("type", dcObject.getDCType(), contentHandler); 528 saxIfNotNull("format", dcObject.getDCFormat(), contentHandler); 529 saxIfNotNull("identifier", dcObject.getDCIdentifier(), contentHandler); 530 saxIfNotNull("source", dcObject.getDCSource(), contentHandler); 531 saxIfNotNull("language", dcObject.getDCLanguage(), contentHandler); 532 saxIfNotNull("relation", dcObject.getDCRelation(), contentHandler); 533 saxIfNotNull("coverage", dcObject.getDCCoverage(), contentHandler); 534 saxIfNotNull("rights", dcObject.getDCRights(), contentHandler); 535 XMLUtils.endElement(contentHandler, "dublin-core-metadata"); 536 } 537 538 /** 539 * Send a value if not null. 540 * @param name the tag name. 541 * @param value the value. 542 * @param contentHandler the ContentHandler receving SAX events. 543 * @throws SAXException if an error occurs during the SAX events generation. 544 */ 545 protected void saxIfNotNull(String name, String value, ContentHandler contentHandler) throws SAXException 546 { 547 if (value != null) 548 { 549 XMLUtils.createElement(contentHandler, name, value); 550 } 551 } 552 553 /** 554 * Send values if not null. 555 * @param name the tag name. 556 * @param values the values. 557 * @param contentHandler the ContentHandler receving SAX events. 558 * @throws SAXException if an error occurs during the SAX events generation. 559 */ 560 protected void saxIfNotNull(String name, String[] values, ContentHandler contentHandler) throws SAXException 561 { 562 if (values != null) 563 { 564 for (String value : values) 565 { 566 XMLUtils.createElement(contentHandler, name, value); 567 } 568 } 569 } 570 571 /** 572 * Send a value if not null. 573 * @param name the tag name. 574 * @param value the value. 575 * @param contentHandler the ContentHandler receving SAX events. 576 * @throws SAXException if an error occurs during the SAX events generation. 577 */ 578 protected void saxIfNotNull(String name, Date value, ContentHandler contentHandler) throws SAXException 579 { 580 if (value != null) 581 { 582 LocalDate ld = DateUtils.asLocalDate(value); 583 XMLUtils.createElement(contentHandler, name, ld.format(DateTimeFormatter.ISO_LOCAL_DATE)); 584 } 585 } 586 587 /** 588 * Generates SAX events for content's comments. 589 * @param content the {@link Content}. 590 * @param contentHandler the ContentHandler receving SAX events. 591 * @throws SAXException if an error occurs during the SAX events generation. 592 */ 593 protected void saxContentComments(CommentableContent content, ContentHandler contentHandler) throws SAXException 594 { 595 List<Comment> comments = content.getComments(false, true); 596 597 if (comments.size() > 0) 598 { 599 XMLUtils.startElement(contentHandler, "comments"); 600 for (Comment comment : comments) 601 { 602 _commentsDAO.saxComment(contentHandler, comment, 0); 603 } 604 XMLUtils.endElement(contentHandler, "comments"); 605 } 606 } 607 608 /** 609 * Add attribute if value is not null 610 * @param attrs The attributes 611 * @param name The name of attribute 612 * @param value The value 613 */ 614 protected void addAttributeIfNotNull (AttributesImpl attrs, String name, String value) 615 { 616 if (value != null) 617 { 618 attrs.addCDATAAttribute(name, value); 619 } 620 } 621}