spring-hateoas-ext/src/main/java/de/escalon/hypermedia/spring/xhtml/XhtmlWriter.java
private void recurseBeanProperties(Class<?> beanType, ActionDescriptor actionDescriptor, ActionInputParameter
            actionInputParameter, Object currentCallValue, String parentParamName) throws IOException {
        // TODO support Option provider by other method args?
        final BeanInfo beanInfo = getBeanInfo(beanType);
        final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        // TODO collection and map

        // TODO: do not add two inputs for setter and ctor

        // TODO almost duplicate of HtmlResourceMessageConverter.recursivelyCreateObject
        if (RequestMethod.POST == RequestMethod.valueOf(actionDescriptor.getHttpMethod())) {
            try {
                Constructor[] constructors = beanType.getConstructors();
                // find default ctor
                Constructor constructor = PropertyUtils.findDefaultCtor(constructors);
                // find ctor with JsonCreator ann
                if (constructor == null) {
                    constructor = PropertyUtils.findJsonCreator(constructors, JsonCreator.class);
                }
                Assert.notNull(constructor, "no default constructor or JsonCreator found for type " + beanType
                        .getName());
                int parameterCount = constructor.getParameterTypes().length;
                if (parameterCount > 0) {
                    Annotation[][] annotationsOnParameters = constructor.getParameterAnnotations();

                    Class[] parameters = constructor.getParameterTypes();
                    int paramIndex = 0;
                    for (Annotation[] annotationsOnParameter : annotationsOnParameters) {
                        for (Annotation annotation : annotationsOnParameter) {
                            if (JsonProperty.class == annotation.annotationType()) {
                                JsonProperty jsonProperty = (JsonProperty) annotation;
                                // TODO use required attribute of JsonProperty
                                String paramName = jsonProperty.value();
                                Class parameterType = parameters[paramIndex];

                                // TODO duplicate below for PropertyDescriptors and in appendForm
                                if (DataType.isSingleValueType(parameterType)) {

                                    if (actionInputParameter.isIncluded(paramName)) {

                                        Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue,
                                                paramName);

                                        ActionInputParameter constructorParamInputParameter = new
												SpringActionInputParameter
                                                (new MethodParameter(constructor, paramIndex), propertyValue);

                                        final Object[] possibleValues =
                                                actionInputParameter.getPossibleValues(
                                                        constructor, paramIndex, actionDescriptor);

                                        appendInputOrSelect(actionInputParameter, parentParamName + paramName,
                                                constructorParamInputParameter, possibleValues);
                                    }
                                } else if (DataType.isArrayOrCollection(parameterType)) {
                                    Object[] callValues = actionInputParameter.getValues();
                                    int items = callValues.length;
                                    for (int i = 0; i < items; i++) {
                                        Object value;
                                        if (i < callValues.length) {
                                            value = callValues[i];
                                        } else {
                                            value = null;
                                        }
                                        recurseBeanProperties(actionInputParameter.getParameterType(),
                                                actionDescriptor, actionInputParameter, value, parentParamName);
                                    }
                                } else {
                                    beginDiv();
                                    write(paramName + ":");
                                    Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue,
                                            paramName);
                                    recurseBeanProperties(parameterType, actionDescriptor, actionInputParameter,
                                            propertyValue, paramName + ".");
                                    endDiv();
                                }
                                paramIndex++; // increase for each @JsonProperty
                            }
                        }
                    }
                    Assert.isTrue(parameters.length == paramIndex,
                            "not all constructor arguments of @JsonCreator " + constructor.getName() +
                                    " are annotated with @JsonProperty");
                }
            } catch (Exception e) {
                throw new RuntimeException("Failed to write input fields for constructor", e);
            }
        } else { // non-POST

            // TODO non-writable properties and public fields: make sure the inputs are part of a form
            // write input field for every setter
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                final Method writeMethod = propertyDescriptor.getWriteMethod();
                if (writeMethod == null) {
                    continue;
                }
                final Class<?> propertyType = propertyDescriptor.getPropertyType();

                String propertyName = propertyDescriptor.getName();

                if (DataType.isSingleValueType(propertyType)) {

                    final Property property = new Property(beanType, propertyDescriptor.getReadMethod(),
                            propertyDescriptor.getWriteMethod(), propertyDescriptor.getName());

                    Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue, propertyName);
                    MethodParameter methodParameter = new MethodParameter(propertyDescriptor.getWriteMethod(), 0);
                    ActionInputParameter propertySetterInputParameter = new SpringActionInputParameter(methodParameter,
                            propertyValue);
                    final Object[] possibleValues = actionInputParameter.getPossibleValues(propertyDescriptor
                                    .getWriteMethod(), 0,
                            actionDescriptor);
                    appendInputOrSelect(actionInputParameter, propertyName, propertySetterInputParameter,
							possibleValues);
                } else if (actionInputParameter.isArrayOrCollection()) {
                    Object[] callValues = actionInputParameter.getValues();
                    int items = callValues.length;
                    for (int i = 0; i < items; i++) {
                        Object value;
                        if (i < callValues.length) {
                            value = callValues[i];
                        } else {
                            value = null;
                        }
                        recurseBeanProperties(actionInputParameter.getParameterType(), actionDescriptor,
                                actionInputParameter, value, parentParamName);
                    }
                } else {
                    beginDiv();
                    write(propertyName + ":");
                    Object propertyValue = PropertyUtils.getPropertyValue(currentCallValue, propertyDescriptor);
                    recurseBeanProperties(propertyType, actionDescriptor, actionInputParameter, propertyValue,
							parentParamName);
                    endDiv();
                }
            }
        }
    }
