hydra-spring/src/main/java/de/escalon/hypermedia/spring/hydra/LinkListSerializer.java
private void recurseSupportedProperties(JsonGenerator jgen, String currentVocab, Class<?>
            valueType, ActionDescriptor allRootParameters,
                                            ActionInputParameter rootParameter, Object currentCallValue,
                                            String propertyPath)
            throws IntrospectionException,
            IOException {


        Map<String, ActionInputParameter> properties = new HashMap<String, ActionInputParameter>();

        // collect supported properties from ctor

        Constructor[] constructors = valueType.getConstructors();
        // find default ctor
        Constructor constructor = PropertyUtils.findDefaultCtor(constructors);
        // find ctor with JsonCreator ann
        if (constructor == null) {
            constructor = PropertyUtils.findJsonCreator(constructors, JsonCreator.class);
        }
        if (constructor == null) {
            // TODO this can be a generic collection, find a way to describe it
            LOG.warn("can't describe supported properties, no default constructor or JsonCreator found for type " + valueType
                    .getName());
            return;
        }

        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();

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

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

                        // TODO collect ctor params, setter params and process
                        // TODO then handle single, collection and bean for both
                        properties.put(paramName, constructorParamInputParameter);
                        paramIndex++; // increase for each @JsonProperty
                    }
                }
            }
            Assert.isTrue(parameters.length == paramIndex,
                    "not all constructor arguments of @JsonCreator " + constructor.getName() +
                            " are annotated with @JsonProperty");
        }

        // collect supported properties from setters

        // TODO support Option provider by other method args?
        final BeanInfo beanInfo = Introspector.getBeanInfo(valueType);
        final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        // TODO collection and map
        // TODO distinguish which properties should be printed as supported - now just setters
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            final Method writeMethod = propertyDescriptor.getWriteMethod();
            if (writeMethod == null) {
                continue;
            }
            // TODO: the property name must be a valid URI - need to check context for terms?
            String propertyName = getWritableExposedPropertyOrPropertyName(propertyDescriptor);

            Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue, propertyDescriptor
                    .getName());

            MethodParameter methodParameter = new MethodParameter(propertyDescriptor.getWriteMethod(), 0);
            ActionInputParameter propertySetterInputParameter = new SpringActionInputParameter(
                    methodParameter, propertyValue);

            properties.put(propertyName, propertySetterInputParameter);
        }

        // write all supported properties
        // TODO we are using the annotatedParameter.parameterName but should use the key of properties here:
        for (ActionInputParameter annotatedParameter : properties.values()) {
            String nextPropertyPathLevel = propertyPath.isEmpty() ? annotatedParameter.getParameterName() :
                    propertyPath + '.' + annotatedParameter.getParameterName();
            Class<?> parameterType = annotatedParameter.getParameterType();
            if (DataType.isSingleValueType(parameterType)) {

                final Object[] possiblePropertyValues = rootParameter.getPossibleValues(allRootParameters);

                if (rootParameter.isIncluded(nextPropertyPathLevel) && !rootParameter.isExcluded
                        (nextPropertyPathLevel)) {
                    writeSupportedProperty(jgen, currentVocab, annotatedParameter,
                            annotatedParameter.getParameterName(), possiblePropertyValues);
                }
                // TODO collections?
                //                        } else if (DataType.isArrayOrCollection(parameterType)) {
                //                            Object[] callValues = rootParameter.getValues();
                //                            int items = callValues.length;
                //                            for (int i = 0; i < items; i++) {
                //                                Object value;
                //                                if (i < callValues.length) {
                //                                    value = callValues[i];
                //                                } else {
                //                                    value = null;
                //                                }
                //                                recurseSupportedProperties(jgen, currentVocab, rootParameter
                // .getParameterType(),
                //                                        allRootParameters, rootParameter, value);
                //                            }
            } else {
                jgen.writeStartObject();
                jgen.writeStringField("hydra:property", annotatedParameter.getParameterName());
                // TODO: is the property required -> for bean props we need the Access annotation to express that

                Expose expose = AnnotationUtils.getAnnotation(parameterType, Expose.class);
                String subClass = null;
                if (expose != null) {
                    subClass = expose.value();
                } else {
                    if (List.class.isAssignableFrom(parameterType)) {
                        Type genericParameterType = annotatedParameter.getGenericParameterType();
                        if (genericParameterType instanceof ParameterizedType) {
                            ParameterizedType parameterizedType = (ParameterizedType) genericParameterType;
                            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                            if (actualTypeArguments.length == 1) {
                                Type actualTypeArgument = actualTypeArguments[0];
                                if (actualTypeArgument instanceof Class) {
                                    parameterType = (Class<?>) actualTypeArgument;
                                    subClass = parameterType.getSimpleName();
                                } else if (actualTypeArgument instanceof ParameterizedType) {
                                    ParameterizedType genericItemType = (ParameterizedType) actualTypeArgument;
                                    Type rawType = genericItemType.getRawType();
                                    if (rawType instanceof Class) {
                                        parameterType = (Class<?>) rawType;
                                        subClass = parameterType.getSimpleName();
                                    }
                                }
                            }
                        }
                        if (subClass != null) {
                            String multipleValueProp = getPropertyOrClassNameInVocab(currentVocab,
                                    "multipleValues",
                                    LdContextFactory.HTTP_SCHEMA_ORG,
                                    "schema:");
                            jgen.writeBooleanField(multipleValueProp, true);
                        }
                    }
                }
                if (subClass == null) {
                    subClass = parameterType.getSimpleName();
                }
                jgen.writeObjectFieldStart(getPropertyOrClassNameInVocab(currentVocab, "rangeIncludes",
                        LdContextFactory.HTTP_SCHEMA_ORG, "schema:"));

                jgen.writeStringField(getPropertyOrClassNameInVocab(currentVocab,
                        "subClassOf",
                        "http://www.w3.org/2000/01/rdf-schema#",
                        "rdfs:"), subClass);

                jgen.writeArrayFieldStart("hydra:supportedProperty");
                // TODO let defaultValue be an filled list, if needed
                Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue, annotatedParameter
                        .getParameterName());

                recurseSupportedProperties(jgen, currentVocab, parameterType,
                        allRootParameters,
                        rootParameter, propertyValue, nextPropertyPathLevel);
                jgen.writeEndArray();

                jgen.writeEndObject();
                jgen.writeEndObject();
            }
        }

    }
