001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2023 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.model.api; 021 022import ca.uhn.fhir.i18n.Msg; 023import org.apache.commons.lang3.builder.ToStringBuilder; 024 025import java.io.Serializable; 026 027import static org.apache.commons.lang3.StringUtils.defaultString; 028import static org.apache.commons.lang3.StringUtils.isBlank; 029import static org.apache.commons.lang3.StringUtils.isNotBlank; 030 031/** 032 * Represents a FHIR resource path specification, e.g. <code>Patient:name</code> 033 * <p> 034 * Note on equality: This class uses {@link #getValue() value} and the {@link #isRecurse() recurse} properties to test 035 * equality. Prior to HAPI 1.2 (and FHIR DSTU2) the recurse property did not exist, so this may merit consideration when 036 * upgrading servers. 037 * </p> 038 * <p> 039 * Note on thread safety: This class is not thread safe. 040 * </p> 041 */ 042public class Include implements Serializable { 043 044 private static final long serialVersionUID = 1L; 045 046 private final boolean myImmutable; 047 private boolean myIterate; 048 private String myValue; 049 private String myParamType; 050 private String myParamName; 051 private String myParamTargetType; 052 053 /** 054 * Constructor for <b>non-recursive</b> include 055 * 056 * @param theValue 057 * The <code>_include</code> value, e.g. "Patient:name" 058 */ 059 public Include(String theValue) { 060 this(theValue, false); 061 } 062 063 /** 064 * Constructor for an include 065 * 066 * @param theValue 067 * The <code>_include</code> value, e.g. "Patient:name" 068 * @param theIterate 069 * Should the include recurse 070 */ 071 public Include(String theValue, boolean theIterate) { 072 this(theValue, theIterate, false); 073 } 074 075 /** 076 * Constructor for an include 077 * 078 * @param theValue 079 * The <code>_include</code> value, e.g. "Patient:name" 080 * @param theIterate 081 * Should the include recurse 082 */ 083 public Include(String theValue, boolean theIterate, boolean theImmutable) { 084 setValue(theValue); 085 myIterate = theIterate; 086 myImmutable = theImmutable; 087 } 088 089 /** 090 * Creates a copy of this include with non-recurse behaviour 091 */ 092 public Include asNonRecursive() { 093 return new Include(myValue, false); 094 } 095 096 /** 097 * Creates a copy of this include with recurse behaviour 098 */ 099 public Include asRecursive() { 100 return new Include(myValue, true); 101 } 102 103 /** 104 * See the note on equality on the {@link Include class documentation} 105 */ 106 @Override 107 public boolean equals(Object obj) { 108 if (this == obj) { 109 return true; 110 } 111 if (obj == null) { 112 return false; 113 } 114 if (getClass() != obj.getClass()) { 115 return false; 116 } 117 Include other = (Include) obj; 118 if (myIterate != other.myIterate) { 119 return false; 120 } 121 if (myValue == null) { 122 if (other.myValue != null) { 123 return false; 124 } 125 } else if (!myValue.equals(other.myValue)) { 126 return false; 127 } 128 return true; 129 } 130 131 /** 132 * Returns the portion of the value before the first colon 133 */ 134 public String getParamType() { 135 return myParamType; 136 } 137 138 /** 139 * Returns the portion of the value after the first colon but before the second colon 140 */ 141 public String getParamName() { 142 return myParamName; 143 } 144 145 /** 146 * Returns the portion of the string after the second colon, or null if there are not two colons in the value. 147 */ 148 public String getParamTargetType() { 149 return myParamTargetType; 150 151 } 152 153 public String getValue() { 154 return myValue; 155 } 156 157 /** 158 * See the note on equality on the {@link Include class documentation} 159 */ 160 @Override 161 public int hashCode() { 162 final int prime = 31; 163 int result = 1; 164 result = prime * result + (myIterate ? 1231 : 1237); 165 result = prime * result + ((myValue == null) ? 0 : myValue.hashCode()); 166 return result; 167 } 168 169 /** 170 * Is this object {@link #toLocked() locked}? 171 */ 172 public boolean isLocked() { 173 return myImmutable; 174 } 175 176 public boolean isRecurse() { 177 return myIterate; 178 } 179 180 /** 181 * Should this include recurse 182 * 183 * @return Returns a reference to <code>this</code> for easy method chaining 184 */ 185 public Include setRecurse(boolean theRecurse) { 186 myIterate = theRecurse; 187 return this; 188 } 189 190 public void setValue(String theValue) { 191 if (myImmutable) { 192 throw new IllegalStateException(Msg.code(1888) + "Can not change the value of this include"); 193 } 194 195 String value = defaultString(theValue); 196 197 int firstColon = value.indexOf(':'); 198 String paramType; 199 String paramName; 200 String paramTargetType; 201 if (firstColon == -1 || firstColon == value.length() - 1) { 202 paramType = null; 203 paramName = null; 204 paramTargetType = null; 205 } else { 206 paramType = value.substring(0, firstColon); 207 int secondColon = value.indexOf(':', firstColon + 1); 208 if (secondColon == -1) { 209 paramName = value.substring(firstColon + 1); 210 paramTargetType = null; 211 } else { 212 paramName = value.substring(firstColon + 1, secondColon); 213 paramTargetType = value.substring(secondColon + 1); 214 } 215 } 216 217 myParamType = paramType; 218 myParamName = paramName; 219 myParamTargetType = paramTargetType; 220 myValue = theValue; 221 222 } 223 224 /** 225 * Return a new 226 */ 227 public Include toLocked() { 228 Include retVal = new Include(myValue, myIterate, true); 229 return retVal; 230 } 231 232 @Override 233 public String toString() { 234 ToStringBuilder builder = new ToStringBuilder(this); 235 builder.append("value", myValue); 236 builder.append("iterate", myIterate); 237 return builder.toString(); 238 } 239 240 /** 241 * Creates and returns a new copy of this Include with the given type. The following table shows what will be 242 * returned: 243 * <table> 244 * <tr> 245 * <th>Initial Contents</th> 246 * <th>theResourceType</th> 247 * <th>Output</th> 248 * </tr> 249 * <tr> 250 * <td>Patient:careProvider</th> 251 * <th>Organization</th> 252 * <th>Patient:careProvider:Organization</th> 253 * </tr> 254 * <tr> 255 * <td>Patient:careProvider:Practitioner</th> 256 * <th>Organization</th> 257 * <th>Patient:careProvider:Organization</th> 258 * </tr> 259 * <tr> 260 * <td>Patient</th> 261 * <th>(any)</th> 262 * <th>{@link IllegalStateException}</th> 263 * </tr> 264 * </table> 265 * 266 * @param theResourceType 267 * The resource type (e.g. "Organization") 268 * @return A new copy of the include. Note that if this include is {@link #toLocked() locked}, the returned include 269 * will be too 270 */ 271 public Include withType(String theResourceType) { 272 StringBuilder b = new StringBuilder(); 273 274 String paramType = getParamType(); 275 String paramName = getParamName(); 276 if (isBlank(paramType) || isBlank(paramName)) { 277 throw new IllegalStateException(Msg.code(1889) + "This include does not contain a value in the format [ResourceType]:[paramName]"); 278 } 279 b.append(paramType); 280 b.append(":"); 281 b.append(paramName); 282 283 if (isNotBlank(theResourceType)) { 284 b.append(':'); 285 b.append(theResourceType); 286 } 287 Include retVal = new Include(b.toString(), myIterate, myImmutable); 288 return retVal; 289 } 290 291}