private void recurseBeanProperties(Class<?> beanType, ActionDescriptor actionDescriptor, ActionInputParameter
            actionInputParameter, Object currentCallValue, String parentParamName) throws IOException {
        // TODO support Option provider by other method args?
        final BeanInfo beanInfo = getBeanInfo(beanType);
        final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        // TODO collection and map

        // TODO: do not add two inputs for setter and ctor

        // TODO almost duplicate of HtmlResourceMessageConverter.recursivelyCreateObject
        if (RequestMethod.POST == RequestMethod.valueOf(actionDescriptor.getHttpMethod())) {
            try {
                Constructor[] constructors = beanType.getConstructors();
                // find default ctor
                Constructor constructor = PropertyUtils.findDefaultCtor(constructors);
                // find ctor with JsonCreator ann
                if (constructor == null) {
                    constructor = PropertyUtils.findJsonCreator(constructors, JsonCreator.class);
                }
                Assert.notNull(constructor, "no default constructor or JsonCreator found for type " + beanType
                        .getName());
                int parameterCount = constructor.getParameterTypes().length;
                if (parameterCount > 0) {
                    Annotation[][] annotationsOnParameters = constructor.getParameterAnnotations();

                    Class[] parameters = constructor.getParameterTypes();
                    int paramIndex = 0;
                    for (Annotation[] annotationsOnParameter : annotationsOnParameters) {
                        for (Annotation annotation : annotationsOnParameter) {
                            if (JsonProperty.class == annotation.annotationType()) {
                                JsonProperty jsonProperty = (JsonProperty) annotation;
                                // TODO use required attribute of JsonProperty
                                String paramName = jsonProperty.value();
                                Class parameterType = parameters[paramIndex];

                                // TODO duplicate below for PropertyDescriptors and in appendForm
                                if (DataType.isSingleValueType(parameterType)) {

                                    if (actionInputParameter.isIncluded(paramName)) {

                                        Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue,
                                                paramName);

                                        ActionInputParameter constructorParamInputParameter = new
												SpringActionInputParameter
                                                (new MethodParameter(constructor, paramIndex), propertyValue);

                                        final Object[] possibleValues =
                                                actionInputParameter.getPossibleValues(
                                                        constructor, paramIndex, actionDescriptor);

                                        appendInputOrSelect(actionInputParameter, parentParamName + paramName,
                                                constructorParamInputParameter, possibleValues);
                                    }
                                } else if (DataType.isArrayOrCollection(parameterType)) {
                                    Object[] callValues = actionInputParameter.getValues();
                                    int items = callValues.length;
                                    for (int i = 0; i < items; i++) {
                                        Object value;
                                        if (i < callValues.length) {
                                            value = callValues[i];
                                        } else {
                                            value = null;
                                        }
                                        recurseBeanProperties(actionInputParameter.getParameterType(),
                                                actionDescriptor, actionInputParameter, value, parentParamName);
                                    }
                                } else {
                                    beginDiv();
                                    write(paramName + ":");
                                    Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue,
                                            paramName);
                                    recurseBeanProperties(parameterType, actionDescriptor, actionInputParameter,
                                            propertyValue, paramName + ".");
                                    endDiv();
                                }
                                paramIndex++; // increase for each @JsonProperty
                            }
                        }
                    }
                    Assert.isTrue(parameters.length == paramIndex,
                            "not all constructor arguments of @JsonCreator " + constructor.getName() +
                                    " are annotated with @JsonProperty");
                }
            } catch (Exception e) {
                throw new RuntimeException("Failed to write input fields for constructor", e);
            }
        } else { // non-POST

            // TODO non-writable properties and public fields: make sure the inputs are part of a form
            // write input field for every setter
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                final Method writeMethod = propertyDescriptor.getWriteMethod();
                if (writeMethod == null) {
                    continue;
                }
                final Class<?> propertyType = propertyDescriptor.getPropertyType();

                String propertyName = propertyDescriptor.getName();

                if (DataType.isSingleValueType(propertyType)) {

                    final Property property = new Property(beanType, propertyDescriptor.getReadMethod(),
                            propertyDescriptor.getWriteMethod(), propertyDescriptor.getName());

                    Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue, propertyName);
                    MethodParameter methodParameter = new MethodParameter(propertyDescriptor.getWriteMethod(), 0);
                    ActionInputParameter propertySetterInputParameter = new SpringActionInputParameter(methodParameter,
                            propertyValue);
                    final Object[] possibleValues = actionInputParameter.getPossibleValues(propertyDescriptor
                                    .getWriteMethod(), 0,
                            actionDescriptor);
                    appendInputOrSelect(actionInputParameter, propertyName, propertySetterInputParameter,
							possibleValues);
                } else if (actionInputParameter.isArrayOrCollection()) {
                    Object[] callValues = actionInputParameter.getValues();
                    int items = callValues.length;
                    for (int i = 0; i < items; i++) {
                        Object value;
                        if (i < callValues.length) {
                            value = callValues[i];
                        } else {
                            value = null;
                        }
                        recurseBeanProperties(actionInputParameter.getParameterType(), actionDescriptor,
                                actionInputParameter, value, parentParamName);
                    }
                } else {
                    beginDiv();
                    write(propertyName + ":");
                    Object propertyValue = PropertyUtils.getPropertyValue(currentCallValue, propertyDescriptor);
                    recurseBeanProperties(propertyType, actionDescriptor, actionInputParameter, propertyValue,
							parentParamName);
                    endDiv();
                }
            }
        }
    }
Renders input fields for bean properties of bean to add or update or patch.

@param beanType
to render
@param actionDescriptor
which describes the method
@param actionInputParameter
which requires the bean
@param currentCallValue
sample call value
@throws IOException