private void recurseSupportedProperties(JsonGenerator jgen, String currentVocab, Class<?>
            valueType, ActionDescriptor allRootParameters,
                                            ActionInputParameter rootParameter, Object currentCallValue,
                                            String propertyPath)
            throws IntrospectionException,
            IOException {


        Map<String, ActionInputParameter> properties = new HashMap<String, ActionInputParameter>();

        // collect supported properties from ctor

        Constructor[] constructors = valueType.getConstructors();
        // find default ctor
        Constructor constructor = PropertyUtils.findDefaultCtor(constructors);
        // find ctor with JsonCreator ann
        if (constructor == null) {
            constructor = PropertyUtils.findJsonCreator(constructors, JsonCreator.class);
        }
        if (constructor == null) {
            // TODO this can be a generic collection, find a way to describe it
            LOG.warn("can't describe supported properties, no default constructor or JsonCreator found for type " + valueType
                    .getName());
            return;
        }

        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();

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

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

                        // TODO collect ctor params, setter params and process
                        // TODO then handle single, collection and bean for both
                        properties.put(paramName, constructorParamInputParameter);
                        paramIndex++; // increase for each @JsonProperty
                    }
                }
            }
            Assert.isTrue(parameters.length == paramIndex,
                    "not all constructor arguments of @JsonCreator " + constructor.getName() +
                            " are annotated with @JsonProperty");
        }

        // collect supported properties from setters

        // TODO support Option provider by other method args?
        final BeanInfo beanInfo = Introspector.getBeanInfo(valueType);
        final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        // TODO collection and map
        // TODO distinguish which properties should be printed as supported - now just setters
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            final Method writeMethod = propertyDescriptor.getWriteMethod();
            if (writeMethod == null) {
                continue;
            }
            // TODO: the property name must be a valid URI - need to check context for terms?
            String propertyName = getWritableExposedPropertyOrPropertyName(propertyDescriptor);

            Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue, propertyDescriptor
                    .getName());

            MethodParameter methodParameter = new MethodParameter(propertyDescriptor.getWriteMethod(), 0);
            ActionInputParameter propertySetterInputParameter = new SpringActionInputParameter(
                    methodParameter, propertyValue);

            properties.put(propertyName, propertySetterInputParameter);
        }

        // write all supported properties
        // TODO we are using the annotatedParameter.parameterName but should use the key of properties here:
        for (ActionInputParameter annotatedParameter : properties.values()) {
            String nextPropertyPathLevel = propertyPath.isEmpty() ? annotatedParameter.getParameterName() :
                    propertyPath + '.' + annotatedParameter.getParameterName();
            Class<?> parameterType = annotatedParameter.getParameterType();
            if (DataType.isSingleValueType(parameterType)) {

                final Object[] possiblePropertyValues = rootParameter.getPossibleValues(allRootParameters);

                if (rootParameter.isIncluded(nextPropertyPathLevel) && !rootParameter.isExcluded
                        (nextPropertyPathLevel)) {
                    writeSupportedProperty(jgen, currentVocab, annotatedParameter,
                            annotatedParameter.getParameterName(), possiblePropertyValues);
                }
                // TODO collections?
                //                        } else if (DataType.isArrayOrCollection(parameterType)) {
                //                            Object[] callValues = rootParameter.getValues();
                //                            int items = callValues.length;
                //                            for (int i = 0; i < items; i++) {
                //                                Object value;
                //                                if (i < callValues.length) {
                //                                    value = callValues[i];
                //                                } else {
                //                                    value = null;
                //                                }
                //                                recurseSupportedProperties(jgen, currentVocab, rootParameter
                // .getParameterType(),
                //                                        allRootParameters, rootParameter, value);
                //                            }
            } else {
                jgen.writeStartObject();
                jgen.writeStringField("hydra:property", annotatedParameter.getParameterName());
                // TODO: is the property required -> for bean props we need the Access annotation to express that

                Expose expose = AnnotationUtils.getAnnotation(parameterType, Expose.class);
                String subClass = null;
                if (expose != null) {
                    subClass = expose.value();
                } else {
                    if (List.class.isAssignableFrom(parameterType)) {
                        Type genericParameterType = annotatedParameter.getGenericParameterType();
                        if (genericParameterType instanceof ParameterizedType) {
                            ParameterizedType parameterizedType = (ParameterizedType) genericParameterType;
                            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                            if (actualTypeArguments.length == 1) {
                                Type actualTypeArgument = actualTypeArguments[0];
                                if (actualTypeArgument instanceof Class) {
                                    parameterType = (Class<?>) actualTypeArgument;
                                    subClass = parameterType.getSimpleName();
                                } else if (actualTypeArgument instanceof ParameterizedType) {
                                    ParameterizedType genericItemType = (ParameterizedType) actualTypeArgument;
                                    Type rawType = genericItemType.getRawType();
                                    if (rawType instanceof Class) {
                                        parameterType = (Class<?>) rawType;
                                        subClass = parameterType.getSimpleName();
                                    }
                                }
                            }
                        }
                        if (subClass != null) {
                            String multipleValueProp = getPropertyOrClassNameInVocab(currentVocab,
                                    "multipleValues",
                                    LdContextFactory.HTTP_SCHEMA_ORG,
                                    "schema:");
                            jgen.writeBooleanField(multipleValueProp, true);
                        }
                    }
                }
                if (subClass == null) {
                    subClass = parameterType.getSimpleName();
                }
                jgen.writeObjectFieldStart(getPropertyOrClassNameInVocab(currentVocab, "rangeIncludes",
                        LdContextFactory.HTTP_SCHEMA_ORG, "schema:"));

                jgen.writeStringField(getPropertyOrClassNameInVocab(currentVocab,
                        "subClassOf",
                        "http://www.w3.org/2000/01/rdf-schema#",
                        "rdfs:"), subClass);

                jgen.writeArrayFieldStart("hydra:supportedProperty");
                // TODO let defaultValue be an filled list, if needed
                Object propertyValue = PropertyUtils.getPropertyOrFieldValue(currentCallValue, annotatedParameter
                        .getParameterName());

                recurseSupportedProperties(jgen, currentVocab, parameterType,
                        allRootParameters,
                        rootParameter, propertyValue, nextPropertyPathLevel);
                jgen.writeEndArray();

                jgen.writeEndObject();
                jgen.writeEndObject();
            }
        }

    }
Writes bean description recursively.

@param jgen              to write to
@param currentVocab      in context
@param valueType         class of value
@param allRootParameters of the method that receives the request body
@param rootParameter     the request body
@param currentCallValue  the value at the current recursion level
@param propertyPath      of the current recursion level
@throws IntrospectionException
@throws IOException