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