001package com.box.sdk;
002
003import java.net.URL;
004import java.util.ArrayList;
005import java.util.List;
006
007import com.eclipsesource.json.JsonArray;
008import com.eclipsesource.json.JsonObject;
009import com.eclipsesource.json.JsonValue;
010
011
012/**
013 * The MetadataTemplate class represents the Box metadata template object.
014 * Templates allow the metadata service to provide a multitude of services,
015 * such as pre-defining sets of key:value pairs or schema enforcement on specific fields.
016 *
017 * @see <a href="https://docs.box.com/reference#metadata-templates">Box metadata templates</a>
018 */
019public class MetadataTemplate extends BoxJSONObject {
020
021    /**
022     * @see #getMetadataTemplate(BoxAPIConnection)
023     */
024    private static final URLTemplate METADATA_TEMPLATE_URL_TEMPLATE
025        = new URLTemplate("metadata_templates/%s/%s/schema");
026
027    /**
028     * @see #createMetadataTemplate(BoxAPIConnection, String, String, String, boolean, List)
029     */
030    private static final URLTemplate METADATA_TEMPLATE_SCHEMA_URL_TEMPLATE
031        = new URLTemplate("metadata_templates/schema");
032
033    /**
034     * @see #getEnterpriseMetadataTemplates(String, int, BoxAPIConnection, String...)
035     */
036    private static final URLTemplate ENTERPRISE_METADATA_URL_TEMPLATE = new URLTemplate("metadata_templates/%s");
037
038    /**
039     * Default metadata type to be used in query.
040     */
041    private static final String DEFAULT_METADATA_TYPE = "properties";
042
043    /**
044     * Global metadata scope. Used by default if the metadata type is "properties".
045     */
046    private static final String GLOBAL_METADATA_SCOPE = "global";
047
048    /**
049     * Enterprise metadata scope. Used by default if the metadata type is not "properties".
050     */
051    private static final String ENTERPRISE_METADATA_SCOPE = "enterprise";
052
053    /**
054     * Default number of entries per page.
055     */
056    private static final int DEFAULT_ENTRIES_LIMIT = 100;
057
058    /**
059     * @see #getTemplateKey()
060     */
061    private String templateKey;
062
063    /**
064     * @see #getScope()
065     */
066    private String scope;
067
068    /**
069     * @see #getDisplayName()
070     */
071    private String displayName;
072
073    /**
074     * @see #getIsHidden()
075     */
076    private Boolean isHidden;
077
078    /**
079     * @see #getFields()
080     */
081    private List<Field> fields;
082
083    /**
084     * Constructs an empty metadata template.
085     */
086    public MetadataTemplate() {
087        super();
088    }
089
090    /**
091     * Constructs a metadata template from a JSON string.
092     * @param json the json encoded metadate template.
093     */
094    public MetadataTemplate(String json) {
095        super(json);
096    }
097
098    /**
099     * Constructs a metadate template from a JSON object.
100     * @param jsonObject the json encoded metadate template.
101     */
102    MetadataTemplate(JsonObject jsonObject) {
103        super(jsonObject);
104    }
105
106    /**
107     * Gets the unique template key to identify the metadata template.
108     * @return the unique template key to identify the metadata template.
109     */
110    public String getTemplateKey() {
111        return this.templateKey;
112    }
113
114    /**
115     * Gets the metadata template scope.
116     * @return the metadata template scope.
117     */
118    public String getScope() {
119        return this.scope;
120    }
121
122    /**
123     * Gets the displayed metadata template name.
124     * @return the displayed metadata template name.
125     */
126    public String getDisplayName() {
127        return this.displayName;
128    }
129
130    /**
131     * Gets is the metadata template hidden.
132     * @return is the metadata template hidden.
133     */
134    public Boolean getIsHidden() {
135        return this.isHidden;
136    }
137
138    /**
139     * Gets the iterable with all fields the metadata template contains.
140     * @return the iterable with all fields the metadata template contains.
141     */
142    public List<Field> getFields() {
143        return this.fields;
144    }
145
146    /**
147     * {@inheritDoc}
148     */
149    @Override
150    void parseJSONMember(JsonObject.Member member) {
151        JsonValue value = member.getValue();
152        String memberName = member.getName();
153        if (memberName.equals("templateKey")) {
154            this.templateKey = value.asString();
155        } else if (memberName.equals("scope")) {
156            this.scope = value.asString();
157        } else if (memberName.equals("displayName")) {
158            this.displayName = value.asString();
159        } else if (memberName.equals("hidden")) {
160            this.isHidden = value.asBoolean();
161        } else if (memberName.equals("fields")) {
162            this.fields = new ArrayList<Field>();
163            for (JsonValue field: value.asArray()) {
164                this.fields.add(new Field(field.asObject()));
165            }
166        }
167    }
168
169    /**
170     * Creates new metadata template.
171     * @param api the API connection to be used.
172     * @param scope the scope of the object.
173     * @param templateKey a unique identifier for the template.
174     * @param displayName the display name of the field.
175     * @param hidden whether this template is hidden in the UI.
176     * @param fields the ordered set of fields for the template
177     * @return the metadata template returned from the server.
178     */
179    public static MetadataTemplate createMetadataTemplate(BoxAPIConnection api, String scope, String templateKey,
180            String displayName, boolean hidden, List<Field> fields) {
181
182        JsonObject jsonObject = new JsonObject();
183        jsonObject.add("scope", scope);
184        jsonObject.add("displayName", displayName);
185        jsonObject.add("hidden", hidden);
186
187        if (templateKey != null) {
188            jsonObject.add("templateKey", templateKey);
189        }
190
191        JsonArray fieldsArray = new JsonArray();
192        if (fields != null && !fields.isEmpty()) {
193            for (Field field : fields) {
194                JsonObject fieldObj = getFieldJsonObject(field);
195
196                fieldsArray.add(fieldObj);
197            }
198
199            jsonObject.add("fields", fieldsArray);
200        }
201
202        URL url = METADATA_TEMPLATE_SCHEMA_URL_TEMPLATE.build(api.getBaseURL());
203        BoxJSONRequest request = new BoxJSONRequest(api, url, "POST");
204        request.setBody(jsonObject.toString());
205
206        BoxJSONResponse response = (BoxJSONResponse) request.send();
207        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
208
209        return new MetadataTemplate(responseJSON);
210    }
211
212    /**
213     * Gets the JsonObject representation of the given field object.
214     * @param field represents a template field
215     * @return the json object
216     */
217    private static JsonObject getFieldJsonObject(Field field) {
218        JsonObject fieldObj = new JsonObject();
219        fieldObj.add("type", field.getType());
220        fieldObj.add("key", field.getKey());
221        fieldObj.add("displayName", field.getDisplayName());
222
223        String fieldDesc = field.getDescription();
224        if (fieldDesc != null) {
225            fieldObj.add("description", field.getDescription());
226        }
227
228        Boolean fieldIsHidden = field.getIsHidden();
229        if (fieldIsHidden != null) {
230            fieldObj.add("hidden", field.getIsHidden());
231        }
232
233        JsonArray array = new JsonArray();
234        List<String> options = field.getOptions();
235        if (options != null && !options.isEmpty()) {
236            for (String option : options) {
237                JsonObject optionObj = new JsonObject();
238                optionObj.add("key", option);
239
240                array.add(optionObj);
241            }
242            fieldObj.add("options", array);
243        }
244
245        return fieldObj;
246    }
247
248    /**
249     * Updates the schema of an existing metadata template.
250     *
251     * @param api the API connection to be used
252     * @param scope the scope of the object
253     * @param template Unique identifier of the template
254     * @param fieldOperations the fields that needs to be updated / added in the template
255     * @return the updated metadata template
256     */
257    public static MetadataTemplate updateMetadataTemplate(BoxAPIConnection api, String scope, String template,
258            List<FieldOperation> fieldOperations) {
259
260        JsonArray array = new JsonArray();
261
262        for (FieldOperation fieldOperation : fieldOperations) {
263            JsonObject jsonObject = getFieldOperationJsonObject(fieldOperation);
264            array.add(jsonObject);
265        }
266
267        QueryStringBuilder builder = new QueryStringBuilder();
268        URL url = METADATA_TEMPLATE_URL_TEMPLATE.build(api.getBaseURL(), scope, template);
269        BoxJSONRequest request = new BoxJSONRequest(api, url, "PUT");
270        request.setBody(array.toString());
271
272        BoxJSONResponse response = (BoxJSONResponse) request.send();
273        JsonObject responseJson = JsonObject.readFrom(response.getJSON());
274
275        return new MetadataTemplate(responseJson);
276    }
277
278    /**
279     * Gets the JsonObject representation of the Field Operation.
280     * @param fieldOperation represents the template update operation
281     * @return the json object
282     */
283    private static JsonObject getFieldOperationJsonObject(FieldOperation fieldOperation) {
284        JsonObject jsonObject = new JsonObject();
285        jsonObject.add("op", fieldOperation.getOp().toString());
286
287        String fieldKey = fieldOperation.getFieldKey();
288        if (fieldKey != null) {
289            jsonObject.add("fieldKey", fieldKey);
290        }
291
292        Field field = fieldOperation.getData();
293        if (field != null) {
294            JsonObject fieldObj = new JsonObject();
295
296            String type = field.getType();
297            if (type != null) {
298                fieldObj.add("type", type);
299            }
300
301            String key = field.getKey();
302            if (key != null) {
303                fieldObj.add("key", key);
304            }
305
306            String displayName = field.getDisplayName();
307            if (displayName != null) {
308                fieldObj.add("displayName", displayName);
309            }
310
311            String description = field.getDescription();
312            if (description != null) {
313                fieldObj.add("description", description);
314            }
315
316            Boolean hidden = field.getIsHidden();
317            if (hidden != null) {
318                fieldObj.add("hidden", hidden);
319            }
320
321            List<String> options = field.getOptions();
322            if (options != null) {
323                JsonArray array = new JsonArray();
324                for (String option: options) {
325                    JsonObject optionObj = new JsonObject();
326                    optionObj.add("key", option);
327
328                    array.add(optionObj);
329                }
330
331                fieldObj.add("options", array);
332            }
333
334            jsonObject.add("data", fieldObj);
335        }
336
337        List<String> fieldKeys = fieldOperation.getFieldKeys();
338        if (fieldKeys != null) {
339            jsonObject.add("fieldKeys", getJsonArray(fieldKeys));
340        }
341
342        List<String> enumOptionKeys = fieldOperation.getEnumOptionKeys();
343        if (enumOptionKeys != null) {
344            jsonObject.add("enumOptionKeys", getJsonArray(enumOptionKeys));
345        }
346
347        return jsonObject;
348    }
349
350    /**
351     * Gets the Json Array representation of the given list of strings.
352     * @param keys List of strings
353     * @return the JsonArray represents the list of keys
354     */
355    private static JsonArray getJsonArray(List<String> keys) {
356        JsonArray array = new JsonArray();
357        for (String key : keys) {
358            array.add(key);
359        }
360
361        return array;
362    }
363
364    /**
365     * Gets the metadata template of properties.
366     * @param api the API connection to be used.
367     * @return the metadata template returned from the server.
368     */
369    public static MetadataTemplate getMetadataTemplate(BoxAPIConnection api) {
370        return getMetadataTemplate(api, DEFAULT_METADATA_TYPE);
371    }
372
373    /**
374     * Gets the metadata template of specified template type.
375     * @param api the API connection to be used.
376     * @param templateName the metadata template type name.
377     * @return the metadata template returned from the server.
378     */
379    public static MetadataTemplate getMetadataTemplate(BoxAPIConnection api, String templateName) {
380        String scope = scopeBasedOnType(templateName);
381        return getMetadataTemplate(api, templateName, scope);
382    }
383
384    /**
385     * Gets the metadata template of specified template type.
386     * @param api the API connection to be used.
387     * @param templateName the metadata template type name.
388     * @param scope the metadata template scope (global or enterprise).
389     * @param fields the fields to retrieve.
390     * @return the metadata template returned from the server.
391     */
392    public static MetadataTemplate getMetadataTemplate(
393            BoxAPIConnection api, String templateName, String scope, String ... fields) {
394        QueryStringBuilder builder = new QueryStringBuilder();
395        if (fields.length > 0) {
396            builder.appendParam("fields", fields);
397        }
398        URL url = METADATA_TEMPLATE_URL_TEMPLATE.buildWithQuery(
399                api.getBaseURL(), builder.toString(), scope, templateName);
400        BoxAPIRequest request = new BoxAPIRequest(api, url, "GET");
401        BoxJSONResponse response = (BoxJSONResponse) request.send();
402        return new MetadataTemplate(response.getJSON());
403    }
404
405    /**
406     * Returns all metadata templates within a user's enterprise.
407     * @param api the API connection to be used.
408     * @param fields the fields to retrieve.
409     * @return the metadata template returned from the server.
410     */
411    public static Iterable<MetadataTemplate> getEnterpriseMetadataTemplates(BoxAPIConnection api, String ... fields) {
412        return getEnterpriseMetadataTemplates(ENTERPRISE_METADATA_SCOPE, api, fields);
413    }
414
415    /**
416     * Returns all metadata templates within a user's scope. Currently only the enterprise scope is supported.
417     * @param scope the scope of the metadata templates.
418     * @param api the API connection to be used.
419     * @param fields the fields to retrieve.
420     * @return the metadata template returned from the server.
421     */
422    public static Iterable<MetadataTemplate> getEnterpriseMetadataTemplates(
423            String scope, BoxAPIConnection api, String ... fields) {
424        return getEnterpriseMetadataTemplates(ENTERPRISE_METADATA_SCOPE, DEFAULT_ENTRIES_LIMIT, api, fields);
425    }
426
427    /**
428     * Returns all metadata templates within a user's scope. Currently only the enterprise scope is supported.
429     * @param scope the scope of the metadata templates.
430     * @param limit maximum number of entries per response.
431     * @param api the API connection to be used.
432     * @param fields the fields to retrieve.
433     * @return the metadata template returned from the server.
434     */
435    public static Iterable<MetadataTemplate> getEnterpriseMetadataTemplates(
436            String scope, int limit, BoxAPIConnection api, String ... fields) {
437        QueryStringBuilder builder = new QueryStringBuilder();
438        if (fields.length > 0) {
439            builder.appendParam("fields", fields);
440        }
441        return new BoxResourceIterable<MetadataTemplate>(
442                api, ENTERPRISE_METADATA_URL_TEMPLATE.buildWithQuery(
443                        api.getBaseURL(), builder.toString(), scope), limit) {
444
445            @Override
446            protected MetadataTemplate factory(JsonObject jsonObject) {
447                return new MetadataTemplate(jsonObject);
448            }
449        };
450    }
451
452    /**
453     * Determines the metadata scope based on type.
454     * @param typeName type of the metadata.
455     * @return scope of the metadata.
456     */
457    private static String scopeBasedOnType(String typeName) {
458        return typeName.equals(DEFAULT_METADATA_TYPE) ? GLOBAL_METADATA_SCOPE : ENTERPRISE_METADATA_SCOPE;
459    }
460
461    /**
462     * Class contains information about the metadata template field.
463     */
464    public static class Field extends BoxJSONObject {
465
466        /**
467         * @see #getType()
468         */
469        private String type;
470
471        /**
472         * @see #getKey()
473         */
474        private String key;
475
476        /**
477         * @see #getDisplayName()
478         */
479        private String displayName;
480
481        /**
482         * @see #getIsHidden()
483         */
484        private Boolean isHidden;
485
486        /**
487         * @see #getDescription()
488         */
489        private String description;
490
491        /**
492         * @see #getOptions()
493         */
494        private List<String> options;
495
496        /**
497         * Constructs an empty metadata template.
498         */
499        public Field() {
500            super();
501        }
502
503        /**
504         * Constructs a metadate template field from a JSON string.
505         * @param json the json encoded metadate template field.
506         */
507        public Field(String json) {
508            super(json);
509        }
510
511        /**
512         * Constructs a metadate template field from a JSON object.
513         * @param jsonObject the json encoded metadate template field.
514         */
515        Field(JsonObject jsonObject) {
516            super(jsonObject);
517        }
518
519        /**
520         * Gets the data type of the field's value.
521         * @return the data type of the field's value.
522         */
523        public String getType() {
524            return this.type;
525        }
526
527        /**
528         * Sets the data type of the field's value.
529         * @param type the data type of the field's value.
530         */
531        public void setType(String type) {
532            this.type = type;
533        }
534
535        /**
536         * Gets the key of the field.
537         * @return the key of the field.
538         */
539        public String getKey() {
540            return this.key;
541        }
542
543        /**
544         * Sets the key of the field.
545         * @param key the key of the field.
546         */
547        public void setKey(String key) {
548            this.key = key;
549        }
550
551        /**
552         * Gets the display name of the field.
553         * @return the display name of the field.
554         */
555        public String getDisplayName() {
556            return this.displayName;
557        }
558
559        /**
560         * Sets the display name of the field.
561         * @param displayName the display name of the field.
562         */
563        public void setDisplayName(String displayName) {
564            this.displayName = displayName;
565        }
566
567        /**
568         * Gets is metadata template field hidden.
569         * @return is metadata template field hidden.
570         */
571        public Boolean getIsHidden() {
572            return this.isHidden;
573        }
574
575        /**
576         * Sets is metadata template field hidden.
577         * @param isHidden is metadata template field hidden?
578         */
579        public void setIsHidden(boolean isHidden) {
580            this.isHidden = isHidden;
581        }
582
583        /**
584         * Gets the description of the field.
585         * @return the description of the field.
586         */
587        public String getDescription() {
588            return this.description;
589        }
590
591        /**
592         * Sets the description of the field.
593         * @param description the description of the field.
594         */
595        public void setDescription(String description) {
596            this.description = description;
597        }
598
599        /**
600         * Gets list of possible options for enum type of the field.
601         * @return list of possible options for enum type of the field.
602         */
603        public List<String> getOptions() {
604            return this.options;
605        }
606
607        /**
608         * Sets list of possible options for enum type of the field.
609         * @param options list of possible options for enum type of the field.
610         */
611        public void setOptions(List<String> options) {
612            this.options = options;
613        }
614
615        /**
616         * {@inheritDoc}
617         */
618        @Override
619        void parseJSONMember(JsonObject.Member member) {
620            JsonValue value = member.getValue();
621            String memberName = member.getName();
622            if (memberName.equals("type")) {
623                this.type = value.asString();
624            } else if (memberName.equals("key")) {
625                this.key = value.asString();
626            } else if (memberName.equals("displayName")) {
627                this.displayName = value.asString();
628            } else if (memberName.equals("hidden")) {
629                this.isHidden = value.asBoolean();
630            } else if (memberName.equals("description")) {
631                this.description = value.asString();
632            } else if (memberName.equals("options")) {
633                this.options = new ArrayList<String>();
634                for (JsonValue key: value.asArray()) {
635                    this.options.add(key.asObject().get("key").asString());
636                }
637            }
638        }
639    }
640
641    /**
642     * Posssible operations that can be performed in a Metadata template.
643     *  <ul>
644     *      <li>Add an enum option</li>
645     *      <li>Add a field</li>
646     *      <li>Edit a field</li>
647     *      <li>Edit template</li>
648     *      <li>Reorder the enum option</li>
649     *      <li>Reorder the field list</li>
650     *  </ul>
651     */
652    public static class FieldOperation extends BoxJSONObject {
653
654        private Operation op;
655        private Field data;
656        private String fieldKey;
657        private List<String> fieldKeys;
658        private List<String> enumOptionKeys;
659
660        /**
661         * Constructs an empty FieldOperation.
662         */
663        public FieldOperation() {
664            super();
665        }
666
667        /**
668         * Constructs a Field operation from a JSON string.
669         * @param json the json encoded metadate template field.
670         */
671        public FieldOperation(String json) {
672            super(json);
673        }
674
675        /**
676         * Constructs a Field operation from a JSON object.
677         * @param jsonObject the json encoded metadate template field.
678         */
679        FieldOperation(JsonObject jsonObject) {
680            super(jsonObject);
681        }
682
683        /**
684         * Gets the operation.
685         * @return the operation
686         */
687        public Operation getOp() {
688            return this.op;
689        }
690
691        /**
692         * Gets the data associated with the operation.
693         * @return the field object representing the data
694         */
695        public Field getData() {
696            return this.data;
697        }
698
699        /**
700         * Gets the field key.
701         * @return the field key
702         */
703        public String getFieldKey() {
704            return this.fieldKey;
705        }
706
707        /**
708         * Gets the list of field keys.
709         * @return the list of Strings
710         */
711        public List<String> getFieldKeys() {
712            return this.fieldKeys;
713        }
714
715        /**
716         * Gets the list of keys of the Enum options.
717         * @return the list of Strings
718         */
719        public List<String> getEnumOptionKeys() {
720            return this.enumOptionKeys;
721        }
722
723        /**
724         * Sets the operation.
725         * @param op the operation
726         */
727        public void setOp(Operation op) {
728            this.op = op;
729        }
730
731        /**
732         * Sets the data.
733         * @param data the Field object representing the data
734         */
735        public void setData(Field data) {
736            this.data = data;
737        }
738
739        /**
740         * Sets the field key.
741         * @param fieldKey the key of the field
742         */
743        public void setFieldKey(String fieldKey) {
744            this.fieldKey = fieldKey;
745        }
746
747        /**
748         * Sets the list of the field keys.
749         * @param fieldKeys the list of strings
750         */
751        public void setFieldKeys(List<String> fieldKeys) {
752            this.fieldKeys = fieldKeys;
753        }
754
755        /**
756         * Sets the list of the enum option keys.
757         * @param enumOptionKeys the list of Strings
758         */
759        public void setEnumOptionKeys(List<String> enumOptionKeys) {
760            this.enumOptionKeys = enumOptionKeys;
761        }
762
763        /**
764         * {@inheritDoc}
765         */
766        @Override
767        void parseJSONMember(JsonObject.Member member) {
768            JsonValue value = member.getValue();
769            String memberName = member.getName();
770            if (memberName.equals("op")) {
771                this.op = Operation.valueOf(value.asString());
772            } else if (memberName.equals("data")) {
773                this.data = new Field(value.asObject());
774            } else if (memberName.equals("fieldKey")) {
775                this.fieldKey = value.asString();
776            } else if (memberName.equals("fieldKeys")) {
777                if (this.fieldKeys == null) {
778                    this.fieldKeys = new ArrayList<String>();
779                } else {
780                    this.fieldKeys.clear();
781                }
782
783                JsonArray array = value.asArray();
784                for (JsonValue jsonValue: array) {
785                    this.fieldKeys.add(jsonValue.asString());
786                }
787            } else if (memberName.equals("enumOptionKeys")) {
788                if (this.enumOptionKeys == null) {
789                    this.enumOptionKeys = new ArrayList<String>();
790                } else {
791                    this.enumOptionKeys.clear();
792                }
793
794                JsonArray array = value.asArray();
795                for (JsonValue jsonValue: array) {
796                    this.enumOptionKeys.add(jsonValue.asString());
797                }
798            }
799        }
800    }
801
802    /**
803     * Possible template operations.
804     */
805    public enum Operation {
806
807        /**
808         * Adds an enum option at the end of the enum option list for the specified field.
809         */
810        addEnumOption,
811
812        /**
813         * Adds a field at the end of the field list for the template.
814         */
815        addField,
816
817        /**
818         * Edits any number of the base properties of a field: displayName, hidden, description.
819         */
820        editField,
821
822        /**
823         * Edits any number of the base properties of a template: displayName, hidden.
824         */
825        editTemplate,
826
827        /**
828         * Reorders the enum option list to match the requested enum option list.
829         */
830        reorderEnumOptions,
831
832        /**
833         * Reorders the field list to match the requested field list.
834         */
835        reorderFields
836    }
837}