001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2024 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 public String getValue() { 153 return myValue; 154 } 155 156 /** 157 * See the note on equality on the {@link Include class documentation} 158 */ 159 @Override 160 public int hashCode() { 161 final int prime = 31; 162 int result = 1; 163 result = prime * result + (myIterate ? 1231 : 1237); 164 result = prime * result + ((myValue == null) ? 0 : myValue.hashCode()); 165 return result; 166 } 167 168 /** 169 * Is this object {@link #toLocked() locked}? 170 */ 171 public boolean isLocked() { 172 return myImmutable; 173 } 174 175 public boolean isRecurse() { 176 return myIterate; 177 } 178 179 /** 180 * Should this include recurse 181 * 182 * @return Returns a reference to <code>this</code> for easy method chaining 183 */ 184 public Include setRecurse(boolean theRecurse) { 185 myIterate = theRecurse; 186 return this; 187 } 188 189 public void setValue(String theValue) { 190 if (myImmutable) { 191 throw new IllegalStateException(Msg.code(1888) + "Can not change the value of this include"); 192 } 193 194 String value = defaultString(theValue); 195 196 int firstColon = value.indexOf(':'); 197 String paramType; 198 String paramName; 199 String paramTargetType; 200 if (firstColon == -1 || firstColon == value.length() - 1) { 201 paramType = null; 202 paramName = null; 203 paramTargetType = null; 204 } else { 205 paramType = value.substring(0, firstColon); 206 int secondColon = value.indexOf(':', firstColon + 1); 207 if (secondColon == -1) { 208 paramName = value.substring(firstColon + 1); 209 paramTargetType = null; 210 } else { 211 paramName = value.substring(firstColon + 1, secondColon); 212 paramTargetType = value.substring(secondColon + 1); 213 } 214 } 215 216 myParamType = paramType; 217 myParamName = paramName; 218 myParamTargetType = paramTargetType; 219 myValue = theValue; 220 } 221 222 /** 223 * Return a new 224 */ 225 public Include toLocked() { 226 Include retVal = new Include(myValue, myIterate, true); 227 return retVal; 228 } 229 230 @Override 231 public String toString() { 232 ToStringBuilder builder = new ToStringBuilder(this); 233 builder.append("value", myValue); 234 builder.append("iterate", myIterate); 235 return builder.toString(); 236 } 237 238 /** 239 * Creates and returns a new copy of this Include with the given type. The following table shows what will be 240 * returned: 241 * <table> 242 * <tr> 243 * <th>Initial Contents</th> 244 * <th>theResourceType</th> 245 * <th>Output</th> 246 * </tr> 247 * <tr> 248 * <td>Patient:careProvider</th> 249 * <th>Organization</th> 250 * <th>Patient:careProvider:Organization</th> 251 * </tr> 252 * <tr> 253 * <td>Patient:careProvider:Practitioner</th> 254 * <th>Organization</th> 255 * <th>Patient:careProvider:Organization</th> 256 * </tr> 257 * <tr> 258 * <td>Patient</th> 259 * <th>(any)</th> 260 * <th>{@link IllegalStateException}</th> 261 * </tr> 262 * </table> 263 * 264 * @param theResourceType 265 * The resource type (e.g. "Organization") 266 * @return A new copy of the include. Note that if this include is {@link #toLocked() locked}, the returned include 267 * will be too 268 */ 269 public Include withType(String theResourceType) { 270 StringBuilder b = new StringBuilder(); 271 272 String paramType = getParamType(); 273 String paramName = getParamName(); 274 if (isBlank(paramType) || isBlank(paramName)) { 275 throw new IllegalStateException( 276 Msg.code(1889) + "This include does not contain a value in the format [ResourceType]:[paramName]"); 277 } 278 b.append(paramType); 279 b.append(":"); 280 b.append(paramName); 281 282 if (isNotBlank(theResourceType)) { 283 b.append(':'); 284 b.append(theResourceType); 285 } 286 Include retVal = new Include(b.toString(), myIterate, myImmutable); 287 return retVal; 288 } 289}