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