• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.health.connect;
18 
19 import static android.health.connect.datatypes.FhirResource.validateFhirResourceType;
20 import static android.health.connect.datatypes.MedicalDataSource.validateMedicalDataSourceIds;
21 import static android.health.connect.internal.datatypes.utils.FhirResourceTypeStringToIntMapper.getFhirResourceTypeInt;
22 
23 import static com.android.healthfitness.flags.Flags.FLAG_PERSONAL_HEALTH_RECORD;
24 
25 import static java.util.Objects.hash;
26 import static java.util.Objects.requireNonNull;
27 
28 import android.annotation.FlaggedApi;
29 import android.annotation.NonNull;
30 import android.health.connect.datatypes.FhirResource;
31 import android.health.connect.datatypes.FhirResource.FhirResourceType;
32 import android.health.connect.datatypes.MedicalDataSource;
33 import android.os.Parcel;
34 import android.os.Parcelable;
35 
36 import java.util.Set;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39 
40 /**
41  * A class to represent a unique identifier of a medical resource.
42  *
43  * <p>This class contains a set of properties that together represent a unique identifier of a
44  * medical resource.
45  *
46  * <p>The medical resource data representation follows the <a href="https://hl7.org/fhir/">Fast
47  * Healthcare Interoperability Resources (FHIR)</a> standard.
48  */
49 @FlaggedApi(FLAG_PERSONAL_HEALTH_RECORD)
50 public final class MedicalResourceId implements Parcelable {
51     @NonNull private final String mDataSourceId;
52     @FhirResourceType private final int mFhirResourceType;
53     @NonNull private final String mFhirResourceId;
54 
55     // Regex of a FHIR resource id is referenced from <a
56     // href="https://build.fhir.org/datatypes.html#id">the official FHIR datatypes</a>.
57     private static final String FHIR_REFERENCE_REGEX = "([A-Za-z]+)/([A-Za-z0-9-.]+)";
58 
59     /**
60      * Constructs a new {@link MedicalResourceId} instance.
61      *
62      * @param dataSourceId The unique identifier of the existing {@link MedicalDataSource},
63      *     representing where the data comes from.
64      * @param fhirResourceType The FHIR resource type. This is the "resourceType" field from a JSON
65      *     representation of FHIR resource data.
66      * @param fhirResourceId The FHIR resource ID. This is the "id" field from a JSON representation
67      *     of FHIR resource data.
68      * @throws IllegalArgumentException if the provided {@code dataSourceId} is not a valid ID, or
69      *     {@code fhirResourceType} is not a valid supported type.
70      */
MedicalResourceId( @onNull String dataSourceId, @FhirResourceType int fhirResourceType, @NonNull String fhirResourceId)71     public MedicalResourceId(
72             @NonNull String dataSourceId,
73             @FhirResourceType int fhirResourceType,
74             @NonNull String fhirResourceId) {
75         requireNonNull(dataSourceId);
76         requireNonNull(fhirResourceId);
77         validateFhirResourceType(fhirResourceType);
78         validateMedicalDataSourceIds(Set.of(dataSourceId));
79         mDataSourceId = dataSourceId;
80         mFhirResourceType = fhirResourceType;
81         mFhirResourceId = fhirResourceId;
82     }
83 
84     /**
85      * Creates a {@link MedicalResourceId} instance from {@code dataSourceId} and {@code
86      * fhirReference}.
87      *
88      * @param dataSourceId The unique identifier of the existing {@link MedicalDataSource},
89      *     representing where the data comes from.
90      * @param fhirReference The FHIR reference string typically extracted from the "reference" field
91      *     in one FHIR resource (source), pointing to another FHIR resource (target) within the same
92      *     data source, for example "Patient/034AB16".
93      * @throws IllegalArgumentException if the provided {@code dataSourceId} is not a valid ID, the
94      *     referenced resource type is not a valid {@link FhirResource} type supported by Health
95      *     Connect, or {@code fhirReference} does not match with the pattern of {@code
96      *     $fhir_resource_type/$fhir_resource_id}, where the FHIR resource type should align with
97      *     the resource list in <a href="https://build.fhir.org/resourcelist.html">the official FHIR
98      *     website</a>, and the FHIR resource ID should also follow the pattern described in <a
99      *     href="https://build.fhir.org/datatypes.html#id">the official FHIR datatypes</a>.
100      */
101     @NonNull
fromFhirReference( @onNull String dataSourceId, @NonNull String fhirReference)102     public static MedicalResourceId fromFhirReference(
103             @NonNull String dataSourceId, @NonNull String fhirReference) {
104         requireNonNull(dataSourceId);
105         requireNonNull(fhirReference);
106         validateMedicalDataSourceIds(Set.of(dataSourceId));
107         Pattern pattern = Pattern.compile(FHIR_REFERENCE_REGEX);
108         Matcher matcher = pattern.matcher(fhirReference);
109         if (!matcher.matches()) {
110             throw new IllegalArgumentException(
111                     "Invalid FHIR reference. Provided "
112                             + fhirReference
113                             + "does not match "
114                             + FHIR_REFERENCE_REGEX);
115         }
116         @FhirResourceType int fhirResourceType = getFhirResourceTypeInt(matcher.group(1));
117         String fhirResourceId = matcher.group(2);
118         return new MedicalResourceId(dataSourceId, fhirResourceType, fhirResourceId);
119     }
120 
121     /**
122      * Constructs this object with the data present in {@code parcel}. It should be in the same
123      * order as {@link MedicalResourceId#writeToParcel}.
124      */
MedicalResourceId(@onNull Parcel in)125     private MedicalResourceId(@NonNull Parcel in) {
126         requireNonNull(in);
127         mDataSourceId = requireNonNull(in.readString());
128         validateMedicalDataSourceIds(Set.of(mDataSourceId));
129         mFhirResourceType = in.readInt();
130         validateFhirResourceType(mFhirResourceType);
131         mFhirResourceId = requireNonNull(in.readString());
132     }
133 
134     @NonNull
135     public static final Creator<MedicalResourceId> CREATOR =
136             new Creator<>() {
137                 /**
138                  * Reading from the {@link Parcel} should have the same order as {@link
139                  * MedicalResourceId#writeToParcel}.
140                  */
141                 @Override
142                 public MedicalResourceId createFromParcel(Parcel in) {
143                     return new MedicalResourceId(in);
144                 }
145 
146                 @Override
147                 public MedicalResourceId[] newArray(int size) {
148                     return new MedicalResourceId[size];
149                 }
150             };
151 
152     /** Returns the unique {@link MedicalDataSource} ID of where the data comes from. */
153     @NonNull
getDataSourceId()154     public String getDataSourceId() {
155         return mDataSourceId;
156     }
157 
158     /** Returns the FHIR resource type. */
159     @FhirResourceType
getFhirResourceType()160     public int getFhirResourceType() {
161         return mFhirResourceType;
162     }
163 
164     /** Returns the FHIR resource ID. */
165     @NonNull
getFhirResourceId()166     public String getFhirResourceId() {
167         return mFhirResourceId;
168     }
169 
170     @Override
describeContents()171     public int describeContents() {
172         return 0;
173     }
174 
175     @Override
writeToParcel(@onNull Parcel dest, int flags)176     public void writeToParcel(@NonNull Parcel dest, int flags) {
177         requireNonNull(dest);
178         dest.writeString(getDataSourceId());
179         dest.writeInt(getFhirResourceType());
180         dest.writeString(getFhirResourceId());
181     }
182 
183     @Override
equals(Object o)184     public boolean equals(Object o) {
185         if (this == o) return true;
186         if (!(o instanceof MedicalResourceId that)) return false;
187         return getDataSourceId().equals(that.getDataSourceId())
188                 && getFhirResourceType() == that.getFhirResourceType()
189                 && getFhirResourceId().equals(that.getFhirResourceId());
190     }
191 
192     @Override
hashCode()193     public int hashCode() {
194         return hash(getDataSourceId(), getFhirResourceType(), getFhirResourceId());
195     }
196 
197     @Override
toString()198     public String toString() {
199         StringBuilder sb = new StringBuilder();
200         sb.append(this.getClass().getSimpleName()).append("{");
201         sb.append("dataSourceId=").append(getDataSourceId());
202         sb.append(",fhirResourceType=").append(getFhirResourceType());
203         sb.append(",fhirResourceId=").append(getFhirResourceId());
204         sb.append("}");
205         return sb.toString();
206     }
207 }
208