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