001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.core.datatype.adapter; 028 029import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes; 030 031import gov.nist.secauto.metaschema.core.datatype.AbstractCustomJavaDataTypeAdapter; 032import gov.nist.secauto.metaschema.core.datatype.object.Date; 033import gov.nist.secauto.metaschema.core.metapath.function.InvalidValueForCastFunctionException; 034import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; 035import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateItem; 036import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDateTimeItem; 037import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; 038import gov.nist.secauto.metaschema.core.metapath.item.atomic.IUntypedAtomicItem; 039import gov.nist.secauto.metaschema.core.util.ObjectUtils; 040 041import java.time.LocalDate; 042import java.time.LocalTime; 043import java.time.ZoneOffset; 044import java.time.ZonedDateTime; 045import java.time.format.DateTimeParseException; 046import java.time.temporal.TemporalAccessor; 047import java.util.List; 048import java.util.regex.Matcher; 049import java.util.regex.Pattern; 050 051import edu.umd.cs.findbugs.annotations.NonNull; 052 053public class DateAdapter 054 extends AbstractCustomJavaDataTypeAdapter<Date, IDateItem> { 055 @NonNull 056 private static final List<String> NAMES = ObjectUtils.notNull( 057 List.of("date")); 058 private static final Pattern DATE_TIMEZONE = Pattern.compile("^(" 059 + "^(?:(?:2000|2400|2800|(?:19|2[0-9](?:0[48]|[2468][048]|[13579][26])))-02-29)" 060 + "|(?:(?:(?:19|2[0-9])[0-9]{2})-02-(?:0[1-9]|1[0-9]|2[0-8]))" 061 + "|(?:(?:(?:19|2[0-9])[0-9]{2})-(?:0[13578]|10|12)-(?:0[1-9]|[12][0-9]|3[01]))" 062 + "|(?:(?:(?:19|2[0-9])[0-9]{2})-(?:0[469]|11)-(?:0[1-9]|[12][0-9]|30))" 063 + ")" 064 + "(Z|[+-][0-9]{2}:[0-9]{2})?$"); 065 066 DateAdapter() { 067 super(Date.class); 068 } 069 070 @Override 071 public List<String> getNames() { 072 return NAMES; 073 } 074 075 @Override 076 public JsonFormatTypes getJsonRawType() { 077 return JsonFormatTypes.STRING; 078 } 079 080 @Override 081 public Date parse(String value) { 082 Matcher matcher = DATE_TIMEZONE.matcher(value); 083 if (!matcher.matches()) { 084 throw new IllegalArgumentException("Invalid date: " + value); 085 } 086 087 String parseValue 088 = String.format("%sT00:00:00%s", matcher.group(1), matcher.group(2) == null ? "" : matcher.group(2)); 089 try { 090 TemporalAccessor accessor = DateFormats.DATE_TIME_WITH_TZ.parse(parseValue); 091 return new Date(ObjectUtils.notNull(ZonedDateTime.from(accessor)), true); // NOPMD - readability 092 } catch (DateTimeParseException ex) { 093 try { 094 TemporalAccessor accessor = DateFormats.DATE_TIME_WITHOUT_TZ.parse(parseValue); 095 LocalDate date = LocalDate.from(accessor); 096 return new Date(ObjectUtils.notNull(ZonedDateTime.of(date, LocalTime.MIN, ZoneOffset.UTC)), false); 097 } catch (DateTimeParseException ex2) { 098 IllegalArgumentException newEx = new IllegalArgumentException(ex2.getLocalizedMessage(), ex2); 099 newEx.addSuppressed(ex); 100 throw newEx; // NOPMD - false positive 101 } 102 } 103 } 104 105 @Override 106 public String asString(Object obj) { 107 Date value = (Date) obj; 108 String retval; 109 if (value.hasTimeZone()) { 110 @SuppressWarnings("null") 111 @NonNull String formatted = DateFormats.DATE_WITH_TZ.format(value.getValue()); 112 retval = formatted; 113 } else { 114 @SuppressWarnings("null") 115 @NonNull String formatted = DateFormats.DATE_WITHOUT_TZ.format(value.getValue()); 116 retval = formatted; 117 } 118 return retval; 119 } 120 121 @Override 122 public Class<IDateItem> getItemClass() { 123 return IDateItem.class; 124 } 125 126 @Override 127 public IDateItem newItem(Object value) { 128 Date item = toValue(value); 129 return IDateItem.valueOf(item); 130 } 131 132 @Override 133 protected @NonNull IDateItem castInternal(@NonNull IAnyAtomicItem item) { 134 IDateItem retval; 135 if (item instanceof IDateTimeItem) { 136 ZonedDateTime value = ((IDateTimeItem) item).asZonedDateTime(); 137 retval = IDateItem.valueOf(value); 138 } else if (item instanceof IStringItem || item instanceof IUntypedAtomicItem) { 139 retval = super.castInternal(item); 140 } else { 141 throw new InvalidValueForCastFunctionException( 142 String.format("unsupported item type '%s'", item.getClass().getName())); 143 } 144 return retval; 145 } 146 147}