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.data.type; 017 018import java.io.ByteArrayInputStream; 019import java.io.IOException; 020import java.io.InputStream; 021import java.time.ZoneId; 022import java.time.ZonedDateTime; 023import java.time.format.DateTimeFormatter; 024import java.util.Calendar; 025import java.util.GregorianCalendar; 026import java.util.LinkedHashMap; 027import java.util.Map; 028import java.util.Optional; 029import java.util.function.Function; 030import java.util.stream.Stream; 031 032import javax.xml.transform.TransformerException; 033 034import org.apache.cocoon.environment.Context; 035import org.apache.cocoon.xml.AttributesImpl; 036import org.apache.cocoon.xml.XMLUtils; 037import org.apache.commons.io.IOUtils; 038import org.apache.commons.lang3.StringUtils; 039import org.apache.commons.lang3.tuple.ImmutableTriple; 040import org.apache.commons.lang3.tuple.Triple; 041import org.w3c.dom.Element; 042import org.xml.sax.ContentHandler; 043import org.xml.sax.SAXException; 044 045import org.ametys.cms.data.Binary; 046import org.ametys.cms.data.File; 047import org.ametys.cms.data.NamedResource; 048import org.ametys.cms.data.Resource; 049import org.ametys.cms.data.RichText; 050import org.ametys.cms.transformation.xslt.ResolveURIComponent; 051import org.ametys.core.model.type.ModelItemTypeHelper; 052import org.ametys.core.upload.Upload; 053import org.ametys.core.util.DateUtils; 054import org.ametys.plugins.repository.AmetysRepositoryException; 055import org.ametys.plugins.repository.RepositoryConstants; 056import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData; 057import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 058import org.ametys.runtime.model.compare.DataChangeType; 059import org.ametys.runtime.model.compare.DataChangeTypeDetail; 060import org.ametys.runtime.model.exception.BadItemTypeException; 061import org.ametys.runtime.model.type.DataContext; 062 063/** 064 * Helper for resource type of elements stored in the repository 065 */ 066public final class ResourceElementTypeHelper 067{ 068 /** Prefix to use for resource's metadata */ 069 public static final String METADATA_PREFIX = "jcr"; 070 071 /** Mime type metadata identifier */ 072 public static final String MIME_TYPE_IDENTIFIER = "mimeType"; 073 074 /** Encoding metadata identifier */ 075 public static final String ENCODING_IDENTIFIER = "encoding"; 076 077 /** Last modification date metadata identifier */ 078 public static final String LAST_MODIFICATION_DATE_IDENTIFIER = "lastModified"; 079 080 /** Identifier of the data containing the resource's data */ 081 public static final String DATA_IDENTIFIER = "data"; 082 083 /** hash metadata identifier */ 084 public static final String HASH_IDENTIFIER = "hash"; 085 086 /** file name metadata identifier */ 087 public static final String FILENAME_IDENTIFIER = "filename"; 088 089 /** Identifier of the data to check to know if the resource is empty */ 090 public static final String EMPTY_RESOURCE_IDENTIFIER = "isEmpty"; 091 092 private ResourceElementTypeHelper() 093 { 094 // Empty constructor 095 } 096 097 /** 098 * Convert the single binary into a JSON object 099 * @param binary the binary to convert 100 * @param binaryType the type of the binary 101 * @param context The context of the binary to convert 102 * @return The file as JSON 103 */ 104 public static Map<String, Object> singleBinaryToJSON(Binary binary, String binaryType, DataContext context) 105 { 106 return ResourceElementTypeHelper.singleFileToJSON(binary, binaryType, context.getDataPath(), _getBinaryURI(context)); 107 } 108 109 /** 110 * Convert the single file into a JSON object 111 * @param file the file to convert 112 * @param fileType the type of the file 113 * @param path the path of file (ex: data path for a binary or resource id for an explorer file) 114 * @param fileURI uri of the file to give to the {@link ResolveURIComponent} 115 * @return The file as JSON 116 */ 117 public static Map<String, Object> singleFileToJSON(File file, String fileType, String path, String fileURI) 118 { 119 Map<String, Object> fileInfos = new LinkedHashMap<>(); 120 121 Optional.ofNullable(file.getMimeType()).ifPresent(mimeType -> fileInfos.put("mimeType", mimeType)); 122 Optional.ofNullable(file.getLastModificationDate()).ifPresent(lastModificationDate -> fileInfos.put("lastModified", lastModificationDate.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))); 123 124 fileInfos.put("size", String.valueOf(file.getLength())); 125 if (path != null) 126 { 127 fileInfos.put("path", path); 128 } 129 fileInfos.put("type", fileType); 130 Optional.ofNullable(file.getName()).ifPresent(filename -> fileInfos.put("filename", filename)); 131 132 String viewUrl = ResolveURIComponent.resolveBoundedImage(fileType, fileURI, 100, 100); 133 String downloadUrl = ResolveURIComponent.resolve(fileType, fileURI, true); 134 135 fileInfos.put("viewUrl", viewUrl); 136 fileInfos.put("downloadUrl", downloadUrl); 137 138 return fileInfos; 139 } 140 141 /** 142 * Sets the given resource's data using the given DOM element and additional data 143 * @param resource the resource 144 * @param element the DOM element 145 * @param additionalData additional data containing the input stream 146 * @param context the Cocoon's context, used to retrieve mime types from file names if needed 147 * @throws TransformerException if an error occurs while parsing the DOM node 148 * @throws IOException if an error occurs reading the resource's input stream 149 */ 150 public static void resourceFromXML(NamedResource resource, Element element, Optional<Object> additionalData, Context context) throws TransformerException, IOException 151 { 152 @SuppressWarnings("unchecked") 153 Map<String, InputStream> files = additionalData.isPresent() ? (Map<String, InputStream>) additionalData.get() : Map.of(); 154 155 String lastModified = element.getAttribute("lastModified"); 156 ZonedDateTime time = StringUtils.isEmpty(lastModified) ? ZonedDateTime.now() : ZonedDateTime.parse(lastModified, DateTimeFormatter.ISO_OFFSET_DATE_TIME); 157 resource.setLastModificationDate(time); 158 159 String filename = element.getAttribute("filename"); 160 resource.setFilename(filename); 161 162 String mimeType = element.getAttribute("mime-type"); 163 if (StringUtils.isEmpty(mimeType)) 164 { 165 mimeType = context.getMimeType(filename.toLowerCase()); 166 167 if (mimeType == null) 168 { 169 mimeType = "application/unknown"; 170 } 171 } 172 173 resource.setMimeType(mimeType); 174 175 if (files.containsKey(filename)) 176 { 177 InputStream inputStream = files.get(filename); 178 resource.setInputStream(inputStream); 179 } 180 } 181 182 /** 183 * Converts an uploaded file to a {@link Binary}. 184 * @param upload the uploaded file. 185 * @return the newly created {@link Binary}. 186 */ 187 public static Binary binaryFromUpload(Upload upload) 188 { 189 Binary binary = new Binary(); 190 191 binary.setFilename(upload.getFilename()); 192 binary.setMimeType(upload.getMimeType()); 193 binary.setLastModificationDate(upload.getUploadedDate()); 194 195 try 196 { 197 binary.setInputStream(upload.getInputStream()); 198 } 199 catch (IOException e) 200 { 201 throw new RuntimeException("Unable to set binary data", e); 202 } 203 204 return binary; 205 } 206 207 /** 208 * Generates SAX events for the given single binary 209 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 210 * @param tagName the tag name of the SAX event to generate. 211 * @param binary the single file to SAX 212 * @param binaryType the type of the file 213 * @param context The context of the binary to convert 214 * @param attributes the attributes for the SAX event to generate 215 * @throws SAXException if an error occurs during the SAX events generation 216 */ 217 public static void singleBinaryToSAX(ContentHandler contentHandler, String tagName, Binary binary, String binaryType, DataContext context, AttributesImpl attributes) throws SAXException 218 { 219 singleFileToSAX(contentHandler, tagName, binary, binaryType, context.getDataPath(), _getBinaryURI(context), attributes); 220 } 221 222 private static String _getBinaryURI(DataContext context) 223 { 224 String dataPath = context.getDataPath(); 225 String uri = context.getObjectId() 226 .map(id -> dataPath + "?objectId=" + id) 227 .orElse(dataPath); 228 return uri; 229 } 230 231 /** 232 * Generates SAX events for the given single file 233 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 234 * @param tagName the tag name of the SAX event to generate. 235 * @param file the single file to SAX 236 * @param fileType the type of the file 237 * @param path the path of file (ex: data path for a binary or resource id for an explorer file) 238 * @param fileURI uri of the file 239 * @param attributes the attributes for the SAX event to generate 240 * @throws SAXException if an error occurs during the SAX events generation 241 */ 242 public static void singleFileToSAX(ContentHandler contentHandler, String tagName, File file, String fileType, String path, String fileURI, AttributesImpl attributes) throws SAXException 243 { 244 AttributesImpl localAttributes = new AttributesImpl(attributes); 245 246 Optional.ofNullable(file.getMimeType()) 247 .ifPresent(mimeType -> localAttributes.addCDATAAttribute("mime-type", mimeType)); 248 249 Optional.ofNullable(file.getLastModificationDate()) 250 .map(DateUtils::zonedDateTimeToString) 251 .ifPresent(lastModified -> localAttributes.addCDATAAttribute("lastModified", lastModified)); 252 253 Optional.ofNullable(file.getName()) 254 .ifPresent(filename -> localAttributes.addCDATAAttribute("filename", filename)); 255 256 localAttributes.addCDATAAttribute("type", fileType); 257 localAttributes.addCDATAAttribute("size", String.valueOf(file.getLength())); 258 localAttributes.addCDATAAttribute("path", path); 259 localAttributes.addCDATAAttribute("uri", fileURI); 260 261 XMLUtils.createElement(contentHandler, tagName, localAttributes); 262 } 263 264 /** 265 * Checks if the given resource data is empty. A resource data is considered as empty if it has no stream data 266 * @param resourceData the resource data to check 267 * @return <code>true</code> if the resource has is empty, <code>false</code> otherwise 268 */ 269 public static boolean isResourceDataEmpty(RepositoryData resourceData) 270 { 271 if (resourceData.hasValue(EMPTY_RESOURCE_IDENTIFIER, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL)) 272 { 273 return resourceData.getBoolean(EMPTY_RESOURCE_IDENTIFIER, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL); 274 } 275 else 276 { 277 return false; 278 } 279 } 280 281 /** 282 * Read the binary from the given repository data 283 * @param binaryData the repository data containing the binary's data 284 * @return the read binary 285 */ 286 public static Binary readBinaryData(RepositoryData binaryData) 287 { 288 String hash = getStringValue(binaryData, HASH_IDENTIFIER, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL); 289 Binary binary = new Binary(binaryData, hash); 290 291 readResourceData(binaryData, binary); 292 Optional.ofNullable(getStringValue(binaryData, FILENAME_IDENTIFIER, RepositoryConstants.NAMESPACE_PREFIX)).ifPresent(binary::setFilename); 293 294 return binary; 295 } 296 297 /** 298 * Read the resource from the given repository data 299 * @param resourceData the repository data containing the resource's data 300 * @param resource the resource to read 301 */ 302 public static void readResourceData(RepositoryData resourceData, Resource resource) 303 { 304 Optional.ofNullable(getStringValue(resourceData, MIME_TYPE_IDENTIFIER, METADATA_PREFIX)).ifPresent(resource::setMimeType); 305 Optional.ofNullable(getStringValue(resourceData, ENCODING_IDENTIFIER, METADATA_PREFIX)).ifPresent(resource::setEncoding); 306 Optional.ofNullable(getDateValue(resourceData, LAST_MODIFICATION_DATE_IDENTIFIER, METADATA_PREFIX)).ifPresent(resource::setLastModificationDate); 307 } 308 309 /** 310 * Empties the resource data with the given name. A resource data is considered as empty if it has no stream data 311 * @param parentResourceData the parent of the resource data to empty 312 * @param name the name of the resource data 313 * @param nodeType the node type of the resource data 314 */ 315 public static void emptyResourceData(ModifiableRepositoryData parentResourceData, String name, String nodeType) 316 { 317 ModifiableRepositoryData resourceData = parentResourceData.hasValue(name) ? parentResourceData.getRepositoryData(name) : parentResourceData.addRepositoryData(name, nodeType); 318 319 try (ByteArrayInputStream is = new ByteArrayInputStream(StringUtils.EMPTY.getBytes())) 320 { 321 resourceData.setValue(DATA_IDENTIFIER, is, METADATA_PREFIX); 322 } 323 catch (IOException e) 324 { 325 throw new AmetysRepositoryException(e); 326 } 327 328 resourceData.setValue(EMPTY_RESOURCE_IDENTIFIER, true, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL); 329 } 330 331 /** 332 * Write the resource in the given repository data 333 * @param parentData the repository data where to write the binary 334 * @param name the name of the element to write 335 * @param value the binary to write 336 */ 337 public static void writeSingleBinaryValue(ModifiableRepositoryData parentData, String name, Binary value) 338 { 339 ModifiableRepositoryData binaryData; 340 if (parentData.hasValue(name)) 341 { 342 binaryData = getRepositoryData(parentData, RepositoryConstants.BINARY_NODETYPE, name, RepositoryConstants.NAMESPACE_PREFIX); 343 } 344 else 345 { 346 binaryData = parentData.addRepositoryData(name, RepositoryConstants.BINARY_NODETYPE); 347 } 348 349 if (value != null) 350 { 351 binaryData.setValue(EMPTY_RESOURCE_IDENTIFIER, false, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL); 352 353 writeResourceData(binaryData, value); 354 Optional.ofNullable(value.getFilename()).ifPresent(filename -> binaryData.setValue(FILENAME_IDENTIFIER, filename, RepositoryConstants.NAMESPACE_PREFIX)); 355 356 if (binaryData.hasValue(HASH_IDENTIFIER, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL)) 357 { 358 String currentHash = getStringValue(binaryData, HASH_IDENTIFIER, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL); 359 if (!currentHash.equals(value.getHash())) 360 { 361 Optional.ofNullable(value.getHash()).ifPresent(hash -> binaryData.setValue(HASH_IDENTIFIER, hash, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL)); 362 Optional.ofNullable(value.getInputStream()).ifPresent(stream -> binaryData.setValue(DATA_IDENTIFIER, stream, METADATA_PREFIX)); 363 } 364 } 365 else 366 { 367 Optional.ofNullable(value.getHash()).ifPresent(hash -> binaryData.setValue(HASH_IDENTIFIER, hash, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL)); 368 Optional.ofNullable(value.getInputStream()).ifPresent(stream -> binaryData.setValue(DATA_IDENTIFIER, stream, METADATA_PREFIX)); 369 } 370 } 371 } 372 373 /** 374 * Write the resource in the given repository data 375 * @param resourceData the repository data where to write the resource's data 376 * @param value the resource to write 377 */ 378 public static void writeResourceData(ModifiableRepositoryData resourceData, Resource value) 379 { 380 Optional.ofNullable(value.getMimeType()).ifPresent(mimeType -> resourceData.setValue(MIME_TYPE_IDENTIFIER, mimeType, METADATA_PREFIX)); 381 Optional.ofNullable(value.getEncoding()).ifPresent(encoding -> resourceData.setValue(ENCODING_IDENTIFIER, encoding, METADATA_PREFIX)); 382 Optional.ofNullable(value.getLastModificationDate()).ifPresent(lastModificationDate -> resourceData.setValue(LAST_MODIFICATION_DATE_IDENTIFIER, _getCalendarFromZonedDateTime(lastModificationDate), METADATA_PREFIX)); 383 } 384 385 /** 386 * Compare the given single binaries and retrieves the changes as a stream of {@link Triple}s. The {@link Triple} contains: 387 * <ul> 388 * <li>the general type of the change (added, modified or removed) as a {@link DataChangeType},</li> 389 * <li>some details about this change if possible (after or before for a date, more or less for a number, ...) as a {@link DataChangeTypeDetail}</li> 390 * <li>The data concerned by this change if not the element itself (or an empty String)</li> 391 * </ul> 392 * @param binary1 the 1st single binary 393 * @param binary2 the 2nd single binary 394 * @return the changes between the two given single binaries as a stream of {@link Triple}s. Retrieves an empty stream if there is no change 395 * @throws IOException if an error occurs while reading the binaries' data 396 */ 397 public static Stream<Triple<DataChangeType, DataChangeTypeDetail, String>> compareSingleBinaries(Binary binary1, Binary binary2) throws IOException 398 { 399 return Stream.of 400 ( 401 // Get changes about the binary's metadata 402 _compareSingleResourcesMetadata(binary1, binary2), 403 404 // Get changes about the binary's file name 405 ModelItemTypeHelper.compareSingleObjects(binary1.getFilename(), binary2.getFilename(), "fileName") 406 .map(Stream::of) 407 .orElseGet(Stream::empty), 408 409 // Get changes about the binary's content 410 _compareSingleResourcesContent(binary1, binary2) 411 ) 412 .flatMap(Function.identity()); 413 } 414 415 /** 416 * Compare the given single rich texts and retrieves the changes as a stream of {@link Triple}s. The {@link Triple} contains: 417 * <ul> 418 * <li>the general type of the change (added, modified or removed) as a {@link DataChangeType},</li> 419 * <li>some details about this change if possible (after or before for a date, more or less for a number, ...) as a {@link DataChangeTypeDetail}</li> 420 * <li>The data concerned by this change if not the element itself (or an empty String)</li> 421 * </ul> 422 * @param richText1 the 1st single rich text 423 * @param richText2 the 2nd single rich text 424 * @return the changes between the two given single rich texts as a stream of {@link Triple}s. Retrieves an empty stream if there is no change 425 * @throws IOException if an error occurs while reading the rich texts' data 426 */ 427 public static Stream<Triple<DataChangeType, DataChangeTypeDetail, String>> compareSingleRichTexts(RichText richText1, RichText richText2) throws IOException 428 { 429 // Note: folders are not compared, they are used for pictures and id of the pictures are in the InputStream, so if something changed, the InputStream changed. 430 return Stream.of 431 ( 432 _compareSingleResourcesMetadata(richText1, richText2), 433 _compareSingleResourcesContent(richText1, richText2) 434 ) 435 .flatMap(Function.identity()); 436 } 437 438 /** 439 * Compare the metadata of the given single resources and retrieves the changes as a stream of {@link Triple}s. The {@link Triple}s contain: 440 * <ul> 441 * <li>the general type of the change (added, modified or removed) as a {@link DataChangeType},</li> 442 * <li>some details about this change if possible (after or before for a date, more or less for a number, ...) as a {@link DataChangeTypeDetail}</li> 443 * <li>The data concerned by this change if not the element itself (or an empty String)</li> 444 * </ul> 445 * @param resource1 the 1st single resource 446 * @param resource2 the 2nd single resource 447 * @return the changes between the metadata of the two given single resources as a stream of {@link Triple}s. Retrieves an empty stream if there is no change 448 * @throws IOException if an error occurs while reading the resources' data 449 */ 450 protected static Stream<Triple<DataChangeType, DataChangeTypeDetail, String>> _compareSingleResourcesMetadata(Resource resource1, Resource resource2) throws IOException 451 { 452 return Stream.of 453 ( 454 ModelItemTypeHelper.compareSingleObjects(resource1.getEncoding(), resource2.getEncoding(), ENCODING_IDENTIFIER), 455 ModelItemTypeHelper.compareSingleDates(resource1.getLastModificationDate(), resource2.getLastModificationDate(), LAST_MODIFICATION_DATE_IDENTIFIER), 456 ModelItemTypeHelper.compareSingleObjects(resource1.getMimeType(), resource2.getMimeType(), MIME_TYPE_IDENTIFIER) 457 ) 458 .filter(Optional::isPresent) 459 .map(Optional::get); 460 } 461 462 /** 463 * Compare the content of the given single resources and retrieves the changes as a stream of {@link Triple}. The {@link Triple}s contain: 464 * <ul> 465 * <li>the general type of the change (added, modified or removed) as a {@link DataChangeType},</li> 466 * <li>some details about this change if possible (after or before for a date, more or less for a number, ...) as a {@link DataChangeTypeDetail}</li> 467 * <li>The data concerned by this change if not the element itself (or an empty String)</li> 468 * </ul> 469 * @param resource1 the 1st single resource 470 * @param resource2 the 2nd single resource 471 * @return the changes between the content of the two given single resources as a stream of {@link Triple}s. Retrieves an empty stream if there is no change 472 * @throws IOException if an error occurs while reading the resources' data 473 */ 474 protected static Stream<Triple<DataChangeType, DataChangeTypeDetail, String>> _compareSingleResourcesContent(Resource resource1, Resource resource2) throws IOException 475 { 476 try ( 477 InputStream is1 = resource1.getInputStream(); 478 InputStream is2 = resource2.getInputStream(); 479 ) 480 { 481 if (!IOUtils.contentEquals(is1, is2)) 482 { 483 return Stream.of(new ImmutableTriple<>(DataChangeType.MODIFIED, DataChangeTypeDetail.NONE, "inputStream")); 484 } 485 else 486 { 487 return Stream.empty(); 488 } 489 } 490 } 491 492 /** 493 * Retrieves the string value from the given repository data 494 * @param repositoryData the repository data containing the data to retrieve 495 * @param name the name of the data to retrieve 496 * @param prefix the prefix of the data to retrieve 497 * @return the string value 498 */ 499 public static String getStringValue(RepositoryData repositoryData, String name, String prefix) 500 { 501 if (!repositoryData.hasValue(name, prefix)) 502 { 503 return null; 504 } 505 else if (RepositoryData.STRING_REPOSITORY_DATA_TYPE.equals(repositoryData.getType(name, prefix))) 506 { 507 return repositoryData.getString(name, prefix); 508 } 509 else 510 { 511 throw new BadItemTypeException("Try to get string value from the non string data '" + prefix + ":" + name + "' on '" + repositoryData + "'"); 512 } 513 } 514 515 /** 516 * Retrieves the string values from the given repository data 517 * @param repositoryData the repository data containing the data to retrieve 518 * @param name the name of the data to retrieve 519 * @param prefix the prefix of the data to retrieve 520 * @return the string values 521 */ 522 public static String[] getStringValues(RepositoryData repositoryData, String name, String prefix) 523 { 524 if (!repositoryData.hasValue(name, prefix)) 525 { 526 return null; 527 } 528 else if (RepositoryData.STRING_REPOSITORY_DATA_TYPE.equals(repositoryData.getType(name, prefix))) 529 { 530 return repositoryData.getStrings(name, prefix); 531 } 532 else 533 { 534 throw new BadItemTypeException("Try to get string value from the non string data '" + prefix + ":" + name + "' on '" + repositoryData + "'"); 535 } 536 } 537 538 /** 539 * Retrieves the child repository data from the given repository data 540 * @param <T> Type of the repository data (modifiable or not) 541 * @param parentData the repository data containing the data to retrieve 542 * @param dataTypeName the type of the data to retrieve 543 * @param name the name of the data to retrieve 544 * @param prefix the prefix of the data to retrieve 545 * @return the child repository data 546 */ 547 @SuppressWarnings("unchecked") 548 public static <T extends RepositoryData> T getRepositoryData(T parentData, String dataTypeName, String name, String prefix) 549 { 550 if (!parentData.hasValue(name, prefix)) 551 { 552 return null; 553 } 554 else if (dataTypeName.equals(parentData.getType(name, prefix))) 555 { 556 return (T) parentData.getRepositoryData(name, prefix); 557 } 558 else 559 { 560 throw new BadItemTypeException("Try to get data of type '" + dataTypeName + "' from the non data '" + prefix + ":" + name + "' on '" + parentData + "'"); 561 } 562 } 563 564 /** 565 * Retrieves the date value from the given repository data 566 * @param repositoryData the repository data containing data to retrieve 567 * @param prefix the prefix of the data to retrieve 568 * @param name the name of the data to retrieve 569 * @return the date value 570 */ 571 public static ZonedDateTime getDateValue(RepositoryData repositoryData, String name, String prefix) 572 { 573 if (!repositoryData.hasValue(name, prefix)) 574 { 575 return null; 576 } 577 else if (RepositoryData.CALENDAR_REPOSITORY_DATA_TYPE.equals(repositoryData.getType(name, prefix))) 578 { 579 return DateUtils.asZonedDateTime(repositoryData.getDate(name, prefix)); 580 } 581 else 582 { 583 throw new BadItemTypeException("Try to get date value from the non date data '" + prefix + ":" + name + "' on '" + repositoryData + "'"); 584 } 585 } 586 587 private static Calendar _getCalendarFromZonedDateTime(ZonedDateTime zonedDateTime) 588 { 589 ZonedDateTime dateTimeOnUTC = zonedDateTime.withZoneSameInstant(ZoneId.of("UTC")); 590 return GregorianCalendar.from(dateTimeOnUTC); 591 } 592}