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