/** * Gets HTML5 parameter type for input field according to {@link Type} annotation. * * @return the type */ @Override public Type getHtmlInputFieldType() { final Type ret; if (inputAnnotation == null || inputAnnotation.value() == Type.FROM_JAVA) { if (isArrayOrCollection() || isRequestBody()) { ret = null; } else if (DataType.isNumber(getParameterType())) { ret = Type.NUMBER; } else { ret = Type.TEXT; } } else { ret = inputAnnotation.value(); } return ret; }
private void writePossiblePropertyValues( JsonGenerator jgen, String currentVocab, ActionInputParameter actionInputParameter, @SuppressWarnings("unused") Object[] possiblePropertyValues) throws IOException { // Enable the following to list possible values. // Problem: how to express individuals only for certain hydra:options // not all hydra:options should be taken as uris, sometimes they might be just literals // how to make that clear to the client? // maybe we must write them out for options // if (possiblePropertyValues.length > 0) { // jgen.writeArrayFieldStart("hydra:option"); // // for (Object possibleValue : possiblePropertyValues) { // // TODO: apply "hydra:option" : { "@type": "@vocab"} to context for enums // writeScalarValue(jgen, possibleValue, // actionInputParameter.getParameterType()); // } // jgen.writeEndArray(); // } if (actionInputParameter.isArrayOrCollection()) { jgen.writeBooleanField( getPropertyOrClassNameInVocab( currentVocab, "multipleValues", LdContextFactory.HTTP_SCHEMA_ORG, "schema:"), true); } // valueRequired (hard to say, using @Access on Event is for all update requests - or make // specific request beans for different // purposes rather than always passing an instance of e.g. Event?) // -> update is a different use case than create - or maybe have an // @Requires("eventStatus") // annotation alongside requestBody to tell which attributes are required or writable, // and use Requires over // bean structure, where ctor with least length of args is required and setters are // supported // but optional? The bean structure does say what is writable for updates, but not what // is required for creation. Right now setters are supportedProperties. For creation we would // have to add constructor arguments as supportedProperties. // (/) defaultValue (pre-filled value, e.g. list of selected items for option) // valueName (for iri templates only) // (/) readonlyValue (true for final public field or absence of setter, send fixed value like // hidden field?) -> use hydra:readable, hydra:writable // (/) multipleValues // (/) valueMinLength // (/) valueMaxLength // (/) valuePattern // minValue (DateTime support) // maxValue (DateTime support) // (/) stepValue final Map<String, Object> inputConstraints = actionInputParameter.getInputConstraints(); if (actionInputParameter.hasCallValue()) { if (actionInputParameter.isArrayOrCollection()) { Object[] callValues = actionInputParameter.getCallValues(); Class<?> componentType = callValues.getClass().getComponentType(); // only write defaultValue for array of scalars if (DataType.isSingleValueType(componentType)) { jgen.writeFieldName( getPropertyOrClassNameInVocab( currentVocab, "defaultValue", LdContextFactory.HTTP_SCHEMA_ORG, "schema:")); jgen.writeStartArray(); for (Object callValue : callValues) { writeScalarValue(jgen, callValue, componentType); } jgen.writeEndArray(); } } else { jgen.writeFieldName( getPropertyOrClassNameInVocab( currentVocab, "defaultValue", LdContextFactory.HTTP_SCHEMA_ORG, "schema:")); writeScalarValue( jgen, actionInputParameter.getCallValue(), actionInputParameter.getParameterType()); } } if (!inputConstraints.isEmpty()) { final List<String> keysToAppendValue = Arrays.asList(Input.MAX, Input.MIN, Input.STEP); for (String keyToAppendValue : keysToAppendValue) { final Object constraint = inputConstraints.get(keyToAppendValue); if (constraint != null) { jgen.writeFieldName( getPropertyOrClassNameInVocab( currentVocab, keyToAppendValue + "Value", LdContextFactory.HTTP_SCHEMA_ORG, "schema:")); jgen.writeNumber(constraint.toString()); } } final List<String> keysToPrependValue = Arrays.asList(Input.MAX_LENGTH, Input.MIN_LENGTH, Input.PATTERN); for (String keyToPrependValue : keysToPrependValue) { final Object constraint = inputConstraints.get(keyToPrependValue); if (constraint != null) { jgen.writeFieldName( getPropertyOrClassNameInVocab( currentVocab, "value" + StringUtils.capitalize(keyToPrependValue), LdContextFactory.HTTP_SCHEMA_ORG, "schema:")); if (Input.PATTERN.equals(keyToPrependValue)) { jgen.writeString(constraint.toString()); } else { jgen.writeNumber(constraint.toString()); } } } } }
private void recurseSupportedProperties( JsonGenerator jgen, String currentVocab, Class<?> beanType, ActionDescriptor actionDescriptor, ActionInputParameter actionInputParameter, Object currentCallValue) throws IntrospectionException, IOException { // TODO support Option provider by other method args? final BeanInfo beanInfo = Introspector.getBeanInfo(beanType); final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); // TODO collection and map for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { final Method writeMethod = propertyDescriptor.getWriteMethod(); if (writeMethod == null) { continue; } final Class<?> propertyType = propertyDescriptor.getPropertyType(); // TODO: the property name must be a valid URI - need to check context for terms? String propertyName = getWritableExposedPropertyOrPropertyName(propertyDescriptor); if (DataType.isSingleValueType(propertyType)) { final Property property = new Property( beanType, propertyDescriptor.getReadMethod(), propertyDescriptor.getWriteMethod(), propertyDescriptor.getName()); Object propertyValue = PropertyUtils.getPropertyValue(currentCallValue, propertyDescriptor); MethodParameter methodParameter = new MethodParameter(propertyDescriptor.getWriteMethod(), 0); ActionInputParameter propertySetterInputParameter = new ActionInputParameter(methodParameter, propertyValue); final Object[] possiblePropertyValues = actionInputParameter.getPossibleValues( propertyDescriptor.getWriteMethod(), 0, actionDescriptor); writeSupportedProperty( jgen, currentVocab, propertySetterInputParameter, propertyName, property, possiblePropertyValues); } else { jgen.writeStartObject(); jgen.writeStringField("hydra:property", propertyName); // TODO: is the property required -> for bean props we need the Access annotation to express // that jgen.writeObjectFieldStart( getPropertyOrClassNameInVocab( currentVocab, "rangeIncludes", LdContextFactory.HTTP_SCHEMA_ORG, "schema:")); Expose expose = AnnotationUtils.getAnnotation(propertyType, Expose.class); String subClass; if (expose != null) { subClass = expose.value(); } else { subClass = propertyType.getSimpleName(); } jgen.writeStringField( getPropertyOrClassNameInVocab( currentVocab, "subClassOf", "http://www.w3.org/2000/01/rdf-schema#", "rdfs:"), subClass); jgen.writeArrayFieldStart("hydra:supportedProperty"); Object propertyValue = PropertyUtils.getPropertyValue(currentCallValue, propertyDescriptor); recurseSupportedProperties( jgen, currentVocab, propertyType, actionDescriptor, actionInputParameter, propertyValue); jgen.writeEndArray(); jgen.writeEndObject(); jgen.writeEndObject(); } } }
/** * Determines if action input parameter is an array or collection. * * @return true if array or collection */ public boolean isArrayOrCollection() { if (arrayOrCollection == null) { arrayOrCollection = DataType.isArrayOrCollection(getParameterType()); } return arrayOrCollection; }