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 private 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 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 * Replaces an existing 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 replace(String path, String value) { 159 this.values.set(this.pathToProperty(path), value); 160 this.addOp("replace", path, value); 161 return this; 162 } 163 164 /** 165 * Removes an existing metadata value. 166 * @param path the path that designates the key. Must be prefixed with a "/". 167 * @return this metadata object. 168 */ 169 public Metadata remove(String path) { 170 this.values.remove(this.pathToProperty(path)); 171 this.addOp("remove", path, null); 172 return this; 173 } 174 175 /** 176 * Tests that a property has the expected value. 177 * @param path the path that designates the key. Must be prefixed with a "/". 178 * @param value the expected value. 179 * @return this metadata object. 180 */ 181 public Metadata test(String path, String value) { 182 this.addOp("test", path, value); 183 return this; 184 } 185 186 /** 187 * Returns a value. 188 * @param path the path that designates the key. Must be prefixed with a "/". 189 * @return the metadata property value. 190 */ 191 public String get(String path) { 192 final JsonValue value = this.values.get(this.pathToProperty(path)); 193 if (value == null) { 194 return null; 195 } 196 if (value.isNumber()) { 197 return value.toString(); 198 } 199 return value.asString(); 200 } 201 202 /** 203 * Returns a list of metadata property paths. 204 * @return the list of metdata property paths. 205 */ 206 public List<String> getPropertyPaths() { 207 List<String> result = new ArrayList<String>(); 208 209 for (String property : this.values.names()) { 210 if (!property.startsWith("$")) { 211 result.add(this.propertyToPath(property)); 212 } 213 } 214 215 return result; 216 } 217 218 /** 219 * Returns the JSON patch string with all operations. 220 * @return the JSON patch string. 221 */ 222 public String getPatch() { 223 if (this.operations == null) { 224 return "[]"; 225 } 226 return this.operations.toString(); 227 } 228 229 /** 230 * Returns the JSON representation of this metadata. 231 * @return the JSON representation of this metadata. 232 */ 233 @Override 234 public String toString() { 235 return this.values.toString(); 236 } 237 238 /** 239 * Converts a JSON patch path to a JSON property name. 240 * Currently the metadata API only supports flat maps. 241 * @param path the path that designates the key. Must be prefixed with a "/". 242 * @return the JSON property name. 243 */ 244 private String pathToProperty(String path) { 245 if (path == null || !path.startsWith("/")) { 246 throw new IllegalArgumentException("Path must be prefixed with a \"/\"."); 247 } 248 return path.substring(1); 249 } 250 251 /** 252 * Converts a JSON property name to a JSON patch path. 253 * @param property the JSON property name. 254 * @return the path that designates the key. 255 */ 256 private String propertyToPath(String property) { 257 if (property == null) { 258 throw new IllegalArgumentException("Property must not be null."); 259 } 260 return "/" + property; 261 } 262 263 /** 264 * Adds a patch operation. 265 * @param op the operation type. Must be add, replace, remove, or test. 266 * @param path the path that designates the key. Must be prefixed with a "/". 267 * @param value the value to be set. 268 */ 269 private void addOp(String op, String path, String value) { 270 if (this.operations == null) { 271 this.operations = new JsonArray(); 272 } 273 274 this.operations.add(new JsonObject() 275 .add("op", op) 276 .add("path", path) 277 .add("value", value)); 278 } 279 280 static String scopeBasedOnType(String typeName) { 281 String scope; 282 if (typeName.equals(DEFAULT_METADATA_TYPE)) { 283 scope = GLOBAL_METADATA_SCOPE; 284 } else { 285 scope = ENTERPRISE_METADATA_SCOPE; 286 } 287 return scope; 288 } 289}