001package com.box.sdk; 002 003import java.text.ParseException; 004import java.util.ArrayList; 005import java.util.Date; 006import java.util.List; 007 008import com.eclipsesource.json.JsonArray; 009import com.eclipsesource.json.JsonObject; 010import com.eclipsesource.json.JsonValue; 011 012/** 013 * The Metadata class represents one type instance of Box metadata. 014 * 015 * Learn more about Box metadata: 016 * https://developers.box.com/metadata-api/ 017 */ 018public class Metadata { 019 020 /** 021 * Specifies the name of the default "properties" metadata template. 022 */ 023 public static final String DEFAULT_METADATA_TYPE = "properties"; 024 025 /** 026 * Specifies the "global" metadata scope. 027 */ 028 public static final String GLOBAL_METADATA_SCOPE = "global"; 029 030 /** 031 * Specifies the "enterprise" metadata scope. 032 */ 033 public static final String ENTERPRISE_METADATA_SCOPE = "enterprise"; 034 035 /** 036 * The default limit of entries per response. 037 */ 038 public static final int DEFAULT_LIMIT = 100; 039 040 /** 041 * URL template for all metadata associated with item. 042 */ 043 public static final URLTemplate GET_ALL_METADATA_URL_TEMPLATE = new URLTemplate("/metadata"); 044 045 /** 046 * Values contained by the metadata object. 047 */ 048 private final JsonObject values; 049 050 /** 051 * Operations to be applied to the metadata object. 052 */ 053 private JsonArray operations; 054 055 /** 056 * Creates an empty metadata. 057 */ 058 public Metadata() { 059 this.values = new JsonObject(); 060 } 061 062 /** 063 * Creates a new metadata. 064 * @param values the initial metadata values. 065 */ 066 public Metadata(JsonObject values) { 067 this.values = values; 068 } 069 070 /** 071 * Creates a copy of another metadata. 072 * @param other the other metadata object to copy. 073 */ 074 public Metadata(Metadata other) { 075 this.values = new JsonObject(other.values); 076 } 077 078 /** 079 * Used to retrieve all metadata associated with the item. 080 * @param item item to get metadata for. 081 * @param fields the optional fields to retrieve. 082 * @return An iterable of metadata instances associated with the item. 083 */ 084 public static Iterable<Metadata> getAllMetadata(BoxItem item, String ... fields) { 085 QueryStringBuilder builder = new QueryStringBuilder(); 086 if (fields.length > 0) { 087 builder.appendParam("fields", fields); 088 } 089 return new BoxResourceIterable<Metadata>( 090 item.getAPI(), 091 GET_ALL_METADATA_URL_TEMPLATE.buildWithQuery(item.getItemURL().toString(), builder.toString()), 092 DEFAULT_LIMIT) { 093 094 @Override 095 protected Metadata factory(JsonObject jsonObject) { 096 return new Metadata(jsonObject); 097 } 098 099 }; 100 } 101 102 /** 103 * Returns the 36 character UUID to identify the metadata object. 104 * @return the metadata ID. 105 */ 106 public String getID() { 107 return this.get("/$id"); 108 } 109 110 /** 111 * Returns the metadata type. 112 * @return the metadata type. 113 */ 114 public String getTypeName() { 115 return this.get("/$type"); 116 } 117 118 /** 119 * Returns the parent object ID (typically the file ID). 120 * @return the parent object ID. 121 */ 122 public String getParentID() { 123 return this.get("/$parent"); 124 } 125 126 /** 127 * Returns the scope. 128 * @return the scope. 129 */ 130 public String getScope() { 131 return this.get("/$scope"); 132 } 133 134 /** 135 * Returns the template name. 136 * @return the template name. 137 */ 138 public String getTemplateName() { 139 return this.get("/$template"); 140 } 141 142 /** 143 * Adds a new metadata value. 144 * @param path the path that designates the key. Must be prefixed with a "/". 145 * @param value the value. 146 * @return this metadata object. 147 */ 148 public Metadata add(String path, String value) { 149 this.values.add(this.pathToProperty(path), value); 150 this.addOp("add", path, value); 151 return this; 152 } 153 154 /** 155 * Adds a new metadata value. 156 * @param path the path that designates the key. Must be prefixed with a "/". 157 * @param value the value. 158 * @return this metadata object. 159 */ 160 public Metadata add(String path, float value) { 161 this.values.add(this.pathToProperty(path), value); 162 this.addOp("add", path, value); 163 return this; 164 } 165 166 /** 167 * Replaces an existing metadata value. 168 * @param path the path that designates the key. Must be prefixed with a "/". 169 * @param value the value. 170 * @return this metadata object. 171 */ 172 public Metadata replace(String path, String value) { 173 this.values.set(this.pathToProperty(path), value); 174 this.addOp("replace", path, value); 175 return this; 176 } 177 178 /** 179 * Replaces an existing metadata value. 180 * @param path the path that designates the key. Must be prefixed with a "/". 181 * @param value the value. 182 * @return this metadata object. 183 */ 184 public Metadata replace(String path, float value) { 185 this.values.set(this.pathToProperty(path), value); 186 this.addOp("replace", path, value); 187 return this; 188 } 189 190 /** 191 * Removes an existing metadata value. 192 * @param path the path that designates the key. Must be prefixed with a "/". 193 * @return this metadata object. 194 */ 195 public Metadata remove(String path) { 196 this.values.remove(this.pathToProperty(path)); 197 this.addOp("remove", path, null); 198 return this; 199 } 200 201 /** 202 * Tests that a property has the expected value. 203 * @param path the path that designates the key. Must be prefixed with a "/". 204 * @param value the expected value. 205 * @return this metadata object. 206 */ 207 public Metadata test(String path, String value) { 208 this.addOp("test", path, value); 209 return this; 210 } 211 212 /** 213 * Returns a value. 214 * @param path the path that designates the key. Must be prefixed with a "/". 215 * @return the metadata property value. 216 * @deprecated Metadata#get() does not handle all possible metadata types; use Metadata#getValue() instead 217 */ 218 @Deprecated 219 public String get(String path) { 220 final JsonValue value = this.values.get(this.pathToProperty(path)); 221 if (value == null) { 222 return null; 223 } 224 if (!value.isString()) { 225 return value.toString(); 226 } 227 return value.asString(); 228 } 229 230 /** 231 * Returns a value, regardless of type. 232 * @param path the path that designates the key. Must be prefixed with a "/". 233 * @return the metadata property value as an indeterminate JSON type. 234 */ 235 public JsonValue getValue(String path) { 236 return this.values.get(this.pathToProperty(path)); 237 } 238 239 /** 240 * Get a value from a string or enum metadata field. 241 * @param path the key path in the metadata object. Must be prefixed with a "/". 242 * @return the metadata value as a string. 243 */ 244 public String getString(String path) { 245 return this.getValue(path).asString(); 246 } 247 248 /** 249 * Get a value from a float metadata field. 250 * @param path the key path in the metadata object. Must be prefixed with a "/". 251 * @return the metadata value as a floating point number. 252 */ 253 public double getFloat(String path) { 254 // @NOTE(mwiller) 2018-02-05: JS number are all 64-bit floating point, so double is the correct type to use here 255 return this.getValue(path).asDouble(); 256 } 257 258 /** 259 * Get a value from a date metadata field. 260 * @param path the key path in the metadata object. Must be prefixed with a "/". 261 * @return the metadata value as a Date. 262 * @throws ParseException when the value cannot be parsed as a valid date 263 */ 264 public Date getDate(String path) throws ParseException { 265 return BoxDateFormat.parse(this.getValue(path).asString()); 266 } 267 268 /** 269 * Returns a list of metadata property paths. 270 * @return the list of metdata property paths. 271 */ 272 public List<String> getPropertyPaths() { 273 List<String> result = new ArrayList<String>(); 274 275 for (String property : this.values.names()) { 276 if (!property.startsWith("$")) { 277 result.add(this.propertyToPath(property)); 278 } 279 } 280 281 return result; 282 } 283 284 /** 285 * Returns the JSON patch string with all operations. 286 * @return the JSON patch string. 287 */ 288 public String getPatch() { 289 if (this.operations == null) { 290 return "[]"; 291 } 292 return this.operations.toString(); 293 } 294 295 /** 296 * Returns the JSON representation of this metadata. 297 * @return the JSON representation of this metadata. 298 */ 299 @Override 300 public String toString() { 301 return this.values.toString(); 302 } 303 304 /** 305 * Converts a JSON patch path to a JSON property name. 306 * Currently the metadata API only supports flat maps. 307 * @param path the path that designates the key. Must be prefixed with a "/". 308 * @return the JSON property name. 309 */ 310 private String pathToProperty(String path) { 311 if (path == null || !path.startsWith("/")) { 312 throw new IllegalArgumentException("Path must be prefixed with a \"/\"."); 313 } 314 return path.substring(1); 315 } 316 317 /** 318 * Converts a JSON property name to a JSON patch path. 319 * @param property the JSON property name. 320 * @return the path that designates the key. 321 */ 322 private String propertyToPath(String property) { 323 if (property == null) { 324 throw new IllegalArgumentException("Property must not be null."); 325 } 326 return "/" + property; 327 } 328 329 /** 330 * Adds a patch operation. 331 * @param op the operation type. Must be add, replace, remove, or test. 332 * @param path the path that designates the key. Must be prefixed with a "/". 333 * @param value the value to be set. 334 */ 335 private void addOp(String op, String path, String value) { 336 if (this.operations == null) { 337 this.operations = new JsonArray(); 338 } 339 340 this.operations.add(new JsonObject() 341 .add("op", op) 342 .add("path", path) 343 .add("value", value)); 344 } 345 346 /** 347 * Adds a patch operation. 348 * @param op the operation type. Must be add, replace, remove, or test. 349 * @param path the path that designates the key. Must be prefixed with a "/". 350 * @param value the value to be set. 351 */ 352 private void addOp(String op, String path, float value) { 353 if (this.operations == null) { 354 this.operations = new JsonArray(); 355 } 356 357 this.operations.add(new JsonObject() 358 .add("op", op) 359 .add("path", path) 360 .add("value", value)); 361 } 362 363 static String scopeBasedOnType(String typeName) { 364 String scope; 365 if (typeName.equals(DEFAULT_METADATA_TYPE)) { 366 scope = GLOBAL_METADATA_SCOPE; 367 } else { 368 scope = ENTERPRISE_METADATA_SCOPE; 369 } 370 return scope; 371 } 372}