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