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.io.IOException; 019import java.time.LocalDate; 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.dateToString(content.getCreationDate())); 223 attrs.addCDATAAttribute("creator", UserIdentity.userIdentityToString(content.getCreator())); 224 attrs.addCDATAAttribute("lastModifiedAt", DateUtils.dateToString(content.getLastModified())); 225 226 Date lastValidatedAt = content.getLastValidationDate(); 227 if (lastValidatedAt != null) 228 { 229 attrs.addCDATAAttribute("lastValidatedAt", DateUtils.dateToString(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 try 321 { 322 XMLUtils.startElement(contentHandler, attributesTagName); 323 324 RepositoryDataContext context = RepositoryDataContext.newInstance(); 325 326 if (view == null) 327 { 328 context.withLocale(locale).withEmptyValues(false); 329 content.dataToSAX(contentHandler, context); 330 } 331 else 332 { 333 if (isEdition) 334 { 335 Set<String> externalizableData = _externalizableDataProviderEP.getExternalizableDataPaths(content); 336 content.dataToSAXForEdition(contentHandler, view, context.withExternalizableData(externalizableData)); 337 } 338 else 339 { 340 content.dataToSAX(contentHandler, view, context.withLocale(locale).withEmptyValues(false)); 341 } 342 } 343 344 XMLUtils.endElement(contentHandler, attributesTagName); 345 } 346 catch (IOException ex) 347 { 348 throw new RuntimeException(ex); 349 } 350 } 351 352 /** 353 * Generates SAX events representing the current workflow step. 354 * @param content the {@link Content}. 355 * @param contentHandler the ContentHandler receiving SAX events. 356 * @param saxWorkflowInfo if true, also produces SAX events for detailed information about the current workflow step. 357 * @throws SAXException if an error occurs during the SAX events generation. 358 */ 359 protected void saxWorkflowStep(Content content, ContentHandler contentHandler, boolean saxWorkflowInfo) throws SAXException 360 { 361 if (content instanceof WorkflowAwareContent) 362 { 363 WorkflowAwareContent waContent = (WorkflowAwareContent) content; 364 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(waContent); 365 366 try 367 { 368 long workflowId = waContent.getWorkflowId(); 369 String workflowName = workflow.getWorkflowName(workflowId); 370 371 Step currentStep = getCurrentStep(waContent, workflow); 372 373 int currentStepId = currentStep.getStepId(); 374 375 I18nizableText workflowStepName = new I18nizableText("application", _worklflowHelper.getStepName(workflowName, currentStepId)); 376 377 AttributesImpl atts = new AttributesImpl(); 378 atts.addAttribute("", "id", "id", "CDATA", String.valueOf(currentStepId)); 379 atts.addAttribute("", "workflowName", "workflowName", "CDATA", String.valueOf(workflowName)); 380 381 if (saxWorkflowInfo) 382 { 383 if ("application".equals(workflowStepName.getCatalogue())) 384 { 385 atts.addAttribute("", "icon-small", "icon-small", "CDATA", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-small.png"); 386 atts.addAttribute("", "icon-medium", "icon-medium", "CDATA", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-medium.png"); 387 atts.addAttribute("", "icon-large", "icon-large", "CDATA", "/plugins/cms/resources_workflow/" + workflowStepName.getKey() + "-large.png"); 388 } 389 else 390 { 391 String pluginName = workflowStepName.getCatalogue().substring("plugin.".length()); 392 atts.addAttribute("", "icon-small", "icon-small", "CDATA", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-small.png"); 393 atts.addAttribute("", "icon-medium", "icon-medium", "CDATA", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-medium.png"); 394 atts.addAttribute("", "icon-large", "icon-large", "CDATA", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-large.png"); 395 } 396 } 397 398 XMLUtils.startElement(contentHandler, "workflow-step", atts); 399 400 if (saxWorkflowInfo) 401 { 402 workflowStepName.toSAX(contentHandler); 403 } 404 405 XMLUtils.endElement(contentHandler, "workflow-step"); 406 } 407 catch (AmetysRepositoryException e) 408 { 409 // Current step id was not positioned 410 } 411 catch (WorkflowException e) 412 { 413 // Ignore, just don't SAX the workflow step. 414 } 415 } 416 } 417 418 /** 419 * Get the current workflow step of the content. 420 * @param content the {@link Content}. 421 * @param workflow the associated workflow. 422 * @return the current step 423 * @throws WorkflowException if somethng got wrong processing workflow data. 424 */ 425 protected Step getCurrentStep(WorkflowAwareContent content, AmetysObjectWorkflow workflow) throws WorkflowException 426 { 427 long workflowId = content.getWorkflowId(); 428 429 Step currentStep = (Step) workflow.getCurrentSteps(workflowId).get(0); 430 431 if (content instanceof VersionAwareAmetysObject) 432 { 433 VersionAwareAmetysObject vaContent = (VersionAwareAmetysObject) content; 434 String currentRevision = vaContent.getRevision(); 435 436 if (currentRevision != null) 437 { 438 439 String[] allRevisions = vaContent.getAllRevisions(); 440 int currentRevIndex = ArrayUtils.indexOf(allRevisions, currentRevision); 441 442 if (currentRevIndex > -1 && currentRevIndex < (allRevisions.length - 1)) 443 { 444 String nextRevision = allRevisions[currentRevIndex + 1]; 445 446 Date currentRevTimestamp = vaContent.getRevisionTimestamp(); 447 Date nextRevTimestamp = vaContent.getRevisionTimestamp(nextRevision); 448 449 // Get all steps between the two revisions. 450 List<Step> steps = _worklflowHelper.getStepsBetween(workflow, workflowId, currentRevTimestamp, nextRevTimestamp); 451 452 // In the old workflow structure 453 // We take the second, which is current revision's last step. 454 if (steps.size() > 0 && steps.get(0) instanceof AmetysStep) 455 { 456 AmetysStep amStep = (AmetysStep) steps.get(0); 457 if (amStep.getProperty("actionFinishDate") != null) 458 { 459 // New workflow structure detected: cut the first workflow step 460 // in the list, as it belongs to the next version. 461 steps = steps.subList(1, steps.size()); 462 } 463 } 464 465 // Order by step descendant. 466 Collections.sort(steps, new Comparator<Step>() 467 { 468 public int compare(Step step1, Step step2) 469 { 470 return -Long.valueOf(step1.getId()).compareTo(step2.getId()); 471 } 472 }); 473 474 // The first step in the list is the current version's last workflow step. 475 if (steps.size() > 0) 476 { 477 currentStep = steps.get(0); 478 } 479 } 480 } 481 } 482 483 return currentStep; 484 } 485 486 /** 487 * Generates SAX events for the content's language. 488 * @param content the {@link Content}. 489 * @param contentHandler the ContentHandler receving SAX events. 490 * @throws SAXException if an error occurs during the SAX events generation. 491 */ 492 protected void saxLanguage(Content content, ContentHandler contentHandler) throws SAXException 493 { 494 String code = content.getLanguage(); 495 if (code != null) 496 { 497 Language language = _languageManager.getLanguage(code); 498 499 AttributesImpl atts = new AttributesImpl(); 500 atts.addCDATAAttribute("code", code); 501 502 if (language != null) 503 { 504 atts.addCDATAAttribute("icon-small", language.getSmallIcon()); 505 atts.addCDATAAttribute("icon-medium", language.getMediumIcon()); 506 atts.addCDATAAttribute("icon-large", language.getLargeIcon()); 507 } 508 509 XMLUtils.startElement(contentHandler, "content-language", atts); 510 if (language != null) 511 { 512 language.getLabel().toSAX(contentHandler); 513 } 514 XMLUtils.endElement(contentHandler, "content-language"); 515 } 516 } 517 518 /** 519 * Generates SAX events for the DC metadata. 520 * @param dcObject the {@link Content}. 521 * @param contentHandler the ContentHandler receving SAX events. 522 * @throws SAXException if an error occurs during the SAX events generation. 523 */ 524 protected void saxDublinCoreMetadata(DublinCoreAwareAmetysObject dcObject, ContentHandler contentHandler) throws SAXException 525 { 526 XMLUtils.startElement(contentHandler, "dublin-core-metadata"); 527 saxIfNotNull("title", dcObject.getDCTitle(), contentHandler); 528 saxIfNotNull("creator", dcObject.getDCCreator(), contentHandler); 529 saxIfNotNull("subject", dcObject.getDCSubject(), contentHandler); 530 saxIfNotNull("description", dcObject.getDCDescription(), contentHandler); 531 saxIfNotNull("publisher", dcObject.getDCPublisher(), contentHandler); 532 saxIfNotNull("contributor", dcObject.getDCContributor(), contentHandler); 533 saxIfNotNull("date", dcObject.getDCDate(), contentHandler); 534 saxIfNotNull("type", dcObject.getDCType(), contentHandler); 535 saxIfNotNull("format", dcObject.getDCFormat(), contentHandler); 536 saxIfNotNull("identifier", dcObject.getDCIdentifier(), contentHandler); 537 saxIfNotNull("source", dcObject.getDCSource(), contentHandler); 538 saxIfNotNull("language", dcObject.getDCLanguage(), contentHandler); 539 saxIfNotNull("relation", dcObject.getDCRelation(), contentHandler); 540 saxIfNotNull("coverage", dcObject.getDCCoverage(), contentHandler); 541 saxIfNotNull("rights", dcObject.getDCRights(), contentHandler); 542 XMLUtils.endElement(contentHandler, "dublin-core-metadata"); 543 } 544 545 /** 546 * Send a value if not null. 547 * @param name the tag name. 548 * @param value the value. 549 * @param contentHandler the ContentHandler receving SAX events. 550 * @throws SAXException if an error occurs during the SAX events generation. 551 */ 552 protected void saxIfNotNull(String name, String value, ContentHandler contentHandler) throws SAXException 553 { 554 if (value != null) 555 { 556 XMLUtils.createElement(contentHandler, name, value); 557 } 558 } 559 560 /** 561 * Send values if not null. 562 * @param name the tag name. 563 * @param values the values. 564 * @param contentHandler the ContentHandler receving SAX events. 565 * @throws SAXException if an error occurs during the SAX events generation. 566 */ 567 protected void saxIfNotNull(String name, String[] values, ContentHandler contentHandler) throws SAXException 568 { 569 if (values != null) 570 { 571 for (String value : values) 572 { 573 XMLUtils.createElement(contentHandler, name, value); 574 } 575 } 576 } 577 578 /** 579 * Send a value if not null. 580 * @param name the tag name. 581 * @param value the value. 582 * @param contentHandler the ContentHandler receving SAX events. 583 * @throws SAXException if an error occurs during the SAX events generation. 584 */ 585 protected void saxIfNotNull(String name, Date value, ContentHandler contentHandler) throws SAXException 586 { 587 if (value != null) 588 { 589 LocalDate ld = DateUtils.asLocalDate(value); 590 XMLUtils.createElement(contentHandler, name, ld.format(DateTimeFormatter.ISO_LOCAL_DATE)); 591 } 592 } 593 594 /** 595 * Generates SAX events for content's comments. 596 * @param content the {@link Content}. 597 * @param contentHandler the ContentHandler receving SAX events. 598 * @throws SAXException if an error occurs during the SAX events generation. 599 */ 600 protected void saxContentComments(CommentableContent content, ContentHandler contentHandler) throws SAXException 601 { 602 List<Comment> comments = content.getComments(false, true); 603 604 if (comments.size() > 0) 605 { 606 XMLUtils.startElement(contentHandler, "comments"); 607 for (Comment comment : comments) 608 { 609 _commentsDAO.saxComment(contentHandler, comment, 0); 610 } 611 XMLUtils.endElement(contentHandler, "comments"); 612 } 613 } 614 615 /** 616 * Add attribute if value is not null 617 * @param attrs The attributes 618 * @param name The name of attribute 619 * @param value The value 620 */ 621 protected void addAttributeIfNotNull (AttributesImpl attrs, String name, String value) 622 { 623 if (value != null) 624 { 625 attrs.addCDATAAttribute(name, value); 626 } 627 } 628}