From 22799b4b7fb1722581612e0634f18535431b6db9 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Mon, 10 Jun 2019 16:20:09 -0700 Subject: [PATCH 1/2] Issue 36854: Custom property descriptor JSON parsing methods Added custom property descriptor and validator JSON parsing using Jackson as much as possible. No longer using deprecated Experiment module helper methods. --- .../labkey/snd/PropertyDescriptorMixin.java | 38 ++++++ .../labkey/snd/PropertyValidatorMixin.java | 16 +++ src/org/labkey/snd/SNDController.java | 128 ++++++++++++++++-- 3 files changed, 172 insertions(+), 10 deletions(-) create mode 100644 src/org/labkey/snd/PropertyDescriptorMixin.java create mode 100644 src/org/labkey/snd/PropertyValidatorMixin.java diff --git a/src/org/labkey/snd/PropertyDescriptorMixin.java b/src/org/labkey/snd/PropertyDescriptorMixin.java new file mode 100644 index 00000000..8c21c776 --- /dev/null +++ b/src/org/labkey/snd/PropertyDescriptorMixin.java @@ -0,0 +1,38 @@ +package org.labkey.snd; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.labkey.api.gwt.client.util.StringProperty; + +/** + * Configures the fields that are not returned when serializing a GWTPropertyDescriptor. + * Ideally we would just add the @JsonIgnore annotations to GWTPropertyDescriptor directly, + * but the GWT compiler would need to have jackson on the classpath which isn't + * necessary. + */ +@JsonIgnoreProperties({ + "setMeasure", + "setDimension", + "setExcludeFromShifting", + "lookupDescription", + "fileType", + "updatedField", + "newField", + "renderUpdate", + "max", + "min", + "validators", // Must handle separately + "lookupValues", + "defaultTypeValue", + "sortOrder", + "value" +}) +public abstract class PropertyDescriptorMixin +{ + PropertyDescriptorMixin(@JsonProperty("PHI") StringProperty phi, @JsonProperty("URL") StringProperty url) + { } + @JsonProperty("PHI") + abstract void setPHI(String phi); // rename property on deserialize + @JsonProperty("URL") + abstract void setURL(String url); // rename property on deserialize +} \ No newline at end of file diff --git a/src/org/labkey/snd/PropertyValidatorMixin.java b/src/org/labkey/snd/PropertyValidatorMixin.java new file mode 100644 index 00000000..3909b8e8 --- /dev/null +++ b/src/org/labkey/snd/PropertyValidatorMixin.java @@ -0,0 +1,16 @@ +package org.labkey.snd; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Configures the fields that are not returned when serializing a GWTPropertyValidator. + * Ideally we would just add the @JsonIgnore annotations to GWTPropertyValidator directly, + * but the GWT compiler would need to have jackson on the classpath which isn't + * necessary. + */ +@JsonIgnoreProperties({ + "type" // Done outside jackson +}) +public abstract class PropertyValidatorMixin +{ +} diff --git a/src/org/labkey/snd/SNDController.java b/src/org/labkey/snd/SNDController.java index cd8417e2..81e5f724 100644 --- a/src/org/labkey/snd/SNDController.java +++ b/src/org/labkey/snd/SNDController.java @@ -16,6 +16,8 @@ package org.labkey.snd; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import org.json.JSONArray; import org.json.JSONObject; import org.labkey.api.action.ApiResponse; @@ -27,9 +29,11 @@ import org.labkey.api.action.SimpleApiJsonForm; import org.labkey.api.action.SimpleViewAction; import org.labkey.api.action.SpringActionController; -import org.labkey.api.exp.api.ExperimentService; import org.labkey.api.gwt.client.DefaultValueType; +import org.labkey.api.gwt.client.model.GWTConditionalFormat; import org.labkey.api.gwt.client.model.GWTPropertyDescriptor; +import org.labkey.api.gwt.client.model.GWTPropertyValidator; +import org.labkey.api.gwt.client.model.PropertyValidatorType; import org.labkey.api.module.Module; import org.labkey.api.security.RequiresLogin; import org.labkey.api.security.RequiresPermission; @@ -48,6 +52,7 @@ import org.labkey.api.snd.SNDService; import org.labkey.api.snd.SuperPackage; import org.labkey.api.util.DateUtil; +import org.labkey.api.util.JsonUtil; import org.labkey.api.util.URLHelper; import org.labkey.api.view.ActionURL; import org.labkey.api.view.JspView; @@ -58,6 +63,7 @@ import org.springframework.validation.Errors; import org.springframework.web.servlet.ModelAndView; +import java.io.IOException; import java.text.DecimalFormat; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -80,14 +86,86 @@ public SNDController() setActionResolver(_actionResolver); } - private Map getExtraFields(JSONArray jsonExtras) + private GWTPropertyValidator jsonToPropertyValidator(JSONObject obj) throws IOException + { + GWTPropertyValidator validator = createPropertyObjectMapper() + .readerFor(GWTPropertyValidator.class) + .readValue(obj.toString()); + + validator.setType(PropertyValidatorType.getType(obj.getString("type"))); + return validator; + } + + private GWTConditionalFormat jsonToConditionalFormatter(JSONObject obj) throws IOException + { + return createPropertyObjectMapper() + .readerFor(GWTConditionalFormat.class) + .readValue(obj.toString()); + } + + private GWTPropertyDescriptor jsonToPropertyDescriptor(JSONObject obj) throws IOException + { + GWTPropertyDescriptor prop = createPropertyObjectMapper() + .readerFor(GWTPropertyDescriptor.class) + .readValue(obj.toString()); + + // property validators + JSONArray jsonValidators = obj.optJSONArray("validators"); + if(null != jsonValidators) + { + List validators = new ArrayList<>(); + for (int i = 0; i < jsonValidators.length(); i++) + { + JSONObject jsonValidator = jsonValidators.getJSONObject(i); + if (null != jsonValidator) + { + validators.add(jsonToPropertyValidator(jsonValidator)); + } + } + prop.setPropertyValidators(validators); + } + + //conditional formats + JSONArray conditionalFormats = obj.optJSONArray("conditionalFormats"); + if (null != conditionalFormats) + { + List conditionalFormatters = new ArrayList<>(); + for (int j = 0; j < conditionalFormats.length(); j++) + { + JSONObject conditionalFormatter = conditionalFormats.getJSONObject(j); + if (null != conditionalFormatter) + { + conditionalFormatters.add(jsonToConditionalFormatter(conditionalFormatter)); + } + } + prop.setConditionalFormats(conditionalFormatters); + } + + return prop; + } + + static void configureObjectMapper(ObjectMapper om) + { + om.addMixIn(GWTPropertyDescriptor.class, PropertyDescriptorMixin.class); + om.addMixIn(GWTPropertyValidator.class, PropertyValidatorMixin.class); + om.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); + } + + protected ObjectMapper createPropertyObjectMapper() + { + ObjectMapper mapper = JsonUtil.DEFAULT_MAPPER.copy(); + configureObjectMapper(mapper); + return mapper; + } + + private Map getExtraFields(JSONArray jsonExtras) throws IOException { Map extras = new HashMap<>(); JSONObject extra; for (int e = 0; e < jsonExtras.length(); e++) { extra = jsonExtras.getJSONObject(e); - extras.put(ExperimentService.get().convertJsonToPropertyDescriptor(extra), extra.get("value")); + extras.put(jsonToPropertyDescriptor(extra), extra.get("value")); } return extras; @@ -106,7 +184,7 @@ public URLHelper getURL(Object o, Errors errors) @RequiresPermission(AdminPermission.class) public class SavePackageAction extends MutatingApiAction { - private GWTPropertyDescriptor convertJsonToPropertyDescriptor(JSONObject json, BindException errors) + private GWTPropertyDescriptor convertJsonToPropertyDescriptor(JSONObject json, BindException errors) throws IOException { String rangeUri = json.getString("rangeURI"); String format = json.getString("format"); @@ -144,7 +222,7 @@ else if (rangeUri.equals("string")) String defaultValue = (String) json.get("defaultValue"); json.put("defaultValue", defaultValue); - return ExperimentService.get().convertJsonToPropertyDescriptor(json); + return jsonToPropertyDescriptor(json); } @Override @@ -166,7 +244,14 @@ public ApiResponse execute(SimpleApiJsonForm form, BindException errors) JSONArray jsonExtras = json.optJSONArray("extraFields"); if (null != jsonExtras) { - pkg.setExtraFields(getExtraFields(jsonExtras)); + try + { + pkg.setExtraFields(getExtraFields(jsonExtras)); + } + catch (IOException e) + { + errors.reject(ERROR_MSG, "Extra fields could not be parsed: " + e.getMessage()); + } } // Get categories @@ -197,7 +282,14 @@ public ApiResponse execute(SimpleApiJsonForm form, BindException errors) break; } attNames.add(name); - pds.add(convertJsonToPropertyDescriptor(attribs.getJSONObject(i), errors)); + try + { + pds.add(convertJsonToPropertyDescriptor(attribs.getJSONObject(i), errors)); + } + catch (IOException e) + { + errors.reject("Unable to parse attribute property: " + e.getMessage()); + } } pkg.setAttributes(pds); } @@ -987,7 +1079,16 @@ public ApiResponse execute(SimpleApiJsonForm form, BindException errors) List eventData = null; JSONArray eventDataJson = json.has("eventData") ? json.getJSONArray("eventData") : null; if (eventDataJson != null) - eventData = parseEventData(eventDataJson); + { + try + { + eventData = parseEventData(eventDataJson); + } + catch (IOException e) + { + errors.reject(ERROR_MSG, "Event JSON data could not be parsed: " + e.getMessage()); + } + } Event event = new Event(eventId, subjectId, date, projectIdrev, note, eventData, getContainer()); event.setQcState(qcState); @@ -996,7 +1097,14 @@ public ApiResponse execute(SimpleApiJsonForm form, BindException errors) JSONArray jsonExtras = json.optJSONArray("extraFields"); if (null != jsonExtras) { - event.setExtraFields(getExtraFields(jsonExtras)); + try + { + event.setExtraFields(getExtraFields(jsonExtras)); + } + catch (IOException e) + { + errors.reject(ERROR_MSG, "Unable to parse extra fields: " + e.getMessage()); + } } Event savedEvent = SNDService.get().saveEvent(getContainer(), getUser(), event, validateOnly); @@ -1014,7 +1122,7 @@ public ApiResponse execute(SimpleApiJsonForm form, BindException errors) return response; } - private List parseEventData(JSONArray eventDataJson) + private List parseEventData(JSONArray eventDataJson) throws IOException { List eventDataList = new ArrayList<>(); From 8515c7ab5428d3e301fae307039b81da75d9dd0b Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Mon, 10 Jun 2019 16:39:36 -0700 Subject: [PATCH 2/2] Remove Jackson mixin fields not related to SND Fields copied over from Experiment module. Removed fields not applicable to SND. --- src/org/labkey/snd/PropertyDescriptorMixin.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/org/labkey/snd/PropertyDescriptorMixin.java b/src/org/labkey/snd/PropertyDescriptorMixin.java index 8c21c776..45b67fca 100644 --- a/src/org/labkey/snd/PropertyDescriptorMixin.java +++ b/src/org/labkey/snd/PropertyDescriptorMixin.java @@ -11,14 +11,6 @@ * necessary. */ @JsonIgnoreProperties({ - "setMeasure", - "setDimension", - "setExcludeFromShifting", - "lookupDescription", - "fileType", - "updatedField", - "newField", - "renderUpdate", "max", "min", "validators", // Must handle separately @@ -29,10 +21,8 @@ }) public abstract class PropertyDescriptorMixin { - PropertyDescriptorMixin(@JsonProperty("PHI") StringProperty phi, @JsonProperty("URL") StringProperty url) + PropertyDescriptorMixin(@JsonProperty("URL") StringProperty url) { } - @JsonProperty("PHI") - abstract void setPHI(String phi); // rename property on deserialize @JsonProperty("URL") abstract void setURL(String url); // rename property on deserialize } \ No newline at end of file