private ItemReader resolveRecord(RecordSchema schema) { ItemReader recordReader = _recordReaders.get(schema); if (recordReader != null) { return recordReader; } RecordAccess recordAccess = getRecordAccess(schema); List<FieldReader> fieldReaders = new ArrayList<FieldReader>(); recordReader = new RecordReader(recordAccess, fieldReaders); _recordReaders.put(schema, recordReader); for (Field field : schema) { ItemReader itemReader = resolveReader(field.getSchema()); boolean reuse = isReusable(field.getSchema().getType()); FieldReader fieldReader = new FieldReader(field, recordAccess, itemReader, reuse); fieldReaders.add(fieldReader); } return recordReader; }
@Override public Schema element(JsonValue which) { if (which instanceof JsonString) { Field field = getField((JsonString) which); if (field == null) { return additional; } else { return field.getSchema(); // TODO: add nullability when optional? } } return null; }
public static List<DynamicFormField> getFields(String jsonString, UserRetriever retriever) { LOG.debug("Starting JSON field description deserialization."); try { ObjectMapper objectMapper = getLenientObjectMapper(); JiraJsonMetadataResponse response = objectMapper.readValue(jsonString, JiraJsonMetadataResponse.class); if (response.projects.size() == 0) { throw new IllegalStateRestException( "No projects were found. " + "Bad permissions can cause this error. Please check your configuration."); } assert response.projects.size() == 1 : "The response contained more than one project. Something went wrong."; Project project = response.getProjectOrNull(); List<DynamicFormField> fieldList = list(); if (project != null) { DynamicFormField issueTypeField = new DynamicFormField(); issueTypeField.setRequired(true); issueTypeField.setName("issuetype"); issueTypeField.setLabel("Issue Type"); issueTypeField.setActive(true); issueTypeField.setEditable(true); issueTypeField.setType("select"); Map<String, String> issueTypeValuesMap = map(); addField(issueTypeField, fieldList, null); Set<String> timetrackingSet = set(); for (IssueType issueType : project.getIssuetypes()) { issueTypeValuesMap.put(issueType.getId(), issueType.getName()); for (Map.Entry<String, Field> entry : issueType.getFields().entrySet()) { Field jsonField = entry.getValue(); String type = jsonField.getSchema().getType(); if ("issuelink".equals(type)) type = "string"; if ("array".equals(type) && "attachment".equals(jsonField.getSchema().getItems())) { continue; // you can't make attachments required and we don't support uploads. } DynamicFormField field = new DynamicFormField(); field.setShow("issuetype=" + issueType.getId()); field.setRequired(jsonField.isRequired()); field.setName(entry.getKey()); field.setLabel(jsonField.getName()); field.setActive(true); field.setEditable(true); if (jsonField.getAllowedValues() != null && !jsonField.getAllowedValues().isEmpty()) { if (MULTISELECT.equals(jsonField.getSchema().getCustom())) { field.setSupportsMultivalue(true); } if (MULTI_CHECKBOX.equals(jsonField.getSchema().getCustom())) { field.setSupportsMultivalue(true); field.setType("checklist"); } else if (CASCADING_SELECT.equals(jsonField.getSchema().getCustom())) { field.setType("select"); } else { field.setType("select"); } field.setOptionsMap(jsonField.getOptionsMap()); } else if (type.equals("timetracking")) { LOG.debug("Adding timetracking fields (x2)"); if (timetrackingSet.contains(entry.getKey())) { continue; // otherwise we will have duplicates } else { timetrackingSet.add(entry.getKey()); } DynamicFormField originalEstimate = new DynamicFormField(); originalEstimate.setRequired(jsonField.isRequired()); originalEstimate.setName("timetracking_originalestimate"); originalEstimate.setLabel("Original Estimate"); originalEstimate.setActive(true); originalEstimate.setEditable(true); originalEstimate.setValidate(TIMETRACKING_REGEX); originalEstimate.setType("text"); originalEstimate.setPlaceholder(PLACEHOLDER_TEXT); originalEstimate.setError("pattern", TIMETRACKING_ERROR); fieldList.add(originalEstimate); DynamicFormField remainingEstimate = new DynamicFormField(); remainingEstimate.setRequired(jsonField.isRequired()); remainingEstimate.setName("timetracking_remainingestimate"); remainingEstimate.setLabel("Remaining Estimate"); remainingEstimate.setActive(true); remainingEstimate.setValidate(TIMETRACKING_REGEX); remainingEstimate.setPlaceholder(PLACEHOLDER_TEXT); remainingEstimate.setEditable(true); remainingEstimate.setType("text"); remainingEstimate.setError("pattern", TIMETRACKING_ERROR); fieldList.add(remainingEstimate); continue; } else if (type.equals("string")) { if (URL_TYPE.equals(jsonField.getSchema().getCustom())) { field.setType("url"); } else if (TEXTAREA_TYPE.equals(jsonField.getSchema().getCustom())) { field.setType("textarea"); } else { field.setType("text"); } } else if (type.equals("number")) { if (FLOAT_TYPE.equals(jsonField.getSchema().getCustom())) { field.setValidate(FLOAT_REGEX); field.setType("text"); field.setError("pattern", "Must be float format (ex. 3.14)"); } else { field.setType("number"); } } else if (type.equals("date") || type.equals("datetime")) { field.setType("date"); } else if (type.equals("array") && jsonField.getSchema().getItems().equals("string")) { field.setType("text"); field.setSupportsMultivalue(true); } else if (type.equals("user")) { field.setType("select"); Map<String, String> map = retriever.getUserMap(); if (map == null) { field.setType("text"); } else field.setOptionsMap(map); } else if (type.equals("array")) { LOG.error( "Unable to determine dynamic type for " + entry.getKey() + ":" + type + " of " + jsonField.getSchema().getItems()); field.setType("select"); } LOG.debug( "Adding new field with label " + field.getLabel() + " and type " + field.getType()); addField(field, fieldList, issueTypeField); } } issueTypeField.setOptionsMap(issueTypeValuesMap); } return fieldList; } catch (IOException e) { LOG.error("Failed to deserialize JSON."); LOG.debug("Failing JSON: " + jsonString, e); throw new RestIOException(e, "Unable to parse server response."); } }
@Override public boolean matches(JsonValue value) throws Exception { if (!(value instanceof JsonRecord)) { return false; } JsonRecord rec = (JsonRecord) value; // assumption: field names are sorted int nr = rec.size(); // number of fields in record int ns = fieldsByName.length; // number of fields in schema int pr = 0; // current field in record int ps = 0; // current field in schema // zip join Iterator<Entry<JsonString, JsonValue>> recIt = rec.iteratorSorted(); Entry<JsonString, JsonValue> recEntry = null; if (nr > 0) recEntry = recIt.next(); while (pr < nr && ps < ns) { Field schemaField = fieldsByName[ps]; JsonString recordFieldName = recEntry.getKey(); // compare int cmp = schemaField.getName().compareTo(recordFieldName); if (cmp < 0) { // field is in schema but not in record if (!schemaField.isOptional) { return false; } ps++; } else if (cmp == 0) { // field is schema and in record if (!schemaField.getSchema().matches(recEntry.getValue())) { return false; } ps++; pr++; if (pr < nr) { assert recIt.hasNext(); recEntry = recIt.next(); } } else { // field is not in schema but in record if (additional == null || !additional.matches(recEntry.getValue())) { return false; } pr++; if (pr < nr) { assert recIt.hasNext(); recEntry = recIt.next(); } } } // only one of them still has fields, i.e., the while loops are exclusive while (pr < nr) { // there are fields left in the record if (additional == null || !additional.matches(recEntry.getValue())) { return false; } pr++; if (pr < nr) { assert recIt.hasNext(); recEntry = recIt.next(); } } assert !recIt.hasNext(); while (ps < ns) { // therea are fields left in the schema if (!fieldsByName[ps].isOptional) { return false; } ps++; } // everything ok return true; }