From 1108d8f22bd348c57cda2382ec65bed54fdc90d0 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 30 Sep 2016 14:19:10 +0100 Subject: [PATCH 01/77] bump httpcomponents versions note core and client are no longer kept in sync; latest version of each has been taken --- parent/pom.xml | 6 +++--- pom.xml | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index c2bb387ef8..3ace422bb7 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -291,7 +291,7 @@ org.apache.httpcomponents httpcore - ${httpclient.version} + ${httpcomponents.httpcore.version} xml-apis @@ -321,13 +321,13 @@ org.apache.httpcomponents httpclient - ${httpclient.version} + ${httpcomponents.httpclient.version} org.apache.httpcomponents httpclient tests - ${httpclient.version} + ${httpcomponents.httpclient.version} aopalliance diff --git a/pom.xml b/pom.xml index b6469aef42..79597d34f6 100644 --- a/pom.xml +++ b/pom.xml @@ -104,7 +104,9 @@ 2.7.5 3.1.4 - 4.4.1 + 4.5.2 + 4.4.5 + 4.4.1 3.3.2 2.3.7 2.0.1 From a3191562bda623a97cbc329b8369578067ddd877 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 9 Jun 2016 17:01:00 +0100 Subject: [PATCH 02/77] Start work on YAML Object Relational Mapping Language --- .../apache/brooklyn/util/yorml/Sketch.java | 447 ++++++++++++++++++ .../apache/brooklyn/util/yorml/SketchOld.java | 124 +++++ .../org/apache/brooklyn/util/yorml/Yorml.java | 54 +++ .../brooklyn/util/yorml/YormlConfig.java | 16 + .../brooklyn/util/yorml/YormlContext.java | 28 ++ .../brooklyn/util/yorml/YormlConverter.java | 64 +++ .../brooklyn/util/yorml/YormlException.java | 24 + .../brooklyn/util/yorml/YormlInternals.java | 7 + .../brooklyn/util/yorml/YormlReadContext.java | 13 + .../brooklyn/util/yorml/YormlSerializer.java | 30 ++ .../util/yorml/YormlTypeRegistry.java | 7 + .../yorml/serializers/FieldsInFieldsMap.java | 74 +++ .../yorml/serializers/InstantiateType.java | 49 ++ .../yorml/tests/MockYormlTypeRegistry.java | 28 ++ .../util/yorml/tests/YormlBasicTests.java | 70 +++ 15 files changed, 1035 insertions(+) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/Sketch.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/SketchOld.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConfig.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlException.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlInternals.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlReadContext.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlSerializer.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Sketch.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Sketch.java new file mode 100644 index 0000000000..3e62200302 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Sketch.java @@ -0,0 +1,447 @@ +package org.apache.brooklyn.util.yorml; + +public class Sketch { + +/* + +// MOTIVATION + +We want a JSON/YAML schema which allows us to do bi-directional serialization to Java with docgen. +That is: +* It is easy for a user to write the YAML which generates the objects they care about +* It is easy for a user to read the YAML generated from data objects +* The syntax of the YAML can be documented automatically from the schema +* JSON can also be read or written (we restrict to the subset of YAML which is isomorphic to JSON) + +The focus on ease-of-reading and ease-of-writing differentiates this from other JSON/YAML +serialization processes. For instance we want to be able to support the following polymorphic +expressions: + +shapes: +- type: square # standard, explicit type and fields, but hard to read + size: 12 + color: red +- square: # type implied by key + size: 12 + color: red +- square: 12 # value is taken as a default key in a map +- red_square # string on its own can be interpreted in many ways but often it's the type +# and optionally (deferred) +- red square: { size: 12 } # multi-word string could be parsed in many ways (a la css border) + +Because in most contexts we have some sense of what we are expecting, we can get very efficient +readable representations. + +Of course you shouldn't use all of these to express the same type; but depending on the subject +matter some syntaxes may be more natural than others. Consider allowing writing: + + effectors: # field in parent, expects list of types 'Effector' + say_hi: # map converts to list treating key as name of effector, expecting type 'Effector' as value + type: ssh # type alias 'ssh' when type 'Effector` is needed matches SshEffector type + parameters: # field in SshEffector, of type Parameter + - name # string given when Parameter expected means it's the parameter name + - name: hello_word # map of type, becomes a Parameter populating fields + description: how to say hello + default: hello + command: | # and now the command, which SshEffector expects + echo ${hello_word} ${name:-world} + +The important thing here is not using all of them at the same time (as we did for shape), +but being *able* to support an author picking the subset that is right for a given situation, +in a way that they can be parsed, they can be generated, and the expected/supported syntax +can be documented automatically. + + +// INRODUCTORY EXAMPLES + +* defining types + +When defining a type, an `id` (how it is known) and an instantiable `type` (parent type) must be supplied. +These are kept in a type registry and can be used when defining other types or instances. + +# should be supplied +- id: shape + # no-arg constructor + type: java:org.acme.Shape # where `class Shape { String name; String color; }` + +You can also define types with default field values set, and of course you can refer to types +that have been defined: + +- id: red-square + type: shape + fields: + # any fields here read/written by direct access by default, or fail if not matched + name: square + color: red + +There are many syntaxes for defining instances, described below. Most of these can +be used when defining new types. + +* defining instances + +You define an instance by referencing a type, and optionally specifying fields: + + type: red-square + +Or + +- type: shape + fields: + name: square + color: red + + +* overwriting fields + +You could do this: + +- id: pink-square + type: red-square + fields: + # map of fields is merged with that of parent + color: pink + +Although this would be more sensible: + +- id: square + type: shape + fields: + name: square +- id: pink-square + type: square + fields: + color: pink + + +* allowing fields at root + (note: automatically the case in some situations) + +- id: ez-square + type: square + serialization: + - type: explicit-field + field-name: color + - type: no-others + +then (instance) + +- type: ez-square + color: blue + +Serialization takes a list of serializer types. These are applied in order, both for serialization +and deserialization, and re-run from the beginning if any are applied. + +`explicit-field` says to look at the root as well as in the 'fields' block. It has one required +parameter, field-name, and several optional ones: + + - type: explicit-field + field-name: color + key-name: color # this is used in yaml + aliases: [ colour ] # accepted in yaml as a synonym for key-name; `alias` also accepted + field-type: string # inferred from java field, but you can constrain further to yaml types + constraint: required # currently just supports 'required', or blank for none, but reserved for future use + description: The color of the shape # text (markdown) + serialization: # optional additional serialization instructions + - if-string: # (defined below) + set-key: field-name + +`no-others` says that any unrecognised fields in YAML will force an error prior to the default +deserialization steps (which attempt to write named config and then fields directly, before failing), +and on serialization it will ignore any unnamed fields. + +As a convenience if an entry in the list is a string S, the entry is taken as +`{ type: explicit-field, field-name: S }`. + +Thus the following would also be allowed: + + serialization: + - color + - type: no-others + + +// ADVANCED + +At the heart of this YAML serialization is the idea of heavily overloading to permit the most +natural way of writing in different situations. We go a bit overboard in 'serialization' to illustrate +below the different strategies. (Feel free to ignore, if you're comfortable with the simple examples.) +If the `serialization` field (which expects a list) is given a map, each pair in that map is +interpreted as follows: +* if V is a map then K is set in that map as 'field-name' (error if field-name is already set) +* if V is not a map then a map is created as { field-name: K, type: V } +Thus you could also write: + + serialization: + color: { alias: colour, description: "The color of the shape", constraint: required } + +(Note that some serialization types, such as 'no-others', cannot be expressed in this way, +because `field-name` is not supported on that type. This syntax is intended for the common +case when all fields are settable and we are defining top-level fields.) + +Finally if the serialization is given a list, and any entry in the list is a map which +does not define a type, the following rules apply: +* If the entry is a map of size larger than one, the type defaults to explicit-field. +* If the entry is a map of size one the key is taken as the type and merged with the value + if the value is a map (or it can be interpreted as such with an if-string on the type) +Thus we can write: + + serialization: + - field-name: color + alias: colour + - no-others: + +Note: this has some surprising side-effects in occasional edge cases; consider: + + # BAD: this would try to load a serialization type 'field-name' + serialization: + - field-name: color + # GOOD options + serialization: + - color + # or + serialization: + color: + # or + serialization: + - field-name: color + alias: colour + + # BAD: this would define a field `explicitField`, then fail because that field-name is in use + serialization: + explicit-field: { field-name: color } + # GOOD options (in addition to those in previous section, but assuming you wanted to say the type explciitly) + serialization: + - explicit-field: { field-name: color } + # or + - explicit-field: color + +It does the right thing in most cases, and it serves to illustrate the flexibility of this +approach. In most cases it's probably a bad idea to do this much overloading! However the +descriptions here will normally be taken from java annotations and not written by hand, +so emphasis is on making it easy-to-read (which overloading does nicely) rather than +easy-to-write. + +Of course if you have any doubt, simply use the long-winded syntax and avoid any convenience syntax: + + serialization: + - type: explicit-field + field-name: color + alias: colour + + +// OTHER BEHAVIORS + +* name mangling pattern, default conversion for fields: + wherever pattern is lower-upper-lower (java) <-> lower-dash-lower-lower (yaml) + + fields: + # corresponds to field shapeColor + shape-color: red + +* primitive types + +All Java primitive types are known, with their boxed and unboxed names, +and the key `value` can be used to set a value. This is normally not necessary +as where a primitive is expected routines will attempt coercion, but in some +cases it is desired. So for instance a red square could be defined as: + +- type: shape + name: + type: string + value: red + +* config/data keys + +Some java types define static ConfigKey fields and a `configure(key, value)` or `configure(ConfigBag)` +method. These are detected and applied as one of the default strategies (below). + + +* accepting lists with generics + +Where the java object is a list, this can correspond to YAML in many ways. +New serializations we introduce include `convert-map-to-map-list` (which allows +a map value to be supplied), `apply-defaults-in-list` (which ensures a set of keys +are present in every entry, using default values wherever the key is absent), +`convert-single-key-maps-in-list` (which gives special behaviour if the list consists +entirely of single-key-maps, useful where a map would normally be supplied but there +might be key collisions), `if-string-in-list` (which applies `if-string` to every +element in the list), and `convert-map-to-singleton-list` (which puts a map into +a list). + +If no special list serialization is supplied for when expecting a type of `list`, +the YAML must be a list and the serialization rules for `x` are then applied. If no +generic type is available for a list and no serialization is specified, an explicit +type is required on all entries. + +Serializations that apply to lists or map entries are applied to each entry, and if +any apply the serialization is then continued from the beginning. + +As a complex example, the `serialization` list we described above has the following formal +schema: + +- field-name: serialization + field-type: list + serialization: + + # transforms `- color` to `- { explicit-field: color }` which will be interpreted again + - type: if-string-in-list + set-key: explicit-field + + # alternative implementation of above (more explicit, not relying on `apply-defaults-in-list`) + # transforms `- color` to `- { type: explicit-field, field-name: color }` + - type: if-string-in-list + set-key: field-name + default: + type: explicit-field + + # describes how a yaml map can correspond to a list + # in this example `k: { type: x }` in a map (not a list) + # becomes an entry `{ field-name: k, type: x}` in a list + # (and same for shorthand `k: x`; however if just `k` is supplied it + # takes a default type `explicit-field`) + - type: convert-map-to-map-list + key-for-key: field-name + key-for-string-value: type # note, only applies if x non-blank + default: + type: explicit-field # note: needed to prevent collision with `convert-single-key-in-list` + + # if yaml is a list containing all maps swith a single key, treat the key specially + # transforms `- x: k` or `- x: { field-name: k }` to `- { type: x, field-name: k }` + # (use this one with care as it can be confusing, but useful where type is the only thing + # always required! typically only use in conjunction with `if-string-in-list` where `set-key: type`.) + - type: convert-single-key-maps-in-list + key-for-key: type # NB fails if this key is present in the value which is a map + key-for-string-value: field-name + + # applies any listed unset "default keys" to the given default values, + # for every map entry in a list + # here this essentially makes `explicit-field` the default type + - type: apply-defaults-in-list + default: + type: explicit-field + + +* accepting maps with generics + +In some cases the underlying type will be a java Map. The lowest level way of representing a map is +as a list of maps specifying the key and value of each entry, as follows: + +- key: + type: string + value: key1 + value: + type: red-square +- key: + type: string + value: key2 + value: a string + +You can also use a more concise map syntax if keys are strings: + + key1: { type: red-square } + key2: "a string" + +If we have information about the generic types -- supplied e.g. with a type of `map` -- +then coercion will be applied in either of the above syntaxes. + + +* where the accepted type is unknown + +In some instances an expected type may be explicitly `java.lang.Object`, or it may be +unknown (eg due to generics). In these cases if no serialization rules are specified, +we take lists as lists, we take maps as objects if a `type` is defined, we take +primitives when used as keys in a map as those primitives, and we take other primitives +as *types*. This last is to prevent errors. It is usually recommended to ensure that +either an expected type will be known or serialization rules are supplied (or both). + + +* default serialization + +It is possible to set some serializations to be defaults run before or after a supplied list. +The default is to run the following after (but this is suppressed if `no-others` is supplied, +in which case you may want to use some before the `no-others` directive): + + * `expected-type-serializers` runs through the serializers defined on the expected type + (so e.g. a shape might define a default size, then a reference to shape would always have that) + * `instantiate-type` on read, converts a map to the declared type (this can be used explicitly + to give information on which fields should be used as parameters in constructors, + or possibly to reference a static factory method) + * `all-config` reads/writes all declared config keys if there is a `configure(...)` method + * `all-matching-fields` reads any key corresponding to a field into an object, or writes all remaining + non-transient fields + * `fields-in-fields-map` applies all the keys in a `fields` block as fields in the object + +// TODO + +* type overloading, if string, if number, if map, if list... inferring type, or setting diff fields +* super-types and abstract types (underlying java of `supertypes` must be assignable from underying java of `type`) +* merging ... deep? field-based? +* setting in java class with annotation +* if-list, if-map, if-key-present, etc +* fields fetched by getters, written by setters +* include/exclude if null/empty/default + + +// IMPLEMENTATION SKETCH + + +## Yorma.java + +typeRegistry +converter + +read + yamlObject + yormaContext(jsonPath,expectedType) + yamlContext(origin,offset,length) +returns object of type expectedType +makes shallow copy of the object, then goes through serializers modifying it or creating/setting result, +until result is done + +write + object + yormaContext(jsonPath,expectedType) +returns jsonable object (map, list, primitive) + +document(type) +generates human-readable schema for a type + +## Serializer.java + +read/write + as above, but also taking YormaConversion and with a ConfigBag blackboard + + + + + +// TO ALLOW + +- id: print-all + type: initdish-effector + steps: + 00-provision: provision + 10-install: + type: bash + contents: | + curl blah + tar blah + 20-run: + type: invoke-effector + effector: launch + parameters: + ... + +- type: entity + fields: + - key: effectors + yamlType: list + yamlGenericType: effector + serializer: + - type: write-list-field + field: effectors +- type: effector + + + */ + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/SketchOld.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/SketchOld.java new file mode 100644 index 0000000000..385829d063 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/SketchOld.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml; + +import java.util.Map; + +public class SketchOld { + +/* + +catalog items can register a converter + +api for finding/using stock converters, and supplying add'l converters (recursive context) + +Context + mgmt + extraGlobalConverters -- immutable list copy on write + extraGlobalHandlers + findConverterFor(String type) +ReadContext + yamlParseNode + Map inputMap -- immutable + Set inputKeys + Object objectBeingCreated + childContextFor(object) +WriteContext + Object objectToOutput + Map outputMap + Set remainingFieldsToWrite + +Handler + + read(ReadContext) <- updates an object, invoking converters as needed + +Converter + ReadContext child = createObject(ReadContext parent) + just creating the type + void populateObject(RC parent) + invoking handlers etc + + + +handleKey("effectors", Handler) + +EntitySpecConverter reads + + +- id: sample + type: InitdishSoftwareProcess + + inputs: + - name: some.config: xxx + brooklyn.config: + some.other.field: xxx + + effectors: + - name: start + type: InitdishEffector + description: some start + parameters: + - name: foo + impl: + 00-acquire_lock: acquire-lock + 01-start: xxx + + sensors: + - name: foo + type: int + value: xxx + persist: optional + + feeds: + - type: https + url: /endpoint + period: 5s + set: all + then: + - json_path: a/b/c + set: foo_abc + then: + json_path: d + set: foo_abc_d + - json_path: x + set: foo_x + + initializers: + - type: InitdishEffectorInitializer + XXX + +*/ + public static class InitdishEffectorParser { + /* + * at spec parse time when we see type is InitdishSoftwareProcess + * we add some Converters + * + * so CatalogItem can specify Converters. + * if it extends we also take those converters. + * + * GlobalConverters + */ + } + + public interface Converter { + void readingMap(Map remainingKeys, Object objectBeingBuilt); + void writingMap(Map remainingFields, Object objectBeingWritten, Map targetMap); + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java new file mode 100644 index 0000000000..10a42271c4 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java @@ -0,0 +1,54 @@ +package org.apache.brooklyn.util.yorml; + +import java.util.List; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.yorml.serializers.FieldsInFieldsMap; +import org.apache.brooklyn.util.yorml.serializers.InstantiateType; + + +public class Yorml { + + YormlConfig config; + + private Yorml() {} + + public static Yorml newInstance(YormlTypeRegistry typeRegistry) { + return newInstance(typeRegistry, MutableList.of( + new FieldsInFieldsMap(), + new InstantiateType() )); + } + + public static Yorml newInstance(YormlTypeRegistry typeRegistry, List serializers) { + Yorml result = new Yorml(); + result.config = new YormlConfig(); + result.config.typeRegistry = typeRegistry; + result.config.serializersPost.addAll(serializers); + + return result; + } + + public Object read(String yaml) { + return read(yaml, null); + } + public Object read(String yaml, String type) { + Object yamlObject = new org.yaml.snakeyaml.Yaml().load(yaml); + YormlReadContext context = new YormlReadContext("", type); + context.setYamlObject(yamlObject); + new YormlConverter(config).read(context); + return context.getJavaObject(); + } + + public Object write(Object java) { + YormlContext context = new YormlContext("", null); + context.setJavaObject(java); + new YormlConverter(config).write(context); + return context.getYamlObject(); + } + +// public T read(String yaml, Class type) { +// } +// public T read(String yaml, TypeToken type) { +// } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConfig.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConfig.java new file mode 100644 index 0000000000..87528c4366 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConfig.java @@ -0,0 +1,16 @@ +package org.apache.brooklyn.util.yorml; + +import java.util.List; + +import org.apache.brooklyn.util.collections.MutableList; + +public class YormlConfig { + + YormlTypeRegistry typeRegistry; + List serializersPost = MutableList.of(); + + public YormlTypeRegistry getTypeRegistry() { + return typeRegistry; + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java new file mode 100644 index 0000000000..d93e37c347 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java @@ -0,0 +1,28 @@ +package org.apache.brooklyn.util.yorml; + +public class YormlContext { + + final String jsonPath; + final String expectedType; + Object javaObject; + Object yamlObject; + + public YormlContext(String jsonPath, String expectedType) { + this.jsonPath = jsonPath; + this.expectedType = expectedType; + } + + public Object getJavaObject() { + return javaObject; + } + public void setJavaObject(Object javaObject) { + this.javaObject = javaObject; + } + public Object getYamlObject() { + return yamlObject; + } + public void setYamlObject(Object yamlObject) { + this.yamlObject = yamlObject; + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java new file mode 100644 index 0000000000..ae71f6a8ed --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java @@ -0,0 +1,64 @@ +package org.apache.brooklyn.util.yorml; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; + +public class YormlConverter { + + private final YormlConfig config; + + public YormlConverter(YormlConfig config) { + this.config = config; + } + + /** + * returns object of type expectedType + * makes shallow copy of the object, then goes through serializers modifying it or creating/setting result, + * until result is done + */ + public Object read(YormlReadContext context) { + List serializers = MutableList.of().appendAll(config.serializersPost); + int i=0; + Map blackboard = MutableMap.of(); + while (i serializers = MutableList.of().appendAll(config.serializersPost); + int i=0; + Map blackboard = MutableMap.of(); + while (i blackboard); + + /** + * modifies java object and/or yaml object and/or blackboard as appropriate, + * when trying to build a yaml object from a java object, + * returning true if it did anything (and so should restart the cycle). + * implementations must NOT return true indefinitely if passed the same instances! + */ + public YormlContinuation write(YormlContext context, YormlConfig config, Map blackboard); + + /** + * generates human-readable schema for a type using this schema. + */ + public String document(String type, YormlConfig config); + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java new file mode 100644 index 0000000000..1b0b46d7ae --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java @@ -0,0 +1,7 @@ +package org.apache.brooklyn.util.yorml; + +public interface YormlTypeRegistry { + + Object newInstance(String type); + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java new file mode 100644 index 0000000000..106569c188 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java @@ -0,0 +1,74 @@ +package org.apache.brooklyn.util.yorml.serializers; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Map; + +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yorml.YormlConfig; +import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.YormlException; +import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; +import org.apache.brooklyn.util.yorml.YormlReadContext; +import org.apache.brooklyn.util.yorml.YormlSerializer; + +public class FieldsInFieldsMap implements YormlSerializer { + + + @Override + public YormlContinuation read(YormlReadContext context, YormlConfig config, Map blackboard) { + // TODO refactor to combine with InstantiateType + if (context.getJavaObject()==null) return YormlContinuation.CONTINUE_UNCHANGED; + if (!(context.getYamlObject() instanceof Map)) return YormlContinuation.CONTINUE_UNCHANGED; + Object fields = ((Map)context.getYamlObject()).get("fields"); + if (fields==null) return YormlContinuation.CONTINUE_UNCHANGED; + if (!(fields instanceof Map)) throw new YormlException("fields must be a map"); + + Object target = context.getJavaObject(); + for (Object f: ((Map)fields).keySet()) { + Object v = ((Map)fields).get(f); + try { + Field ff = Reflections.findField(target.getClass(), Strings.toString(f)); + if (ff==null) { + // just skip (could throw, but leave it in case something else recognises) + } else if (Modifier.isStatic(ff.getModifiers())) { + // as above + } else { + ff.setAccessible(true); + ff.set(target, v); + ((Map)fields).remove(Strings.toString(f)); + if (((Map)fields).isEmpty()) { + ((Map)context.getYamlObject()).remove("fields"); + } + } + } catch (Exception e) { throw Exceptions.propagate(e); } + } + return YormlContinuation.CONTINUE_CHANGED; + } + + @SuppressWarnings("unchecked") + @Override + public YormlContinuation write(YormlContext context, YormlConfig config, Map blackboard) { + if (context.getYamlObject() instanceof Map) { + Map fields = (Map) ((Map)context.getYamlObject()).get("fields"); + if (fields==null) { + fields = MutableMap.of(); + // TODO proper check. all fields. blackboard. etc. + fields.put("color", "red"); + ((Map)context.getYamlObject()).put("fields", fields); + return YormlContinuation.RESTART; + } + } + return YormlContinuation.CONTINUE_UNCHANGED; + } + + @Override + public String document(String type, YormlConfig config) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java new file mode 100644 index 0000000000..1a0f3f526e --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java @@ -0,0 +1,49 @@ +package org.apache.brooklyn.util.yorml.serializers; + +import java.util.Map; + +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.yorml.YormlConfig; +import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.YormlException; +import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; +import org.apache.brooklyn.util.yorml.YormlReadContext; +import org.apache.brooklyn.util.yorml.YormlSerializer; + +public class InstantiateType implements YormlSerializer { + + @Override + public YormlContinuation read(YormlReadContext context, YormlConfig config, Map blackboard) { + if (context.getJavaObject()!=null) return YormlContinuation.CONTINUE_UNCHANGED; + if (!(context.getYamlObject() instanceof Map)) return YormlContinuation.CONTINUE_UNCHANGED; + Object type = ((Map)context.getYamlObject()).get("type"); + if (type==null) return YormlContinuation.CONTINUE_UNCHANGED; + if (!(type instanceof String)) throw new YormlException("type must be a string"); + + Object result = config.getTypeRegistry().newInstance((String)type); + if (result==null) return YormlContinuation.CONTINUE_UNCHANGED; + + context.setJavaObject(result); + return YormlContinuation.RESTART; + } + + @Override + public YormlContinuation write(YormlContext context, YormlConfig config, Map blackboard) { + if (context.getYamlObject()==null) { + MutableMap map = MutableMap.of(); + context.setYamlObject(map); + // TODO primitives. map+list. type registry plain types. osgi. + map.put("type", "java:"+context.getJavaObject().getClass().getName()); + // TODO put fields remaining in TODO on blackboard, then check + return YormlContinuation.RESTART; + } + return YormlContinuation.CONTINUE_UNCHANGED; + } + + @Override + public String document(String type, YormlConfig config) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java new file mode 100644 index 0000000000..aa3187e26e --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java @@ -0,0 +1,28 @@ +package org.apache.brooklyn.util.yorml.tests; + +import java.util.Map; + +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.yorml.YormlTypeRegistry; + +public class MockYormlTypeRegistry implements YormlTypeRegistry { + + Map> types = MutableMap.of(); + + @Override + public Object newInstance(String typeName) { + Class type = types.get(typeName); + if (type==null) return null; + try { + return type.newInstance(); + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + + public void put(String typeName, Class type) { + types.put(typeName, type); + } + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java new file mode 100644 index 0000000000..a238e99ffa --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java @@ -0,0 +1,70 @@ +package org.apache.brooklyn.util.yorml.tests; + +import org.apache.brooklyn.util.collections.Jsonya; +import org.apache.brooklyn.util.yorml.Yorml; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class YormlBasicTests { + + public static class Shape { + String name; + String color; + int size; + } + + @Test + public void testReadJavaType() { + MockYormlTypeRegistry tr = new MockYormlTypeRegistry(); + tr.put("shape", Shape.class); + Yorml y = Yorml.newInstance(tr); + Object resultO = y.read("{ type: shape }", "object"); + + Assert.assertNotNull(resultO); + Assert.assertTrue(resultO instanceof Shape, "Wrong result type: "+resultO); + Shape result = (Shape)resultO; + Assert.assertNull(result.name); + Assert.assertNull(result.color); + } + + @Test + public void testReadFieldInFields() { + MockYormlTypeRegistry tr = new MockYormlTypeRegistry(); + tr.put("shape", Shape.class); + Yorml y = Yorml.newInstance(tr); + Object resultO = y.read("{ type: shape, fields: { color: red } }", "object"); + + Assert.assertNotNull(resultO); + Assert.assertTrue(resultO instanceof Shape, "Wrong result type: "+resultO); + Shape result = (Shape)resultO; + Assert.assertNull(result.name); + Assert.assertEquals(result.color, "red"); + } + + @Test + public void testWriteFieldInFields() { + MockYormlTypeRegistry tr = new MockYormlTypeRegistry(); + tr.put("shape", Shape.class); + Yorml y = Yorml.newInstance(tr); + + Shape s = new Shape(); + s.color = "red"; + Object resultO = y.write(s); + + Assert.assertNotNull(resultO); + String out = Jsonya.newInstance().add(resultO).toString(); + String expected = Jsonya.newInstance().add("type", "java"+":"+Shape.class.getPackage().getName()+"."+YormlBasicTests.class.getSimpleName()+"$"+"Shape") + .at("fields").add("color", "red").root().toString(); + Assert.assertEquals(out, expected); + } +/* +[{ "type": "java:org.apache.brooklyn.util.yorml.tests.YormlBasicTests$Shape", "fields": { "color": "red" } }] but found +[{ "type": "java:org.apache.brooklyn.util.yorml.tests.YormlBasicTests.Shape", "fields": { "color": "red" } }] + +java.lang.AssertionError: expected +[{ "type": "java:org.apache.brooklyn.util.yorml.tests.YormlBasicTests$Shape", "fields": { "color": "red" } }] +[{ "type": "java:class org.apache.brooklyn.util.yorml.tests.YormlBasicTests$Shape", "fields": { "color": "red" } }] + + */ + +} From 97c1226d205aca25a0988e1b0926b733c385403d Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 24 Jun 2016 11:13:27 +0100 Subject: [PATCH 03/77] flesh out much more of YORML --- .../apache/brooklyn/util/yorml/SketchOld.java | 124 ------------ .../org/apache/brooklyn/util/yorml/Yorml.java | 2 +- .../yorml/serializers/FieldsInFieldsMap.java | 131 ++++++++----- .../yorml/serializers/InstantiateType.java | 101 ++++++---- .../YormlSerializerComposition.java | 92 +++++++++ .../util/yorml/{Sketch.java => sketch.md} | 177 ++++++++++-------- .../util/javalang/ReflectionsTest.java | 110 ++++++++++- .../util/yorml/tests/YormlBasicTests.java | 11 +- 8 files changed, 448 insertions(+), 300 deletions(-) delete mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/SketchOld.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java rename utils/common/src/main/java/org/apache/brooklyn/util/yorml/{Sketch.java => sketch.md} (85%) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/SketchOld.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/SketchOld.java deleted file mode 100644 index 385829d063..0000000000 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/SketchOld.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.brooklyn.util.yorml; - -import java.util.Map; - -public class SketchOld { - -/* - -catalog items can register a converter - -api for finding/using stock converters, and supplying add'l converters (recursive context) - -Context - mgmt - extraGlobalConverters -- immutable list copy on write - extraGlobalHandlers - findConverterFor(String type) -ReadContext - yamlParseNode - Map inputMap -- immutable - Set inputKeys - Object objectBeingCreated - childContextFor(object) -WriteContext - Object objectToOutput - Map outputMap - Set remainingFieldsToWrite - -Handler - - read(ReadContext) <- updates an object, invoking converters as needed - -Converter - ReadContext child = createObject(ReadContext parent) - just creating the type - void populateObject(RC parent) - invoking handlers etc - - - -handleKey("effectors", Handler) - -EntitySpecConverter reads - - -- id: sample - type: InitdishSoftwareProcess - - inputs: - - name: some.config: xxx - brooklyn.config: - some.other.field: xxx - - effectors: - - name: start - type: InitdishEffector - description: some start - parameters: - - name: foo - impl: - 00-acquire_lock: acquire-lock - 01-start: xxx - - sensors: - - name: foo - type: int - value: xxx - persist: optional - - feeds: - - type: https - url: /endpoint - period: 5s - set: all - then: - - json_path: a/b/c - set: foo_abc - then: - json_path: d - set: foo_abc_d - - json_path: x - set: foo_x - - initializers: - - type: InitdishEffectorInitializer - XXX - -*/ - public static class InitdishEffectorParser { - /* - * at spec parse time when we see type is InitdishSoftwareProcess - * we add some Converters - * - * so CatalogItem can specify Converters. - * if it extends we also take those converters. - * - * GlobalConverters - */ - } - - public interface Converter { - void readingMap(Map remainingKeys, Object objectBeingBuilt); - void writingMap(Map remainingFields, Object objectBeingWritten, Map targetMap); - } - -} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java index 10a42271c4..ff4910904f 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java @@ -14,7 +14,7 @@ public class Yorml { private Yorml() {} public static Yorml newInstance(YormlTypeRegistry typeRegistry) { - return newInstance(typeRegistry, MutableList.of( + return newInstance(typeRegistry, MutableList.of( new FieldsInFieldsMap(), new InstantiateType() )); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java index 106569c188..5a082bce52 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java @@ -2,73 +2,100 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.List; import java.util.Map; +import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yorml.YormlConfig; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.YormlException; import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; -import org.apache.brooklyn.util.yorml.YormlReadContext; -import org.apache.brooklyn.util.yorml.YormlSerializer; -public class FieldsInFieldsMap implements YormlSerializer { +public class FieldsInFieldsMap extends YormlSerializerComposition { - - @Override - public YormlContinuation read(YormlReadContext context, YormlConfig config, Map blackboard) { - // TODO refactor to combine with InstantiateType - if (context.getJavaObject()==null) return YormlContinuation.CONTINUE_UNCHANGED; - if (!(context.getYamlObject() instanceof Map)) return YormlContinuation.CONTINUE_UNCHANGED; - Object fields = ((Map)context.getYamlObject()).get("fields"); - if (fields==null) return YormlContinuation.CONTINUE_UNCHANGED; - if (!(fields instanceof Map)) throw new YormlException("fields must be a map"); - - Object target = context.getJavaObject(); - for (Object f: ((Map)fields).keySet()) { - Object v = ((Map)fields).get(f); - try { - Field ff = Reflections.findField(target.getClass(), Strings.toString(f)); - if (ff==null) { - // just skip (could throw, but leave it in case something else recognises) - } else if (Modifier.isStatic(ff.getModifiers())) { - // as above - } else { - ff.setAccessible(true); - ff.set(target, v); - ((Map)fields).remove(Strings.toString(f)); - if (((Map)fields).isEmpty()) { - ((Map)context.getYamlObject()).remove("fields"); + public FieldsInFieldsMap() { super(Worker.class); } + + public static class Worker extends YormlSerializerWorker { + public YormlContinuation read() { + if (!hasJavaObject()) return YormlContinuation.CONTINUE_UNCHANGED; + + @SuppressWarnings("unchecked") + Map fields = getFromYamlMap("fields", Map.class); + if (fields==null) return YormlContinuation.CONTINUE_UNCHANGED; + + for (Object f: ((Map)fields).keySet()) { + Object v = ((Map)fields).get(f); + try { + Field ff = Reflections.findField(getJavaObject().getClass(), Strings.toString(f)); + if (ff==null) { + // just skip (could throw, but leave it in case something else recognises) + } else if (Modifier.isStatic(ff.getModifiers())) { + // as above + } else { + ff.setAccessible(true); + ff.set(getJavaObject(), v); + ((Map)fields).remove(Strings.toString(f)); + if (((Map)fields).isEmpty()) { + ((Map)context.getYamlObject()).remove("fields"); + } } - } - } catch (Exception e) { throw Exceptions.propagate(e); } + } catch (Exception e) { throw Exceptions.propagate(e); } + } + return YormlContinuation.CONTINUE_CHANGED; } - return YormlContinuation.CONTINUE_CHANGED; - } - @SuppressWarnings("unchecked") - @Override - public YormlContinuation write(YormlContext context, YormlConfig config, Map blackboard) { - if (context.getYamlObject() instanceof Map) { - Map fields = (Map) ((Map)context.getYamlObject()).get("fields"); - if (fields==null) { - fields = MutableMap.of(); - // TODO proper check. all fields. blackboard. etc. - fields.put("color", "red"); - ((Map)context.getYamlObject()).put("fields", fields); - return YormlContinuation.RESTART; + public YormlContinuation write() { + if (!isYamlMap()) return YormlContinuation.CONTINUE_UNCHANGED; + if (getFromYamlMap("fields", Map.class)!=null) return YormlContinuation.CONTINUE_UNCHANGED; + FieldsInBlackboard fib = FieldsInBlackboard.peek(blackboard); + if (fib==null || fib.fieldsToWriteFromJava.isEmpty()) return YormlContinuation.CONTINUE_UNCHANGED; + + Map fields = MutableMap.of(); + + for (String f: MutableList.copyOf(fib.fieldsToWriteFromJava)) { + Maybe v = Reflections.getFieldValueMaybe(getJavaObject(), f); + if (v.isPresent()) { + fib.fieldsToWriteFromJava.remove(f); + if (v.get()!=null) { + // TODO assert null checks + fields.put(f, v.get()); + } + } } + + if (fields.isEmpty()) return YormlContinuation.CONTINUE_UNCHANGED; + + setInYamlMap("fields", fields); + return YormlContinuation.RESTART; } - return YormlContinuation.CONTINUE_UNCHANGED; } - @Override - public String document(String type, YormlConfig config) { - // TODO Auto-generated method stub - return null; + /** Indicates that something has handled the type + * (on read, creating the java object, and on write, setting the `type` field in the yaml object) + * and made a determination of what fields need to be handled */ + public static class FieldsInBlackboard { + private static String KEY = "FIELDS_IN_BLACKBOARD"; + + public static boolean isPresent(Map blackboard) { + return blackboard.containsKey(KEY); + } + public static FieldsInBlackboard peek(Map blackboard) { + return (FieldsInBlackboard) blackboard.get(KEY); + } + public static FieldsInBlackboard getOrCreate(Map blackboard) { + if (!isPresent(blackboard)) { blackboard.put(KEY, new FieldsInBlackboard()); } + return peek(blackboard); + } + public static FieldsInBlackboard create(Map blackboard) { + if (isPresent(blackboard)) { throw new IllegalStateException("Already present"); } + blackboard.put(KEY, new FieldsInBlackboard()); + return peek(blackboard); + } + + List fieldsToWriteFromJava; + // TODO if fields are *required* ? } - + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java index 1a0f3f526e..69a4b44358 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java @@ -1,49 +1,80 @@ package org.apache.brooklyn.util.yorml.serializers; -import java.util.Map; +import java.lang.reflect.Field; +import java.util.List; +import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.yorml.YormlConfig; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.YormlException; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.javalang.Boxing; +import org.apache.brooklyn.util.javalang.FieldOrderings; +import org.apache.brooklyn.util.javalang.ReflectionPredicates; +import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; -import org.apache.brooklyn.util.yorml.YormlReadContext; -import org.apache.brooklyn.util.yorml.YormlSerializer; +import org.apache.brooklyn.util.yorml.serializers.FieldsInFieldsMap.FieldsInBlackboard; -public class InstantiateType implements YormlSerializer { +public class InstantiateType extends YormlSerializerComposition { - @Override - public YormlContinuation read(YormlReadContext context, YormlConfig config, Map blackboard) { - if (context.getJavaObject()!=null) return YormlContinuation.CONTINUE_UNCHANGED; - if (!(context.getYamlObject() instanceof Map)) return YormlContinuation.CONTINUE_UNCHANGED; - Object type = ((Map)context.getYamlObject()).get("type"); - if (type==null) return YormlContinuation.CONTINUE_UNCHANGED; - if (!(type instanceof String)) throw new YormlException("type must be a string"); - - Object result = config.getTypeRegistry().newInstance((String)type); - if (result==null) return YormlContinuation.CONTINUE_UNCHANGED; - - context.setJavaObject(result); - return YormlContinuation.RESTART; - } + public InstantiateType() { super(Worker.class); } + + public static class Worker extends YormlSerializerWorker { + public YormlContinuation read() { + if (hasJavaObject()) return YormlContinuation.CONTINUE_UNCHANGED; + + // TODO check is primitive? + // TODO if map and list? + + String type = getFromYamlMap("type", String.class); + if (type==null) return YormlContinuation.CONTINUE_UNCHANGED; + + Object result = config.getTypeRegistry().newInstance((String)type); + context.setJavaObject(result); + + return YormlContinuation.RESTART; + } + + public YormlContinuation write() { + if (hasYamlObject()) return YormlContinuation.CONTINUE_UNCHANGED; + if (!hasJavaObject()) return YormlContinuation.CONTINUE_UNCHANGED; + if (FieldsInBlackboard.isPresent(blackboard)) return YormlContinuation.CONTINUE_UNCHANGED; + + FieldsInBlackboard fib = FieldsInBlackboard.create(blackboard); + fib.fieldsToWriteFromJava = MutableList.of(); + + Object jo = getJavaObject(); + if (Boxing.isPrimitiveOrBoxedObject(jo)) { + context.setJavaObject(jo); + return YormlContinuation.RESTART; + } - @Override - public YormlContinuation write(YormlContext context, YormlConfig config, Map blackboard) { - if (context.getYamlObject()==null) { + // TODO map+list -- here, or in separate serializers? + MutableMap map = MutableMap.of(); context.setYamlObject(map); - // TODO primitives. map+list. type registry plain types. osgi. - map.put("type", "java:"+context.getJavaObject().getClass().getName()); - // TODO put fields remaining in TODO on blackboard, then check + + // TODO look up registry type + // TODO support osgi + + map.put("type", "java:"+getJavaObject().getClass().getName()); + + List fields = Reflections.findFields(getJavaObject().getClass(), + null, + FieldOrderings.ALPHABETICAL_FIELD_THEN_SUB_BEST_FIRST); + Field lastF = null; + for (Field f: fields) { + Maybe v = Reflections.getFieldValueMaybe(getJavaObject(), f); + if (ReflectionPredicates.IS_FIELD_NON_TRANSIENT.apply(f) && v.isPresentAndNonNull()) { + String name = f.getName(); + if (lastF!=null && lastF.getName().equals(f.getName())) { + // if field is shadowed use FQN + name = f.getDeclaringClass().getCanonicalName()+"."+name; + } + fib.fieldsToWriteFromJava.add(name); + } + lastF = f; + } + return YormlContinuation.RESTART; } - return YormlContinuation.CONTINUE_UNCHANGED; } - - @Override - public String document(String type, YormlConfig config) { - // TODO Auto-generated method stub - return null; - } - } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java new file mode 100644 index 0000000000..7cde5a11bd --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java @@ -0,0 +1,92 @@ +package org.apache.brooklyn.util.yorml.serializers; + +import java.util.Map; + +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.yorml.YormlConfig; +import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; +import org.apache.brooklyn.util.yorml.YormlReadContext; +import org.apache.brooklyn.util.yorml.YormlSerializer; + +public class YormlSerializerComposition implements YormlSerializer { + + protected final Class workerType; + + public YormlSerializerComposition(Class workerType) { + this.workerType = workerType; + } + + public abstract static class YormlSerializerWorker { + + protected YormlContext context; + protected YormlReadContext readContext; + protected YormlConfig config; + protected Map blackboard; + + private void initRead(YormlReadContext context, YormlConfig config, Map blackboard) { + if (this.context!=null) throw new IllegalStateException("Already initialized, for "+context); + this.context = context; + this.readContext = context; + this.config = config; + this.blackboard = blackboard; + } + + private void initWrite(YormlContext context, YormlConfig config, Map blackboard) { + if (this.context!=null) throw new IllegalStateException("Already initialized, for "+context); + this.context = context; + this.config = config; + this.blackboard = blackboard; + } + + public boolean hasJavaObject() { return context.getJavaObject()!=null; } + public boolean hasYamlObject() { return context.getYamlObject()!=null; } + public Object getJavaObject() { return context.getJavaObject(); } + public Object getYamlObject() { return context.getYamlObject(); } + + public boolean isYamlMap() { return context.getYamlObject() instanceof Map; } + @SuppressWarnings("unchecked") + public Map getYamlMap() { return (Map)context.getYamlObject(); } + /** Returns the value of the given key if present in the map and of the given type. + * If the YAML is not a map, or the key is not present, or the type is different, this returns null. */ + @SuppressWarnings("unchecked") + public T getFromYamlMap(String key, Class type) { + if (!isYamlMap()) return null; + Object v = getYamlMap().get(key); + if (v==null) return null; + if (!type.isInstance(v)) return null; + return (T) v; + } + protected void setInYamlMap(String key, Object value) { + ((Map)getYamlMap()).put(key, value); + } + + public abstract YormlContinuation read(); + public abstract YormlContinuation write(); + } + + @Override + public YormlContinuation read(YormlReadContext context, YormlConfig config, Map blackboard) { + YormlSerializerWorker worker; + try { + worker = workerType.newInstance(); + } catch (Exception e) { throw Exceptions.propagate(e); } + worker.initRead(context, config, blackboard); + return worker.read(); + } + + @Override + public YormlContinuation write(YormlContext context, YormlConfig config, Map blackboard) { + YormlSerializerWorker worker; + try { + worker = workerType.newInstance(); + } catch (Exception e) { throw Exceptions.propagate(e); } + worker.initWrite(context, config, blackboard); + return worker.write(); + } + + @Override + public String document(String type, YormlConfig config) { + return null; + } +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Sketch.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md similarity index 85% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/Sketch.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md index 3e62200302..22164e147a 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Sketch.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md @@ -1,10 +1,7 @@ -package org.apache.brooklyn.util.yorml; -public class Sketch { +# YORML: The YAML Object Relational Mapping Language -/* - -// MOTIVATION +## Movitvation We want a JSON/YAML schema which allows us to do bi-directional serialization to Java with docgen. That is: @@ -17,6 +14,7 @@ public class Sketch { serialization processes. For instance we want to be able to support the following polymorphic expressions: +``` shapes: - type: square # standard, explicit type and fields, but hard to read size: 12 @@ -28,6 +26,7 @@ public class Sketch { - red_square # string on its own can be interpreted in many ways but often it's the type # and optionally (deferred) - red square: { size: 12 } # multi-word string could be parsed in many ways (a la css border) +``` Because in most contexts we have some sense of what we are expecting, we can get very efficient readable representations. @@ -35,6 +34,7 @@ public class Sketch { Of course you shouldn't use all of these to express the same type; but depending on the subject matter some syntaxes may be more natural than others. Consider allowing writing: +``` effectors: # field in parent, expects list of types 'Effector' say_hi: # map converts to list treating key as name of effector, expecting type 'Effector' as value type: ssh # type alias 'ssh' when type 'Effector` is needed matches SshEffector type @@ -45,6 +45,7 @@ public class Sketch { default: hello command: | # and now the command, which SshEffector expects echo ${hello_word} ${name:-world} +``` The important thing here is not using all of them at the same time (as we did for shape), but being *able* to support an author picking the subset that is right for a given situation, @@ -52,57 +53,69 @@ The important thing here is not using all of them at the same time (as we did fo can be documented automatically. -// INRODUCTORY EXAMPLES +## Introductory Examples + -* defining types +### Defining types When defining a type, an `id` (how it is known) and an instantiable `type` (parent type) must be supplied. These are kept in a type registry and can be used when defining other types or instances. -# should be supplied +``` - id: shape - # no-arg constructor type: java:org.acme.Shape # where `class Shape { String name; String color; }` +``` + +The `java:` prefix is an optional shorthand to allow a Java type to be accessed. +For now this assumes a no-arg constructor. You can also define types with default field values set, and of course you can refer to types that have been defined: +``` - id: red-square type: shape fields: # any fields here read/written by direct access by default, or fail if not matched name: square color: red +``` There are many syntaxes for defining instances, described below. Most of these can be used when defining new types. -* defining instances + +### Defining instances You define an instance by referencing a type, and optionally specifying fields: - type: red-square + type: red-square Or -- type: shape - fields: - name: square - color: red + - type: shape + fields: + name: square + color: red + +TODO - integrate with registry -* overwriting fields +### Overwriting fields You could do this: +``` - id: pink-square type: red-square fields: # map of fields is merged with that of parent color: pink +``` Although this would be more sensible: +``` - id: square type: shape fields: @@ -111,11 +124,15 @@ The important thing here is not using all of them at the same time (as we did fo type: square fields: color: pink +``` + +TODO - just take what the parent has set and proceed -* allowing fields at root - (note: automatically the case in some situations) +### Allowing fields at root +If we define something like this: + - id: ez-square type: square serialization: @@ -123,17 +140,26 @@ The important thing here is not using all of them at the same time (as we did fo field-name: color - type: no-others -then (instance) +Then we could skip the `fields` item altogether: +``` - type: ez-square color: blue +``` + +TODO explicit-field +TODO no-others + +## Intermission: On serializers and implementation (can skip) + Serialization takes a list of serializer types. These are applied in order, both for serialization and deserialization, and re-run from the beginning if any are applied. `explicit-field` says to look at the root as well as in the 'fields' block. It has one required parameter, field-name, and several optional ones: +``` - type: explicit-field field-name: color key-name: color # this is used in yaml @@ -144,6 +170,7 @@ The important thing here is not using all of them at the same time (as we did fo serialization: # optional additional serialization instructions - if-string: # (defined below) set-key: field-name +``` `no-others` says that any unrecognised fields in YAML will force an error prior to the default deserialization steps (which attempt to write named config and then fields directly, before failing), @@ -154,12 +181,12 @@ deserialization steps (which attempt to write named config and then fields direc Thus the following would also be allowed: - serialization: - - color - - type: no-others + serialization: + - color + - type: no-others -// ADVANCED +### On overloading (really can skip!) At the heart of this YAML serialization is the idea of heavily overloading to permit the most natural way of writing in different situations. We go a bit overboard in 'serialization' to illustrate @@ -170,8 +197,8 @@ deserialization steps (which attempt to write named config and then fields direc * if V is not a map then a map is created as { field-name: K, type: V } Thus you could also write: - serialization: - color: { alias: colour, description: "The color of the shape", constraint: required } + serialization: + color: { alias: colour, description: "The color of the shape", constraint: required } (Note that some serialization types, such as 'no-others', cannot be expressed in this way, because `field-name` is not supported on that type. This syntax is intended for the common @@ -184,13 +211,14 @@ deserialization steps (which attempt to write named config and then fields direc if the value is a map (or it can be interpreted as such with an if-string on the type) Thus we can write: - serialization: - - field-name: color - alias: colour - - no-others: + serialization: + - field-name: color + alias: colour + - no-others: Note: this has some surprising side-effects in occasional edge cases; consider: +``` # BAD: this would try to load a serialization type 'field-name' serialization: - field-name: color @@ -213,6 +241,7 @@ deserialization steps (which attempt to write named config and then fields direc - explicit-field: { field-name: color } # or - explicit-field: color +``` It does the right thing in most cases, and it serves to illustrate the flexibility of this approach. In most cases it's probably a bad idea to do this much overloading! However the @@ -222,40 +251,48 @@ deserialization steps (which attempt to write named config and then fields direc Of course if you have any doubt, simply use the long-winded syntax and avoid any convenience syntax: +``` serialization: - type: explicit-field field-name: color alias: colour +``` +## Further Behaviours -// OTHER BEHAVIORS +### Name Mangling and Aliases -* name mangling pattern, default conversion for fields: - wherever pattern is lower-upper-lower (java) <-> lower-dash-lower-lower (yaml) +We apply a default conversion for fields: +wherever pattern is lower-upper-lower (java) <-> lower-dash-lower-lower (yaml). +These are handled as a default set of aliases. + + fields: + # corresponds to field shapeColor + shape-color: red - fields: - # corresponds to field shapeColor - shape-color: red -* primitive types +### Primitive types All Java primitive types are known, with their boxed and unboxed names, and the key `value` can be used to set a value. This is normally not necessary as where a primitive is expected routines will attempt coercion, but in some cases it is desired. So for instance a red square could be defined as: +``` - type: shape name: type: string value: red +``` -* config/data keys + +### Config/data keys Some java types define static ConfigKey fields and a `configure(key, value)` or `configure(ConfigBag)` method. These are detected and applied as one of the default strategies (below). -* accepting lists with generics +### Accepting lists, including generics Where the java object is a list, this can correspond to YAML in many ways. New serializations we introduce include `convert-map-to-map-list` (which allows @@ -278,6 +315,7 @@ method. These are detected and applied as one of the default strategies (below). As a complex example, the `serialization` list we described above has the following formal schema: +``` - field-name: serialization field-type: list serialization: @@ -318,13 +356,14 @@ method. These are detected and applied as one of the default strategies (below). - type: apply-defaults-in-list default: type: explicit-field +``` - -* accepting maps with generics +### Accepting maps, including generics In some cases the underlying type will be a java Map. The lowest level way of representing a map is as a list of maps specifying the key and value of each entry, as follows: +``` - key: type: string value: key1 @@ -334,17 +373,18 @@ method. These are detected and applied as one of the default strategies (below). type: string value: key2 value: a string +``` You can also use a more concise map syntax if keys are strings: - key1: { type: red-square } - key2: "a string" + key1: { type: red-square } + key2: "a string" If we have information about the generic types -- supplied e.g. with a type of `map` -- then coercion will be applied in either of the above syntaxes. -* where the accepted type is unknown +### Where the accepted type is unknown In some instances an expected type may be explicitly `java.lang.Object`, or it may be unknown (eg due to generics). In these cases if no serialization rules are specified, @@ -354,7 +394,7 @@ method. These are detected and applied as one of the default strategies (below). either an expected type will be known or serialization rules are supplied (or both). -* default serialization +### Default serialization It is possible to set some serializations to be defaults run before or after a supplied list. The default is to run the following after (but this is suppressed if `no-others` is supplied, @@ -370,7 +410,8 @@ The default is to run the following after (but this is suppressed if `no-others` non-transient fields * `fields-in-fields-map` applies all the keys in a `fields` block as fields in the object -// TODO + +## Even more further behaviours (not part of MVP) * type overloading, if string, if number, if map, if list... inferring type, or setting diff fields * super-types and abstract types (underlying java of `supertypes` must be assignable from underying java of `type`) @@ -381,41 +422,27 @@ The default is to run the following after (but this is suppressed if `no-others` * include/exclude if null/empty/default -// IMPLEMENTATION SKETCH - +## Implementation Notes -## Yorma.java +We have a `Converter` which runs through `Serializer`s, +where each supports `read`, `write`, and `document`. -typeRegistry -converter +A blackboard is used to share information including suppressing serializers. +Serializers do nothing if their preconditions aren't met, +and serializers can (and typically do) restart the serialization cycle if they change data. -read - yamlObject - yormaContext(jsonPath,expectedType) - yamlContext(origin,offset,length) -returns object of type expectedType -makes shallow copy of the object, then goes through serializers modifying it or creating/setting result, -until result is done +So the general process is: -write - object - yormaContext(jsonPath,expectedType) -returns jsonable object (map, list, primitive) +* first r/w the type, and on write note the fields to write +* adjust the data structure until no further adjustments are made +* check that everything that needed to be done was done -document(type) -generates human-readable schema for a type -## Serializer.java +## Real World Use Cases -read/write - as above, but also taking YormaConversion and with a ConfigBag blackboard - - - - - -// TO ALLOW +### An Init.d-style entity/effector language +``` - id: print-all type: initdish-effector steps: @@ -440,8 +467,4 @@ The default is to run the following after (but this is suppressed if `no-others` - type: write-list-field field: effectors - type: effector - - - */ - -} +``` diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/javalang/ReflectionsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/javalang/ReflectionsTest.java index b6bb63c798..f5ec96e4a1 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/javalang/ReflectionsTest.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/javalang/ReflectionsTest.java @@ -26,7 +26,7 @@ import java.util.Arrays; import java.util.List; -import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.collections.MutableSet; import org.testng.Assert; import org.testng.annotations.Test; @@ -151,4 +151,112 @@ public void testGetAllInterfaces() throws Exception { Assert.assertEquals(Reflections.getAllInterfaces(C.class), ImmutableList.of(M.class, K.class, I.class, J.class, L.class)); } + public static class FF1 { + int y; + public int x; + public FF1(int x, int y) { this.x = x; this.y = y; } + } + public static class FF2 extends FF1 { + public int z; + int x; + public FF2(int x, int y, int x2, int z) { super(x, y); this.x = x2; this.z = z; } + } + + @Test + public void testFindPublicFields() throws Exception { + List fields = Reflections.findPublicFieldsOrderedBySuper(FF2.class); + if (fields.size() != 2) Assert.fail("Wrong number of fields: "+fields); + int i=0; + Assert.assertEquals(fields.get(i++).getName(), "x"); + Assert.assertEquals(fields.get(i++).getName(), "z"); + } + + @Test + public void testFindAllFields() throws Exception { + List fields = Reflections.findFields(FF2.class, null, null); + // defaults to SUB_BEST_FIELD_LAST_THEN_ALPHA + if (fields.size() != 4) Assert.fail("Wrong number of fields: "+fields); + int i=0; + Assert.assertEquals(fields.get(i++).getName(), "x"); + Assert.assertEquals(fields.get(i++).getName(), "y"); + Assert.assertEquals(fields.get(i++).getName(), "x"); + Assert.assertEquals(fields.get(i++).getName(), "z"); + } + + @Test + public void testFindAllFieldsSubBestFirstThenAlpha() throws Exception { + List fields = Reflections.findFields(FF2.class, null, FieldOrderings.SUB_BEST_FIELD_FIRST_THEN_ALPHABETICAL); + if (fields.size() != 4) Assert.fail("Wrong number of fields: "+fields); + int i=0; + Assert.assertEquals(fields.get(i++).getName(), "x"); + Assert.assertEquals(fields.get(i++).getName(), "z"); + Assert.assertEquals(fields.get(i++).getName(), "x"); + Assert.assertEquals(fields.get(i++).getName(), "y"); + } + + @Test + public void testFindAllFieldsSubBestLastThenAlpha() throws Exception { + List fields = Reflections.findFields(FF2.class, null, FieldOrderings.SUB_BEST_FIELD_LAST_THEN_ALPHABETICAL); + if (fields.size() != 4) Assert.fail("Wrong number of fields: "+fields); + int i=0; + Assert.assertEquals(fields.get(i++).getName(), "x"); + Assert.assertEquals(fields.get(i++).getName(), "y"); + Assert.assertEquals(fields.get(i++).getName(), "x"); + Assert.assertEquals(fields.get(i++).getName(), "z"); + } + + @Test + public void testFindAllFieldsAlphaSubBestFirst() throws Exception { + List fields = Reflections.findFields(FF2.class, null, FieldOrderings.ALPHABETICAL_FIELD_THEN_SUB_BEST_FIRST); + if (fields.size() != 4) Assert.fail("Wrong number of fields: "+fields); + int i=0; + Assert.assertEquals(fields.get(i).getName(), "x"); + Assert.assertEquals(fields.get(i++).getDeclaringClass(), FF2.class); + Assert.assertEquals(fields.get(i).getName(), "x"); + Assert.assertEquals(fields.get(i++).getDeclaringClass(), FF1.class); + Assert.assertEquals(fields.get(i++).getName(), "y"); + Assert.assertEquals(fields.get(i++).getName(), "z"); + } + + @Test + public void testFindAllFieldsNotAlpha() throws Exception { + // ?? - does this test depend on the JVM? it preserves the default order of fields + List fields = Reflections.findFields(FF2.class, null, FieldOrderings.SUB_BEST_FIELD_LAST_THEN_DEFAULT); + if (fields.size() != 4) Assert.fail("Wrong number of fields: "+fields); + int i=0; + // can't say more about order than this + Assert.assertEquals(MutableSet.of(fields.get(i++).getName(), fields.get(i++).getName()), + MutableSet.of("x", "y")); + Assert.assertEquals(MutableSet.of(fields.get(i++).getName(), fields.get(i++).getName()), + MutableSet.of("x", "z")); + } + + @Test + public void testFindField() throws Exception { + FF2 f2 = new FF2(1,2,3,4); + Field fz = Reflections.findField(FF2.class, "z"); + Assert.assertEquals(fz.get(f2), 4); + Field fx2 = Reflections.findField(FF2.class, "x"); + Assert.assertEquals(fx2.get(f2), 3); + Field fy = Reflections.findField(FF2.class, "y"); + Assert.assertEquals(fy.get(f2), 2); + Field fx1 = Reflections.findField(FF1.class, "x"); + Assert.assertEquals(fx1.get(f2), 1); + + Field fxC2 = Reflections.findField(FF2.class, FF2.class.getCanonicalName()+"."+"x"); + Assert.assertEquals(fxC2.get(f2), 3); + Field fxC1 = Reflections.findField(FF2.class, FF1.class.getCanonicalName()+"."+"x"); + Assert.assertEquals(fxC1.get(f2), 1); + } + + @Test + public void testGetFieldValue() { + FF2 f2 = new FF2(1,2,3,4); + Assert.assertEquals(Reflections.getFieldValueMaybe(f2, "x").get(), 3); + Assert.assertEquals(Reflections.getFieldValueMaybe(f2, "y").get(), 2); + + Assert.assertEquals(Reflections.getFieldValueMaybe(f2, FF2.class.getCanonicalName()+"."+"x").get(), 3); + Assert.assertEquals(Reflections.getFieldValueMaybe(f2, FF1.class.getCanonicalName()+"."+"x").get(), 1); + } + } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java index a238e99ffa..ee170cdddc 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java @@ -54,17 +54,8 @@ public void testWriteFieldInFields() { Assert.assertNotNull(resultO); String out = Jsonya.newInstance().add(resultO).toString(); String expected = Jsonya.newInstance().add("type", "java"+":"+Shape.class.getPackage().getName()+"."+YormlBasicTests.class.getSimpleName()+"$"+"Shape") - .at("fields").add("color", "red").root().toString(); + .at("fields").add("color", "red", "size", 0).root().toString(); Assert.assertEquals(out, expected); } -/* -[{ "type": "java:org.apache.brooklyn.util.yorml.tests.YormlBasicTests$Shape", "fields": { "color": "red" } }] but found -[{ "type": "java:org.apache.brooklyn.util.yorml.tests.YormlBasicTests.Shape", "fields": { "color": "red" } }] - -java.lang.AssertionError: expected -[{ "type": "java:org.apache.brooklyn.util.yorml.tests.YormlBasicTests$Shape", "fields": { "color": "red" } }] -[{ "type": "java:class org.apache.brooklyn.util.yorml.tests.YormlBasicTests$Shape", "fields": { "color": "red" } }] - - */ } From 9c16de62dbd54b476acc8cf1f7a10b201f55ca8c Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 24 Jun 2016 11:03:40 +0100 Subject: [PATCH 04/77] more YORML improvements, now dealing with fields in fields, primtives, and coercion --- .../org/apache/brooklyn/util/yorml/Yorml.java | 26 ++++- .../brooklyn/util/yorml/YormlConfig.java | 26 +++++ .../brooklyn/util/yorml/YormlContext.java | 26 ++++- .../util/yorml/YormlContextForRead.java | 37 ++++++ .../util/yorml/YormlContextForWrite.java | 27 +++++ .../brooklyn/util/yorml/YormlConverter.java | 45 +++++++- .../brooklyn/util/yorml/YormlException.java | 18 +++ .../brooklyn/util/yorml/YormlInternals.java | 18 +++ .../brooklyn/util/yorml/YormlReadContext.java | 13 --- .../brooklyn/util/yorml/YormlRequirement.java | 25 ++++ .../brooklyn/util/yorml/YormlSerializer.java | 24 +++- .../util/yorml/YormlTypeRegistry.java | 23 ++++ .../yorml/serializers/FieldsInBlackboard.java | 58 ++++++++++ .../yorml/serializers/FieldsInFieldsMap.java | 69 +++++------ .../yorml/serializers/InstantiateType.java | 56 +++++++-- .../serializers/ReadingTypeOnBlackboard.java | 56 +++++++++ .../YormlSerializerComposition.java | 57 +++++++-- .../org/apache/brooklyn/util/yorml/sketch.md | 18 +++ .../yorml/tests/MockYormlTypeRegistry.java | 61 +++++++++- .../util/yorml/tests/YormlBasicTests.java | 108 +++++++++++++++++- .../util/yorml/tests/YormlTestFixture.java | 64 +++++++++++ 21 files changed, 771 insertions(+), 84 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForRead.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForWrite.java delete mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlReadContext.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlRequirement.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInBlackboard.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ReadingTypeOnBlackboard.java create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java index ff4910904f..7e6741ed4e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.brooklyn.util.yorml; import java.util.List; @@ -32,15 +50,17 @@ public Object read(String yaml) { return read(yaml, null); } public Object read(String yaml, String type) { - Object yamlObject = new org.yaml.snakeyaml.Yaml().load(yaml); - YormlReadContext context = new YormlReadContext("", type); + return readFromYamlObject(new org.yaml.snakeyaml.Yaml().load(yaml), type); + } + public Object readFromYamlObject(Object yamlObject, String type) { + YormlContextForRead context = new YormlContextForRead("", type); context.setYamlObject(yamlObject); new YormlConverter(config).read(context); return context.getJavaObject(); } public Object write(Object java) { - YormlContext context = new YormlContext("", null); + YormlContextForWrite context = new YormlContextForWrite("", null); context.setJavaObject(java); new YormlConverter(config).write(context); return context.getYamlObject(); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConfig.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConfig.java index 87528c4366..da30560ce0 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConfig.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConfig.java @@ -1,16 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.brooklyn.util.yorml; import java.util.List; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.javalang.coerce.TypeCoercer; +import org.apache.brooklyn.util.javalang.coerce.TypeCoercerExtensible; public class YormlConfig { YormlTypeRegistry typeRegistry; + TypeCoercer coercer = TypeCoercerExtensible.newDefault(); + List serializersPost = MutableList.of(); public YormlTypeRegistry getTypeRegistry() { return typeRegistry; } + + public TypeCoercer getCoercer() { + return coercer; + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java index d93e37c347..7a2ce8b601 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java @@ -1,6 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.brooklyn.util.yorml; -public class YormlContext { +public abstract class YormlContext { final String jsonPath; final String expectedType; @@ -12,6 +30,12 @@ public YormlContext(String jsonPath, String expectedType) { this.expectedType = expectedType; } + public String getJsonPath() { + return jsonPath; + } + public String getExpectedType() { + return expectedType; + } public Object getJavaObject() { return javaObject; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForRead.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForRead.java new file mode 100644 index 0000000000..d5763a7956 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForRead.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml; + +import org.apache.brooklyn.util.text.Strings; + +public class YormlContextForRead extends YormlContext { + + public YormlContextForRead(String jsonPath, String expectedType) { + super(jsonPath, expectedType); + } + + String origin; + int offset; + int length; + + @Override + public String toString() { + return "reading"+(expectedType!=null ? " "+expectedType : "")+" at "+(Strings.isNonBlank(jsonPath) ? jsonPath : "root"); + } +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForWrite.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForWrite.java new file mode 100644 index 0000000000..8ac92fd60b --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForWrite.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml; + +public class YormlContextForWrite extends YormlContext { + + public YormlContextForWrite(String jsonPath, String expectedType) { + super(jsonPath, expectedType); + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java index ae71f6a8ed..7a5743f0e3 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.brooklyn.util.yorml; import java.util.List; @@ -6,6 +24,7 @@ import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; +import org.apache.brooklyn.util.yorml.serializers.ReadingTypeOnBlackboard; public class YormlConverter { @@ -20,36 +39,46 @@ public YormlConverter(YormlConfig config) { * makes shallow copy of the object, then goes through serializers modifying it or creating/setting result, * until result is done */ - public Object read(YormlReadContext context) { + public Object read(YormlContextForRead context) { List serializers = MutableList.of().appendAll(config.serializersPost); int i=0; Map blackboard = MutableMap.of(); + ReadingTypeOnBlackboard.get(blackboard); + while (i blackboard) { + for (Object bo: blackboard.values()) { + if (bo instanceof YormlRequirement) { + ((YormlRequirement)bo).checkCompletion(context); + } + } + } + /** * returns jsonable object (map, list, primitive) */ - public Object write(YormlContext context) { + public Object write(YormlContextForWrite context) { List serializers = MutableList.of().appendAll(config.serializersPost); int i=0; Map blackboard = MutableMap.of(); while (i blackboard); + public YormlContinuation read(YormlContextForRead context, YormlConverter converter, Map blackboard); /** * modifies java object and/or yaml object and/or blackboard as appropriate, @@ -20,11 +38,11 @@ public interface YormlSerializer { * returning true if it did anything (and so should restart the cycle). * implementations must NOT return true indefinitely if passed the same instances! */ - public YormlContinuation write(YormlContext context, YormlConfig config, Map blackboard); + public YormlContinuation write(YormlContextForWrite context, YormlConverter converter, Map blackboard); /** * generates human-readable schema for a type using this schema. */ - public String document(String type, YormlConfig config); + public String document(String type, YormlConverter converter); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java index 1b0b46d7ae..736a9ae3d5 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java @@ -1,7 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.brooklyn.util.yorml; public interface YormlTypeRegistry { Object newInstance(String type); + Class getJavaType(String typeName); + + String getTypeName(Object obj); + String getTypeNameOfClass(Class type); + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInBlackboard.java new file mode 100644 index 0000000000..14f6c43a46 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInBlackboard.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.serializers; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.YormlException; +import org.apache.brooklyn.util.yorml.YormlRequirement; + +/** Indicates that something has handled the type + * (on read, creating the java object, and on write, setting the `type` field in the yaml object) + * and made a determination of what fields need to be handled */ +public class FieldsInBlackboard implements YormlRequirement { + private static String KEY = "FIELDS_IN_BLACKBOARD"; + + public static boolean isPresent(Map blackboard) { + return blackboard.containsKey(KEY); + } + public static FieldsInBlackboard peek(Map blackboard) { + return (FieldsInBlackboard) blackboard.get(KEY); + } + public static FieldsInBlackboard getOrCreate(Map blackboard) { + if (!isPresent(blackboard)) { blackboard.put(KEY, new FieldsInBlackboard()); } + return peek(blackboard); + } + public static FieldsInBlackboard create(Map blackboard) { + if (isPresent(blackboard)) { throw new IllegalStateException("Already present"); } + blackboard.put(KEY, new FieldsInBlackboard()); + return peek(blackboard); + } + + List fieldsToWriteFromJava; + + @Override + public void checkCompletion(YormlContext context) { + if (!fieldsToWriteFromJava.isEmpty()) { + throw new YormlException("Incomplete write of Java object data: "+fieldsToWriteFromJava, context); + } + } +} \ No newline at end of file diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java index 5a082bce52..290e1e1931 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java @@ -1,8 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.brooklyn.util.yorml.serializers; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.List; import java.util.Map; import org.apache.brooklyn.util.collections.MutableList; @@ -11,6 +28,8 @@ import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yorml.YormlContextForRead; +import org.apache.brooklyn.util.yorml.YormlContextForWrite; import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; public class FieldsInFieldsMap extends YormlSerializerComposition { @@ -25,7 +44,7 @@ public YormlContinuation read() { Map fields = getFromYamlMap("fields", Map.class); if (fields==null) return YormlContinuation.CONTINUE_UNCHANGED; - for (Object f: ((Map)fields).keySet()) { + for (Object f: MutableList.copyOf( ((Map)fields).keySet() )) { Object v = ((Map)fields).get(f); try { Field ff = Reflections.findField(getJavaObject().getClass(), Strings.toString(f)); @@ -34,8 +53,13 @@ public YormlContinuation read() { } else if (Modifier.isStatic(ff.getModifiers())) { // as above } else { + String fieldType = config.getTypeRegistry().getTypeNameOfClass(ff.getType()); + YormlContextForRead subcontext = new YormlContextForRead(context.getJsonPath()+"/"+f, fieldType); + subcontext.setYamlObject(v); + Object v2 = converter.read(subcontext); + ff.setAccessible(true); - ff.set(getJavaObject(), v); + ff.set(getJavaObject(), v2); ((Map)fields).remove(Strings.toString(f)); if (((Map)fields).isEmpty()) { ((Map)context.getYamlObject()).remove("fields"); @@ -58,9 +82,16 @@ public YormlContinuation write() { Maybe v = Reflections.getFieldValueMaybe(getJavaObject(), f); if (v.isPresent()) { fib.fieldsToWriteFromJava.remove(f); - if (v.get()!=null) { - // TODO assert null checks - fields.put(f, v.get()); + if (v.get()==null) { + // silently drop null fields + } else { + Field ff = Reflections.findFieldMaybe(getJavaObject().getClass(), f).get(); + String fieldType = config.getTypeRegistry().getTypeNameOfClass(ff.getType()); + YormlContextForWrite subcontext = new YormlContextForWrite(context.getJsonPath()+"/"+f, fieldType); + subcontext.setJavaObject(v.get()); + + Object v2 = converter.write(subcontext); + fields.put(f, v2); } } } @@ -71,31 +102,5 @@ public YormlContinuation write() { return YormlContinuation.RESTART; } } - - /** Indicates that something has handled the type - * (on read, creating the java object, and on write, setting the `type` field in the yaml object) - * and made a determination of what fields need to be handled */ - public static class FieldsInBlackboard { - private static String KEY = "FIELDS_IN_BLACKBOARD"; - - public static boolean isPresent(Map blackboard) { - return blackboard.containsKey(KEY); - } - public static FieldsInBlackboard peek(Map blackboard) { - return (FieldsInBlackboard) blackboard.get(KEY); - } - public static FieldsInBlackboard getOrCreate(Map blackboard) { - if (!isPresent(blackboard)) { blackboard.put(KEY, new FieldsInBlackboard()); } - return peek(blackboard); - } - public static FieldsInBlackboard create(Map blackboard) { - if (isPresent(blackboard)) { throw new IllegalStateException("Already present"); } - blackboard.put(KEY, new FieldsInBlackboard()); - return peek(blackboard); - } - - List fieldsToWriteFromJava; - // TODO if fields are *required* ? - } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java index 69a4b44358..e307ad5afb 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.brooklyn.util.yorml.serializers; import java.lang.reflect.Field; @@ -10,8 +28,8 @@ import org.apache.brooklyn.util.javalang.FieldOrderings; import org.apache.brooklyn.util.javalang.ReflectionPredicates; import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; -import org.apache.brooklyn.util.yorml.serializers.FieldsInFieldsMap.FieldsInBlackboard; public class InstantiateType extends YormlSerializerComposition { @@ -21,15 +39,37 @@ public static class Worker extends YormlSerializerWorker { public YormlContinuation read() { if (hasJavaObject()) return YormlContinuation.CONTINUE_UNCHANGED; - // TODO check is primitive? + Class expectedJavaType = getExpectedTypeJava(); + + String type = null; + if ((getYamlObject() instanceof String) || Boxing.isPrimitiveOrBoxedObject(getYamlObject())) { + // default handling of primitives: + // if type is expected, try to coerce + if (expectedJavaType!=null) { + Maybe result = config.getCoercer().tryCoerce(getYamlObject(), expectedJavaType); + if (result.isPresent()) { + context.setJavaObject(result.get()); + return YormlContinuation.RESTART; + } + ReadingTypeOnBlackboard.get(blackboard).addNote("Cannot interpret '"+getYamlObject()+"' as "+expectedJavaType.getCanonicalName()); + return YormlContinuation.CONTINUE_UNCHANGED; + } + // if type not expected, treat as a type + type = Strings.toString(getYamlObject()); + } + // TODO if map and list? - String type = getFromYamlMap("type", String.class); + if (type==null) type = getFromYamlMap("type", String.class); if (type==null) return YormlContinuation.CONTINUE_UNCHANGED; Object result = config.getTypeRegistry().newInstance((String)type); - context.setJavaObject(result); + if (result==null) { + ReadingTypeOnBlackboard.get(blackboard).addNote("Unknown type '"+type+"'"); + return YormlContinuation.CONTINUE_UNCHANGED; + } + context.setJavaObject(result); return YormlContinuation.RESTART; } @@ -42,9 +82,9 @@ public YormlContinuation write() { fib.fieldsToWriteFromJava = MutableList.of(); Object jo = getJavaObject(); - if (Boxing.isPrimitiveOrBoxedObject(jo)) { - context.setJavaObject(jo); - return YormlContinuation.RESTART; + if (Boxing.isPrimitiveOrBoxedObject(jo) || jo instanceof CharSequence) { + context.setYamlObject(jo); + return YormlContinuation.FINISHED; } // TODO map+list -- here, or in separate serializers? @@ -55,7 +95,7 @@ public YormlContinuation write() { // TODO look up registry type // TODO support osgi - map.put("type", "java:"+getJavaObject().getClass().getName()); + map.put("type", config.getTypeRegistry().getTypeName(getJavaObject()) ); List fields = Reflections.findFields(getJavaObject().getClass(), null, diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ReadingTypeOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ReadingTypeOnBlackboard.java new file mode 100644 index 0000000000..3bce75e93c --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ReadingTypeOnBlackboard.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.serializers; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.YormlException; +import org.apache.brooklyn.util.yorml.YormlRequirement; + +public class ReadingTypeOnBlackboard implements YormlRequirement { + + List errorNotes = MutableList.of(); + + public static final String KEY = ReadingTypeOnBlackboard.class.getCanonicalName(); + + public static ReadingTypeOnBlackboard get(Map blackboard) { + Object v = blackboard.get(KEY); + if (v==null) { + v = new ReadingTypeOnBlackboard(); + blackboard.put(KEY, v); + } + return (ReadingTypeOnBlackboard) v; + } + + @Override + public void checkCompletion(YormlContext context) { + if (context.getJavaObject()!=null) return; + if (errorNotes.isEmpty()) throw new YormlException("No means to identify type to instantiate", context); + throw new YormlException(Strings.join(errorNotes, "; "), context); + } + + public void addNote(String message) { + errorNotes.add(message); + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java index 7cde5a11bd..971c189df3 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java @@ -1,12 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.brooklyn.util.yorml.serializers; import java.util.Map; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yorml.YormlConfig; import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.YormlContextForWrite; +import org.apache.brooklyn.util.yorml.YormlConverter; import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; -import org.apache.brooklyn.util.yorml.YormlReadContext; +import org.apache.brooklyn.util.yorml.YormlContextForRead; import org.apache.brooklyn.util.yorml.YormlSerializer; public class YormlSerializerComposition implements YormlSerializer { @@ -19,26 +40,38 @@ public YormlSerializerComposition(Class workerT public abstract static class YormlSerializerWorker { + protected YormlConverter converter; protected YormlContext context; - protected YormlReadContext readContext; + protected YormlContextForRead readContext; protected YormlConfig config; protected Map blackboard; - private void initRead(YormlReadContext context, YormlConfig config, Map blackboard) { + private void initRead(YormlContextForRead context, YormlConverter converter, Map blackboard) { if (this.context!=null) throw new IllegalStateException("Already initialized, for "+context); this.context = context; this.readContext = context; - this.config = config; + this.converter = converter; + this.config = converter.getConfig(); this.blackboard = blackboard; } - private void initWrite(YormlContext context, YormlConfig config, Map blackboard) { + private void initWrite(YormlContextForWrite context, YormlConverter converter, Map blackboard) { if (this.context!=null) throw new IllegalStateException("Already initialized, for "+context); this.context = context; - this.config = config; + this.converter = converter; + this.config = converter.getConfig(); this.blackboard = blackboard; } - + + /** If there is an expected type -- other than "Object"! -- return the java instance. Otherwise null. */ + public Class getExpectedTypeJava() { + String et = context.getExpectedType(); + if (Strings.isBlank(et)) return null; + Class ett = config.getTypeRegistry().getJavaType(et); + if (Object.class.equals(ett)) return null; + return ett; + } + public boolean hasJavaObject() { return context.getJavaObject()!=null; } public boolean hasYamlObject() { return context.getYamlObject()!=null; } public Object getJavaObject() { return context.getJavaObject(); } @@ -66,27 +99,27 @@ protected void setInYamlMap(String key, Object value) { } @Override - public YormlContinuation read(YormlReadContext context, YormlConfig config, Map blackboard) { + public YormlContinuation read(YormlContextForRead context, YormlConverter converter, Map blackboard) { YormlSerializerWorker worker; try { worker = workerType.newInstance(); } catch (Exception e) { throw Exceptions.propagate(e); } - worker.initRead(context, config, blackboard); + worker.initRead(context, converter, blackboard); return worker.read(); } @Override - public YormlContinuation write(YormlContext context, YormlConfig config, Map blackboard) { + public YormlContinuation write(YormlContextForWrite context, YormlConverter converter, Map blackboard) { YormlSerializerWorker worker; try { worker = workerType.newInstance(); } catch (Exception e) { throw Exceptions.propagate(e); } - worker.initWrite(context, config, blackboard); + worker.initWrite(context, converter, blackboard); return worker.write(); } @Override - public String document(String type, YormlConfig config) { + public String document(String type, YormlConverter converter) { return null; } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md index 22164e147a..9b9a789442 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md @@ -1,3 +1,21 @@ +% +% Licensed to the Apache Software Foundation (ASF) under one +% or more contributor license agreements. See the NOTICE file +% distributed with this work for additional information +% regarding copyright ownership. The ASF licenses this file +% to you under the Apache License, Version 2.0 (the +% "License"); you may not use this file except in compliance +% with the License. You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, +% software distributed under the License is distributed on an +% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +% KIND, either express or implied. See the License for the +% specific language governing permissions and limitations +% under the License. +% # YORML: The YAML Object Relational Mapping Language diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java index aa3187e26e..14c0df93f6 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java @@ -1,9 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.brooklyn.util.yorml.tests; import java.util.Map; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.javalang.Boxing; +import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yorml.YormlTypeRegistry; public class MockYormlTypeRegistry implements YormlTypeRegistry { @@ -12,8 +33,10 @@ public class MockYormlTypeRegistry implements YormlTypeRegistry { @Override public Object newInstance(String typeName) { - Class type = types.get(typeName); - if (type==null) return null; + Class type = getJavaType(typeName); + if (type==null) { + return null; + } try { return type.newInstance(); } catch (Exception e) { @@ -21,8 +44,42 @@ public Object newInstance(String typeName) { } } + @Override + public java.lang.Class getJavaType(String typeName) { + Class type = types.get(typeName); + if (type==null) type = Boxing.getPrimitiveType(typeName).orNull(); + if (type==null && "string".equals(typeName)) type = String.class; + if (type==null && typeName.startsWith("java:")) { + typeName = Strings.removeFromStart(typeName, "java:"); + try { + // TODO use injected loader? + type = Class.forName(typeName); + } catch (ClassNotFoundException e) { + // ignore, this isn't a java type + } + } + return type; + } + public void put(String typeName, Class type) { types.put(typeName, type); } + @Override + public String getTypeName(Object obj) { + return getTypeNameOfClass(obj.getClass()); + } + + @Override + public String getTypeNameOfClass(Class type) { + for (Map.Entry> t: types.entrySet()) { + if (t.getValue().equals(type)) return t.getKey(); + } + Maybe primitive = Boxing.getPrimitiveName(type); + if (primitive.isPresent()) return primitive.get(); + if (String.class.equals(type)) return "string"; + // TODO map and list? + + return "java:"+type.getName(); + } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java index ee170cdddc..0bece7e2b7 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java @@ -1,18 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.brooklyn.util.yorml.tests; +import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.Jsonya; import org.apache.brooklyn.util.yorml.Yorml; import org.testng.Assert; import org.testng.annotations.Test; +import com.google.common.base.Objects; + +/** Tests that the default serializers can read/write types and fields. + *

+ * And shows how to use them at a low level. + */ public class YormlBasicTests { - public static class Shape { + static class Shape { String name; String color; int size; + + @Override + public boolean equals(Object xo) { + if (xo==null || !Objects.equal(getClass(), xo.getClass())) return false; + Shape x = (Shape) xo; + return Objects.equal(name, x.name) && Objects.equal(color, x.color) && Objects.equal(size, x.size); + } } + // very basic tests illustrating read/write + @Test public void testReadJavaType() { MockYormlTypeRegistry tr = new MockYormlTypeRegistry(); @@ -42,9 +76,8 @@ public void testReadFieldInFields() { } @Test - public void testWriteFieldInFields() { + public void testWritePrimitiveFieldInFields() { MockYormlTypeRegistry tr = new MockYormlTypeRegistry(); - tr.put("shape", Shape.class); Yorml y = Yorml.newInstance(tr); Shape s = new Shape(); @@ -53,9 +86,76 @@ public void testWriteFieldInFields() { Assert.assertNotNull(resultO); String out = Jsonya.newInstance().add(resultO).toString(); - String expected = Jsonya.newInstance().add("type", "java"+":"+Shape.class.getPackage().getName()+"."+YormlBasicTests.class.getSimpleName()+"$"+"Shape") + String expected = Jsonya.newInstance().add("type", "java"+":"+Shape.class.getName()) .at("fields").add("color", "red", "size", 0).root().toString(); Assert.assertEquals(out, expected); } + + // now using the fixture + + @Test + public void testSimpleFieldInFieldsWithTestCase() { + Shape s = new Shape(); + s.color = "red"; + + YormlTestFixture.newInstance().writing(s). + reading( + Jsonya.newInstance().add("type", "java"+":"+Shape.class.getName()) + .at("fields").add("color", "red", "size", 0).root().toString() + ).doReadWriteAssertingJsonMatch(); + } + + @Test + public void testStringOnItsOwn() { + YormlTestFixture.newInstance(). + write("hello").assertResult("hello"). + read("hello", "string").assertResult("hello"); + } + + @Test + public void testFailOnUnknownType() { + try { + YormlTestFixture ytc = YormlTestFixture.newInstance().read("{ type: shape }", null); + Asserts.shouldHaveFailedPreviously("Got "+ytc.lastReadResult+" when we should have failed due to unknown type shape"); + } catch (Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, "shape", "unknown type"); + } + } + + public static class ShapePair { + String pairName; + Shape shape1; + Shape shape2; + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ShapePair)) return false; + ShapePair sp = (ShapePair)obj; + return Objects.equal(pairName, sp.pairName) && Objects.equal(shape1, sp.shape1) && Objects.equal(shape2, sp.shape2); + } + } + + @Test + public void testWriteComplexFieldInFields() { + ShapePair pair = new ShapePair(); + pair.shape1 = new Shape(); + pair.shape1.color = "red"; + pair.shape2 = new Shape(); + pair.shape2.color = "blue"; + pair.shape2.size = 8; + + YormlTestFixture.newInstance(). + addType("shape", Shape.class). + addType("shape-pair", ShapePair.class). + writing(pair). + reading( + Jsonya.newInstance().add("type", "shape-pair") + .at("fields", "shape1").add("type", "shape") + .at("fields").add("color", "red", "size", 0) + .root() + .at("fields", "shape2").add("type", "shape") + .at("fields").add("color", "blue", "size", 8) + .root().toString() + ).doReadWriteAssertingJsonMatch(); + } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java new file mode 100644 index 0000000000..63c15cefe7 --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java @@ -0,0 +1,64 @@ +package org.apache.brooklyn.util.yorml.tests; + +import org.apache.brooklyn.util.collections.Jsonya; +import org.apache.brooklyn.util.yorml.Yorml; +import org.testng.Assert; + +public class YormlTestFixture { + + public static YormlTestFixture newInstance() { return new YormlTestFixture(); } + + MockYormlTypeRegistry tr = new MockYormlTypeRegistry(); + Yorml y = Yorml.newInstance(tr); + + Object writeObject; + Object lastWriteResult; + String readObject; + String readObjectExpectedType; + Object lastReadResult; + Object lastResult; + + public YormlTestFixture writing(Object objectToWrite) { + writeObject = objectToWrite; + return this; + } + public YormlTestFixture reading(String stringToRead, String expectedType) { + readObject = stringToRead; + readObjectExpectedType = expectedType; + return this; + } + public YormlTestFixture reading(String stringToRead) { + readObject = stringToRead; + return this; + } + + public YormlTestFixture write(Object objectToWrite) { + writing(objectToWrite); + lastWriteResult = y.write(objectToWrite); + lastResult = lastWriteResult; + return this; + } + public YormlTestFixture read(String objectToRead, String expectedType) { + reading(objectToRead, expectedType); + lastReadResult = y.read(objectToRead, expectedType); + lastResult = lastReadResult; + return this; + } + + public YormlTestFixture assertResult(Object expectation) { + Assert.assertEquals(lastResult, expectation); + return this; + } + public YormlTestFixture doReadWriteAssertingJsonMatch() { + read(readObject, readObjectExpectedType); + write(writeObject); + Assert.assertEquals(Jsonya.newInstance().add(lastWriteResult).toString(), readObject, "Write output matches read input."); + Assert.assertEquals(lastReadResult, writeObject, "Read output matches write input."); + return this; + } + + public YormlTestFixture addType(String name, Class type) { + tr.put(name, type); + return this; + } +} \ No newline at end of file From 0f509067da8ce105fa0b10e2c5d1d0cf7b94d9fe Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 24 Jun 2016 22:50:37 +0100 Subject: [PATCH 05/77] more yorml - implicit types and fail on incomplete read --- .../org/apache/brooklyn/util/yorml/Yorml.java | 13 +- .../brooklyn/util/yorml/YormlSerializer.java | 7 + ...dsMap.java => FieldsInMapUnderFields.java} | 41 ++--- .../yorml/serializers/InstantiateType.java | 30 +++- ...board.java => JavaFieldsOnBlackboard.java} | 17 +- .../serializers/YamlKeysOnBlackboard.java | 57 +++++++ .../YormlSerializerComposition.java | 23 ++- .../org/apache/brooklyn/util/yorml/sketch.md | 10 +- .../brooklyn/util/guava/IfFunctionsTest.java | 1 + .../util/yorml/tests/YormlBasicTests.java | 161 +++++++++++++++--- .../util/yorml/tests/YormlTestFixture.java | 50 +++++- 11 files changed, 334 insertions(+), 76 deletions(-) rename utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/{FieldsInFieldsMap.java => FieldsInMapUnderFields.java} (72%) rename utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/{FieldsInBlackboard.java => JavaFieldsOnBlackboard.java} (75%) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java index 7e6741ed4e..ffad857cc6 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java @@ -21,7 +21,7 @@ import java.util.List; import org.apache.brooklyn.util.collections.MutableList; -import org.apache.brooklyn.util.yorml.serializers.FieldsInFieldsMap; +import org.apache.brooklyn.util.yorml.serializers.FieldsInMapUnderFields; import org.apache.brooklyn.util.yorml.serializers.InstantiateType; @@ -33,7 +33,7 @@ private Yorml() {} public static Yorml newInstance(YormlTypeRegistry typeRegistry) { return newInstance(typeRegistry, MutableList.of( - new FieldsInFieldsMap(), + new FieldsInMapUnderFields(), new InstantiateType() )); } @@ -49,8 +49,8 @@ public static Yorml newInstance(YormlTypeRegistry typeRegistry, List + * Instances of this class should be thread-safe for use with simultaneous conversions. + * Often implementations will extend {@link YormlSerializerComposition} and which stores + * per-conversion data in a per-method-invocation object. + */ public interface YormlSerializer { /** diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java similarity index 72% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java index 290e1e1931..a24f13198d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInFieldsMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java @@ -32,37 +32,40 @@ import org.apache.brooklyn.util.yorml.YormlContextForWrite; import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; -public class FieldsInFieldsMap extends YormlSerializerComposition { +public class FieldsInMapUnderFields extends YormlSerializerComposition { - public FieldsInFieldsMap() { super(Worker.class); } + public FieldsInMapUnderFields() { super(Worker.class); } public static class Worker extends YormlSerializerWorker { public YormlContinuation read() { if (!hasJavaObject()) return YormlContinuation.CONTINUE_UNCHANGED; @SuppressWarnings("unchecked") - Map fields = getFromYamlMap("fields", Map.class); + Map fields = peekFromYamlKeysOnBlackboard("fields", Map.class).orNull(); if (fields==null) return YormlContinuation.CONTINUE_UNCHANGED; for (Object f: MutableList.copyOf( ((Map)fields).keySet() )) { Object v = ((Map)fields).get(f); try { - Field ff = Reflections.findField(getJavaObject().getClass(), Strings.toString(f)); - if (ff==null) { - // just skip (could throw, but leave it in case something else recognises) - } else if (Modifier.isStatic(ff.getModifiers())) { - // as above + Maybe ffm = Reflections.findFieldMaybe(getJavaObject().getClass(), Strings.toString(f)); + if (ffm.isAbsentOrNull()) { + // just skip (could throw, but leave it in case something else recognises it?) } else { - String fieldType = config.getTypeRegistry().getTypeNameOfClass(ff.getType()); - YormlContextForRead subcontext = new YormlContextForRead(context.getJsonPath()+"/"+f, fieldType); - subcontext.setYamlObject(v); - Object v2 = converter.read(subcontext); - - ff.setAccessible(true); - ff.set(getJavaObject(), v2); - ((Map)fields).remove(Strings.toString(f)); - if (((Map)fields).isEmpty()) { - ((Map)context.getYamlObject()).remove("fields"); + Field ff = ffm.get(); + if (Modifier.isStatic(ff.getModifiers())) { + // as above + } else { + String fieldType = config.getTypeRegistry().getTypeNameOfClass(ff.getType()); + YormlContextForRead subcontext = new YormlContextForRead(context.getJsonPath()+"/"+f, fieldType); + subcontext.setYamlObject(v); + Object v2 = converter.read(subcontext); + + ff.setAccessible(true); + ff.set(getJavaObject(), v2); + ((Map)fields).remove(Strings.toString(f)); + if (((Map)fields).isEmpty()) { + removeFromYamlKeysOnBlackboard("fields"); + } } } } catch (Exception e) { throw Exceptions.propagate(e); } @@ -73,7 +76,7 @@ public YormlContinuation read() { public YormlContinuation write() { if (!isYamlMap()) return YormlContinuation.CONTINUE_UNCHANGED; if (getFromYamlMap("fields", Map.class)!=null) return YormlContinuation.CONTINUE_UNCHANGED; - FieldsInBlackboard fib = FieldsInBlackboard.peek(blackboard); + JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); if (fib==null || fib.fieldsToWriteFromJava.isEmpty()) return YormlContinuation.CONTINUE_UNCHANGED; Map fields = MutableMap.of(); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java index e307ad5afb..16180684b8 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java @@ -60,14 +60,26 @@ public YormlContinuation read() { // TODO if map and list? - if (type==null) type = getFromYamlMap("type", String.class); - if (type==null) return YormlContinuation.CONTINUE_UNCHANGED; + YamlKeysOnBlackboard.create(blackboard).yamlKeysToReadToJava = MutableMap.copyOf(getYamlMap()); + + if (type==null) type = peekFromYamlKeysOnBlackboard("type", String.class).orNull(); + Object result = null; + if (type==null && expectedJavaType!=null) { + Maybe resultM = Reflections.invokeConstructorFromArgsIncludingPrivate(expectedJavaType); + if (resultM.isPresent()) result = resultM.get(); + else ReadingTypeOnBlackboard.get(blackboard).addNote("Expected type is not no-arg instantiable: '"+expectedJavaType+"' ("+ + ((Maybe.Absent)resultM).getException()+")"); + } + if (type==null && result==null) return YormlContinuation.CONTINUE_UNCHANGED; - Object result = config.getTypeRegistry().newInstance((String)type); + if (result==null) { + result = config.getTypeRegistry().newInstance((String)type); + } if (result==null) { ReadingTypeOnBlackboard.get(blackboard).addNote("Unknown type '"+type+"'"); return YormlContinuation.CONTINUE_UNCHANGED; } + removeFromYamlKeysOnBlackboard("type"); context.setJavaObject(result); return YormlContinuation.RESTART; @@ -76,9 +88,9 @@ public YormlContinuation read() { public YormlContinuation write() { if (hasYamlObject()) return YormlContinuation.CONTINUE_UNCHANGED; if (!hasJavaObject()) return YormlContinuation.CONTINUE_UNCHANGED; - if (FieldsInBlackboard.isPresent(blackboard)) return YormlContinuation.CONTINUE_UNCHANGED; + if (JavaFieldsOnBlackboard.isPresent(blackboard)) return YormlContinuation.CONTINUE_UNCHANGED; - FieldsInBlackboard fib = FieldsInBlackboard.create(blackboard); + JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.create(blackboard); fib.fieldsToWriteFromJava = MutableList.of(); Object jo = getJavaObject(); @@ -95,7 +107,11 @@ public YormlContinuation write() { // TODO look up registry type // TODO support osgi - map.put("type", config.getTypeRegistry().getTypeName(getJavaObject()) ); + if (getJavaObject().getClass().equals(getExpectedTypeJava())) { + // skip explicitly writing the type + } else { + map.put("type", config.getTypeRegistry().getTypeName(getJavaObject()) ); + } List fields = Reflections.findFields(getJavaObject().getClass(), null, @@ -103,7 +119,7 @@ public YormlContinuation write() { Field lastF = null; for (Field f: fields) { Maybe v = Reflections.getFieldValueMaybe(getJavaObject(), f); - if (ReflectionPredicates.IS_FIELD_NON_TRANSIENT.apply(f) && v.isPresentAndNonNull()) { + if (ReflectionPredicates.IS_FIELD_NON_TRANSIENT.apply(f) && ReflectionPredicates.IS_FIELD_NON_STATIC.apply(f) && v.isPresentAndNonNull()) { String name = f.getName(); if (lastF!=null && lastF.getName().equals(f.getName())) { // if field is shadowed use FQN diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/JavaFieldsOnBlackboard.java similarity index 75% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInBlackboard.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/JavaFieldsOnBlackboard.java index 14f6c43a46..89275c2932 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/JavaFieldsOnBlackboard.java @@ -28,22 +28,23 @@ /** Indicates that something has handled the type * (on read, creating the java object, and on write, setting the `type` field in the yaml object) * and made a determination of what fields need to be handled */ -public class FieldsInBlackboard implements YormlRequirement { - private static String KEY = "FIELDS_IN_BLACKBOARD"; +public class JavaFieldsOnBlackboard implements YormlRequirement { + + private static String KEY = JavaFieldsOnBlackboard.class.getName(); public static boolean isPresent(Map blackboard) { return blackboard.containsKey(KEY); } - public static FieldsInBlackboard peek(Map blackboard) { - return (FieldsInBlackboard) blackboard.get(KEY); + public static JavaFieldsOnBlackboard peek(Map blackboard) { + return (JavaFieldsOnBlackboard) blackboard.get(KEY); } - public static FieldsInBlackboard getOrCreate(Map blackboard) { - if (!isPresent(blackboard)) { blackboard.put(KEY, new FieldsInBlackboard()); } + public static JavaFieldsOnBlackboard getOrCreate(Map blackboard) { + if (!isPresent(blackboard)) { blackboard.put(KEY, new JavaFieldsOnBlackboard()); } return peek(blackboard); } - public static FieldsInBlackboard create(Map blackboard) { + public static JavaFieldsOnBlackboard create(Map blackboard) { if (isPresent(blackboard)) { throw new IllegalStateException("Already present"); } - blackboard.put(KEY, new FieldsInBlackboard()); + blackboard.put(KEY, new JavaFieldsOnBlackboard()); return peek(blackboard); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java new file mode 100644 index 0000000000..fc1a5bfce2 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.serializers; + +import java.util.Map; + +import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.YormlException; +import org.apache.brooklyn.util.yorml.YormlRequirement; + +/** Keys from a YAML map that still need to be handled */ +public class YamlKeysOnBlackboard implements YormlRequirement { + + private static String KEY = YamlKeysOnBlackboard.class.getName(); + + public static boolean isPresent(Map blackboard) { + return blackboard.containsKey(KEY); + } + public static YamlKeysOnBlackboard peek(Map blackboard) { + return (YamlKeysOnBlackboard) blackboard.get(KEY); + } + public static YamlKeysOnBlackboard getOrCreate(Map blackboard) { + if (!isPresent(blackboard)) { blackboard.put(KEY, new YamlKeysOnBlackboard()); } + return peek(blackboard); + } + public static YamlKeysOnBlackboard create(Map blackboard) { + if (isPresent(blackboard)) { throw new IllegalStateException("Already present"); } + blackboard.put(KEY, new YamlKeysOnBlackboard()); + return peek(blackboard); + } + + Map yamlKeysToReadToJava; + + @Override + public void checkCompletion(YormlContext context) { + if (!yamlKeysToReadToJava.isEmpty()) { + // TODO limit to depth 2 ? + throw new YormlException("Incomplete read of YAML keys: "+yamlKeysToReadToJava, context); + } + } +} \ No newline at end of file diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java index 971c189df3..aae79856fe 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java @@ -21,13 +21,14 @@ import java.util.Map; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yorml.YormlConfig; import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.YormlContextForRead; import org.apache.brooklyn.util.yorml.YormlContextForWrite; import org.apache.brooklyn.util.yorml.YormlConverter; import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; -import org.apache.brooklyn.util.yorml.YormlContextForRead; import org.apache.brooklyn.util.yorml.YormlSerializer; public class YormlSerializerComposition implements YormlSerializer { @@ -81,7 +82,9 @@ public Class getExpectedTypeJava() { @SuppressWarnings("unchecked") public Map getYamlMap() { return (Map)context.getYamlObject(); } /** Returns the value of the given key if present in the map and of the given type. - * If the YAML is not a map, or the key is not present, or the type is different, this returns null. */ + * If the YAML is not a map, or the key is not present, or the type is different, this returns null. + *

+ * See also {@link #peekFromYamlKeysOnBlackboard(String, Class)} which most read serializers should use. */ @SuppressWarnings("unchecked") public T getFromYamlMap(String key, Class type) { if (!isYamlMap()) return null; @@ -93,7 +96,21 @@ public T getFromYamlMap(String key, Class type) { protected void setInYamlMap(String key, Object value) { ((Map)getYamlMap()).put(key, value); } - + @SuppressWarnings("unchecked") + protected Maybe peekFromYamlKeysOnBlackboard(String key, Class expectedType) { + YamlKeysOnBlackboard ykb = YamlKeysOnBlackboard.peek(blackboard); + if (ykb==null || ykb.yamlKeysToReadToJava==null || !ykb.yamlKeysToReadToJava.containsKey(key)) { + return Maybe.absent(); + } + Object v = ykb.yamlKeysToReadToJava.get(key); + if (expectedType!=null && !expectedType.isInstance(v)) return Maybe.absent(); + return Maybe.of((T)v); + } + protected void removeFromYamlKeysOnBlackboard(String key) { + YamlKeysOnBlackboard ykb = YamlKeysOnBlackboard.peek(blackboard); + ykb.yamlKeysToReadToJava.remove(key); + } + public abstract YormlContinuation read(); public abstract YormlContinuation write(); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md index 9b9a789442..1496426c14 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md @@ -19,7 +19,7 @@ # YORML: The YAML Object Relational Mapping Language -## Movitvation +## Motivation We want a JSON/YAML schema which allows us to do bi-directional serialization to Java with docgen. That is: @@ -455,6 +455,14 @@ So the general process is: * adjust the data structure until no further adjustments are made * check that everything that needed to be done was done +### TODO + +* explicit-field reads fields at root +* explicit-field aliases +* defining serializers? +* complex syntax, type as key, etc +* maps and lists + ## Real World Use Cases diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/guava/IfFunctionsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/guava/IfFunctionsTest.java index 7e140606ca..5d25351271 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/guava/IfFunctionsTest.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/guava/IfFunctionsTest.java @@ -39,6 +39,7 @@ public void testNoBuilder() { checkTF(IfFunctions.ifEquals(false).value("F").ifEquals(true).value("T").defaultValue("?"), "?"); } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testPredicateAndSupplier() { // we cannot use checkTF here as an IntelliJ issues causes the project to fail to launch as IntelliJ does not diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java index 0bece7e2b7..4ec14be65a 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java @@ -35,16 +35,18 @@ public class YormlBasicTests { static class Shape { String name; String color; - int size; @Override public boolean equals(Object xo) { if (xo==null || !Objects.equal(getClass(), xo.getClass())) return false; Shape x = (Shape) xo; - return Objects.equal(name, x.name) && Objects.equal(color, x.color) && Objects.equal(size, x.size); + return Objects.equal(name, x.name) && Objects.equal(color, x.color); } + + public Shape name(String name) { this.name = name; return this; } + public Shape color(String color) { this.color = color; return this; } } - + // very basic tests illustrating read/write @Test @@ -80,13 +82,13 @@ public void testWritePrimitiveFieldInFields() { MockYormlTypeRegistry tr = new MockYormlTypeRegistry(); Yorml y = Yorml.newInstance(tr); - Shape s = new Shape(); + Shape s = new ShapeWithSize(); s.color = "red"; Object resultO = y.write(s); Assert.assertNotNull(resultO); String out = Jsonya.newInstance().add(resultO).toString(); - String expected = Jsonya.newInstance().add("type", "java"+":"+Shape.class.getName()) + String expected = Jsonya.newInstance().add("type", "java"+":"+ShapeWithSize.class.getName()) .at("fields").add("color", "red", "size", 0).root().toString(); Assert.assertEquals(out, expected); } @@ -94,24 +96,88 @@ public void testWritePrimitiveFieldInFields() { // now using the fixture @Test - public void testSimpleFieldInFieldsWithTestCase() { - Shape s = new Shape(); - s.color = "red"; - - YormlTestFixture.newInstance().writing(s). + public void testFieldInFieldsUsingTestFixture() { + YormlTestFixture.newInstance().writing( new Shape().color("red") ). reading( Jsonya.newInstance().add("type", "java"+":"+Shape.class.getName()) - .at("fields").add("color", "red", "size", 0).root().toString() - ).doReadWriteAssertingJsonMatch(); + .at("fields").add("color", "red").root().toString() ). + doReadWriteAssertingJsonMatch(); } @Test - public void testStringOnItsOwn() { + public void testStringPrimitiveOnItsOwn() { YormlTestFixture.newInstance(). write("hello").assertResult("hello"). - read("hello", "string").assertResult("hello"); + read("hello", "string"). + assertResult("hello"); + } + + @Test + public void testRegisteredType() { + YormlTestFixture.newInstance(). + addType("shape", Shape.class). + writing(new Shape()). + reading( "{ type: shape }" ). + doReadWriteAssertingJsonMatch(); + } + + @Test + public void testExpectedType() { + YormlTestFixture.newInstance(). + addType("shape", Shape.class). + writing(new Shape().color("red"), "shape"). + reading( "{ fields: { color: red } }", "shape" ). + doReadWriteAssertingJsonMatch(); + } + + @Test + public void testExtraFieldError() { + // TODO see failures + // TODO extra blackboard item of fields to handle + // TODO instantiate expected type if none explicit + try { + YormlTestFixture.newInstance(). + addType("shape", Shape.class). + read( "{ type: shape, fields: { size: 4 } }", "shape" ); + Asserts.shouldHaveFailedPreviously("should complain about fields still existing"); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "size"); + } + } + + static class ShapeWithSize extends Shape { + int size; + + @Override + public boolean equals(Object xo) { + return super.equals(xo) && Objects.equal(size, ((ShapeWithSize)xo).size); + } + + public ShapeWithSize size(int size) { this.size = size; return this; } + public ShapeWithSize name(String name) { return (ShapeWithSize)super.name(name); } + public ShapeWithSize color(String color) { return (ShapeWithSize)super.color(color); } + } + + @Test + public void testFieldInExtendedClassInFields() { + YormlTestFixture.newInstance().writing( new ShapeWithSize().size(4).color("red") ). + reading( + Jsonya.newInstance().add("type", "java"+":"+ShapeWithSize.class.getName()) + .at("fields").add("color", "red", "size", 4).root().toString() ). + doReadWriteAssertingJsonMatch(); } + @Test + public void testFieldInExtendedClassInFieldsDefault() { + // note we get 0 written + YormlTestFixture.newInstance().writing( new ShapeWithSize().color("red") ). + reading( + Jsonya.newInstance().add("type", "java"+":"+ShapeWithSize.class.getName()) + .at("fields").add("color", "red", "size", 0).root().toString() ). + doReadWriteAssertingJsonMatch(); + } + + @Test public void testFailOnUnknownType() { try { @@ -122,7 +188,7 @@ public void testFailOnUnknownType() { } } - public static class ShapePair { + static class ShapePair { String pairName; Shape shape1; Shape shape2; @@ -137,25 +203,72 @@ public boolean equals(Object obj) { @Test public void testWriteComplexFieldInFields() { ShapePair pair = new ShapePair(); - pair.shape1 = new Shape(); - pair.shape1.color = "red"; - pair.shape2 = new Shape(); - pair.shape2.color = "blue"; - pair.shape2.size = 8; + pair.pairName = "red and blue"; + pair.shape1 = new Shape().color("red"); + pair.shape2 = new ShapeWithSize().size(8).color("blue"); YormlTestFixture.newInstance(). addType("shape", Shape.class). + addType("shape-with-size", ShapeWithSize.class). addType("shape-pair", ShapePair.class). writing(pair). reading( Jsonya.newInstance().add("type", "shape-pair") - .at("fields", "shape1").add("type", "shape") - .at("fields").add("color", "red", "size", 0) + .at("fields").add("pairName", pair.pairName) + .at("shape1") + // .add("type", "shape") // default is suppressed + .at("fields").add("color", pair.shape1.color) .root() - .at("fields", "shape2").add("type", "shape") - .at("fields").add("color", "blue", "size", 8) + .at("fields", "shape2") + .add("type", "shape-with-size") + .at("fields").add("color", pair.shape2.color, "size", ((ShapeWithSize)pair.shape2).size) .root().toString() ).doReadWriteAssertingJsonMatch(); } + static class ShapeWithWeirdFields extends ShapeWithSize { + static int aStatic = 1; + transient int aTransient = 11; + + /** masks name in parent */ + String name; + ShapeWithWeirdFields realName(String name) { this.name = name; return this; } + } + + @Test + public void testStaticNotWrittenButExtendedItemsAre() { + ShapeWithWeirdFields shape = new ShapeWithWeirdFields(); + shape.size(4).name("weird-shape"); + shape.realName("normal-trust-me"); + ShapeWithWeirdFields.aStatic = 2; + shape.aTransient = 12; + + YormlTestFixture.newInstance(). + addType("shape-weird", ShapeWithWeirdFields.class). + writing(shape).reading("{ \"type\": \"shape-weird\", " + + "\"fields\": { \"name\": \"normal-trust-me\", " + + "\""+Shape.class.getCanonicalName()+"."+"name\": \"weird-shape\", " + + "\"size\": 4 " + + "} }"). + doReadWriteAssertingJsonMatch(); + } + + @Test + public void testStaticNotRead() { + ShapeWithWeirdFields.aStatic = 3; + try { + YormlTestFixture.newInstance(). + addType("shape-weird", ShapeWithWeirdFields.class). + read("{ \"type\": \"shape-weird\", " + + "\"fields\": { \"aStatic\": 4 " + + "} }", null); + Assert.assertEquals(3, ShapeWithWeirdFields.aStatic); + Asserts.shouldHaveFailedPreviously(); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "aStatic"); + } + Assert.assertEquals(3, ShapeWithWeirdFields.aStatic); + } + + } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java index 63c15cefe7..2f10fd9ec3 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java @@ -1,6 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.brooklyn.util.yorml.tests; import org.apache.brooklyn.util.collections.Jsonya; +import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yorml.Yorml; import org.testng.Assert; @@ -12,6 +31,7 @@ public class YormlTestFixture { Yorml y = Yorml.newInstance(tr); Object writeObject; + String writeObjectExpectedType; Object lastWriteResult; String readObject; String readObjectExpectedType; @@ -19,22 +39,28 @@ public class YormlTestFixture { Object lastResult; public YormlTestFixture writing(Object objectToWrite) { + return writing(objectToWrite, null); + } + public YormlTestFixture writing(Object objectToWrite, String expectedType) { writeObject = objectToWrite; + writeObjectExpectedType = expectedType; return this; } + public YormlTestFixture reading(String stringToRead) { + return reading(stringToRead, null); + } public YormlTestFixture reading(String stringToRead, String expectedType) { readObject = stringToRead; readObjectExpectedType = expectedType; return this; } - public YormlTestFixture reading(String stringToRead) { - readObject = stringToRead; - return this; - } public YormlTestFixture write(Object objectToWrite) { - writing(objectToWrite); - lastWriteResult = y.write(objectToWrite); + return write(objectToWrite, null); + } + public YormlTestFixture write(Object objectToWrite, String expectedType) { + writing(objectToWrite, expectedType); + lastWriteResult = y.write(objectToWrite, expectedType); lastResult = lastWriteResult; return this; } @@ -51,12 +77,18 @@ public YormlTestFixture assertResult(Object expectation) { } public YormlTestFixture doReadWriteAssertingJsonMatch() { read(readObject, readObjectExpectedType); - write(writeObject); - Assert.assertEquals(Jsonya.newInstance().add(lastWriteResult).toString(), readObject, "Write output matches read input."); - Assert.assertEquals(lastReadResult, writeObject, "Read output matches write input."); + write(writeObject, writeObjectExpectedType); + assertEqualsIgnoringQuotes(Jsonya.newInstance().add(lastWriteResult).toString(), readObject, "Write output should read input"); + assertEqualsIgnoringQuotes(lastReadResult, writeObject, "Read output should match write input"); return this; } + static void assertEqualsIgnoringQuotes(Object s1, Object s2, String message) { + if (s1 instanceof String) s1 = Strings.replaceAllNonRegex((String)s1, "\"", ""); + if (s2 instanceof String) s2 = Strings.replaceAllNonRegex((String)s2, "\"", ""); + Assert.assertEquals(s1, s2, message); + } + public YormlTestFixture addType(String name, Class type) { tr.put(name, type); return this; From 77aafabc159ccf9d5507855169310b90bb463f07 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 27 Jun 2016 01:14:31 +0100 Subject: [PATCH 06/77] notes for explicit-field, failing test, and preparatory refactor --- .../org/apache/brooklyn/util/yorml/Yorml.java | 17 +-- .../util/yorml/YormlTypeRegistry.java | 2 +- .../yorml/serializers/InstantiateType.java | 3 +- .../org/apache/brooklyn/util/yorml/sketch.md | 17 ++- .../util/yorml/tests/ExplicitFieldTests.java | 116 ++++++++++++++++++ .../yorml/tests/MockYormlTypeRegistry.java | 96 ++++++++++++--- .../util/yorml/tests/YormlTestFixture.java | 11 +- 7 files changed, 230 insertions(+), 32 deletions(-) create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java index ffad857cc6..8892ca284c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java @@ -27,9 +27,13 @@ public class Yorml { - YormlConfig config; + final YormlConfig config; - private Yorml() {} + private Yorml(YormlConfig config) { this.config = config; } + + public static Yorml newInstance(YormlConfig config) { + return new Yorml(config); + } public static Yorml newInstance(YormlTypeRegistry typeRegistry) { return newInstance(typeRegistry, MutableList.of( @@ -38,12 +42,11 @@ public static Yorml newInstance(YormlTypeRegistry typeRegistry) { } public static Yorml newInstance(YormlTypeRegistry typeRegistry, List serializers) { - Yorml result = new Yorml(); - result.config = new YormlConfig(); - result.config.typeRegistry = typeRegistry; - result.config.serializersPost.addAll(serializers); + YormlConfig config = new YormlConfig(); + config.typeRegistry = typeRegistry; + config.serializersPost.addAll(serializers); - return result; + return new Yorml(config); } public Object read(String yaml) { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java index 736a9ae3d5..0b4a9d9502 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java @@ -20,7 +20,7 @@ public interface YormlTypeRegistry { - Object newInstance(String type); + Object newInstance(String type, Yorml yorml); Class getJavaType(String typeName); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java index 16180684b8..5f243c134e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java @@ -29,6 +29,7 @@ import org.apache.brooklyn.util.javalang.ReflectionPredicates; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yorml.Yorml; import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; public class InstantiateType extends YormlSerializerComposition { @@ -73,7 +74,7 @@ public YormlContinuation read() { if (type==null && result==null) return YormlContinuation.CONTINUE_UNCHANGED; if (result==null) { - result = config.getTypeRegistry().newInstance((String)type); + result = config.getTypeRegistry().newInstance((String)type, Yorml.newInstance(config)); } if (result==null) { ReadingTypeOnBlackboard.get(blackboard).addNote("Unknown type '"+type+"'"); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md index 1496426c14..aa13e1be96 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md @@ -181,9 +181,10 @@ parameter, field-name, and several optional ones: - type: explicit-field field-name: color key-name: color # this is used in yaml - aliases: [ colour ] # accepted in yaml as a synonym for key-name; `alias` also accepted + aliases: [ colour ] # things to accept in yaml as synonyms for key-name; `alias` also accepted + disable-default-aliases: boolean # if true, means only exact matches on key-name and aliases are accepted, otherwise a set of mangles are applied field-type: string # inferred from java field, but you can constrain further to yaml types - constraint: required # currently just supports 'required', or blank for none, but reserved for future use + constraint: required # currently just supports 'required' (and 'null' not allowed) or blank for none (default), but reserved for future use description: The color of the shape # text (markdown) serialization: # optional additional serialization instructions - if-string: # (defined below) @@ -254,7 +255,7 @@ Note: this has some surprising side-effects in occasional edge cases; consider: # BAD: this would define a field `explicitField`, then fail because that field-name is in use serialization: explicit-field: { field-name: color } - # GOOD options (in addition to those in previous section, but assuming you wanted to say the type explciitly) + # GOOD options (in addition to those in previous section, but assuming you wanted to say the type explicitly) serialization: - explicit-field: { field-name: color } # or @@ -452,7 +453,14 @@ and serializers can (and typically do) restart the serialization cycle if they c So the general process is: * first r/w the type, and on write note the fields to write -* adjust the data structure until no further adjustments are made +* adjust the data until a pass of serializers completes with all CONTINUE or any FINISHED (and nothing requesting a rerun); + on read from yaml to java, this is: + * unpacking any complex structure in the YAML data object + * reading the fields, removing from a list in the blackboard and writing to the object + and on write from java : + * creating any complex structure for the YAML data object + * writing the fields (which might be a map on blackboard, maybe referring to the YAML map, maybe referring to an object within it) + ?? but if something is primitive-or-map depending on fields? add CONTINUE_UNCHANGED_BUT_RERUN_IF_CHANGED? * check that everything that needed to be done was done ### TODO @@ -462,6 +470,7 @@ So the general process is: * defining serializers? * complex syntax, type as key, etc * maps and lists +* best-serialization vs first-serialization ## Real World Use Cases diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java new file mode 100644 index 0000000000..0aeb8104dc --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.tests; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.yorml.YormlSerializer; +import org.apache.brooklyn.util.yorml.tests.YormlBasicTests.Shape; +import org.testng.annotations.Test; + +/** Tests that explicit fields can be set at the outer level in yaml. */ +public class ExplicitFieldTests { + + /* + +serializers can come from: + * default pre (any?) + * declared type + * calling context + * expected type + * default post - fields in fields and instantiate type + + +- id: java-shape-defaulting-square + type: java:Shape + serializers: + - type: explicit-field + field-name: name + aliases: [ shape-name ] + default: square # alternative to above + definition: # body, yaml, item, content + ... + +- id: shape + type: java:Shape + serializers: + - type: explicit-field + field-name: name + aliases: [ shape-name ] + - type: explicit-field + field-name: color + aliases: [ shape-color ] +- id: yaml-shape-defaulting-square + type: shape + serializers: + - type: explicit-field + field-name: name + default: square + # ensure if shape-name set it overrides 'square' +- id: red-square + type: square + fields: + shape-color: red + # read red-square, but writes { type: shape, name: square, color: red } + # except in context expecting shape it writes { name: square, color: red } + # and in context expecting square it writes { color: red } + + +# sub-type serializers go first, also before expected-type serializers +# but... +# 1) if we read something with shape-name do we do a setField("name", "square") ? +# NO: defaults request RERUN_IF_CHANGED if there are fields to read present, only apply when no fields to read present +# 2) if java fields contains 'name: square' do we write it? +# NO: defaults write to a defaults map if not present +# and field writers don't write if a defaults map contains the default value +# so on write explicit-fields will +# * populate a key in the DEFAULTS map if not present +# and on init +# * keep a list of mangles/aliases +# and on read +# * look up all mangles/aliases once the type is known +# * error if there are multiple mangles/aliases with different values +# (inefficient for that to run multiple times but we'll live with that) + */ + + public static YormlSerializer explicitFieldSerializer(String yaml) { +// return YormlTestFixture.newInstance().read(yaml, "java:"+ExplicitField.class); + return null; + } + + protected static YormlTestFixture simpleExplicitFieldFixture() { + return YormlTestFixture.newInstance(). + addType("shape", Shape.class, MutableList.of(explicitFieldSerializer("field-name: name, aliases: [ shape-name ]"))); + } + static String SIMPLE_IN = "{ type: shape, name: diamond, fields: { color: black } }"; + static Shape SIMPLE_OUT = new Shape().name("diamond").color("black"); + + @Test + public void testReadExplicitField() { + simpleExplicitFieldFixture(). + read( SIMPLE_IN, null ). + assertResult( SIMPLE_OUT); + } + @Test + public void testWriteExplicitField() { + simpleExplicitFieldFixture(). + write( SIMPLE_OUT, null ). + assertResult( SIMPLE_IN ); + } + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java index 14c0df93f6..6b49e3e115 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java @@ -18,6 +18,7 @@ */ package org.apache.brooklyn.util.yorml.tests; +import java.util.List; import java.util.Map; import org.apache.brooklyn.util.collections.MutableMap; @@ -25,44 +26,105 @@ import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Boxing; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yaml.Yamls; +import org.apache.brooklyn.util.yorml.Yorml; +import org.apache.brooklyn.util.yorml.YormlSerializer; import org.apache.brooklyn.util.yorml.YormlTypeRegistry; +import com.google.common.collect.Iterables; + public class MockYormlTypeRegistry implements YormlTypeRegistry { - Map> types = MutableMap.of(); + static class MockRegisteredType { + final String id; + final String parentType; + + final Class javaType; + final List serializers; + final Object yamlDefinition; + + public MockRegisteredType(String id, String parentType, Class javaType, List serializers, Object yamlDefinition) { + super(); + this.id = id; + this.parentType = parentType; + this.javaType = javaType; + this.serializers = serializers; + this.yamlDefinition = yamlDefinition; + } + } + + Map types = MutableMap.of(); @Override - public Object newInstance(String typeName) { - Class type = getJavaType(typeName); + public Object newInstance(String typeName, Yorml yorml) { + MockRegisteredType type = types.get(typeName); if (type==null) { return null; } try { - return type.newInstance(); + if (type.yamlDefinition!=null) { + String parentTypeName = type.parentType; + if (type.parentType==null && type.javaType!=null) parentTypeName = getDefaultTypeNameOfClass(type.javaType); + return yorml.readFromYamlObject(type.yamlDefinition, parentTypeName); + } + Class javaType = getJavaType(type, null); + if (javaType==null) { + throw new IllegalStateException("Incomplete hierarchy for `"+typeName+"`"); + } + return javaType.newInstance(); } catch (Exception e) { throw Exceptions.propagate(e); } } @Override - public java.lang.Class getJavaType(String typeName) { - Class type = types.get(typeName); - if (type==null) type = Boxing.getPrimitiveType(typeName).orNull(); - if (type==null && "string".equals(typeName)) type = String.class; - if (type==null && typeName.startsWith("java:")) { + public Class getJavaType(String typeName) { + return getJavaType(types.get(typeName), typeName); + } + + protected Class getJavaType(MockRegisteredType registeredType, String typeName) { + Class result = null; + + if (result==null && registeredType!=null) result = registeredType.javaType; + if (result==null && registeredType!=null) result = getJavaType(registeredType.parentType); + + if (result==null) result = Boxing.getPrimitiveType(typeName).orNull(); + if (result==null) result = Boxing.getPrimitiveType(typeName).orNull(); + if (result==null && "string".equals(typeName)) result = String.class; + if (result==null && typeName.startsWith("java:")) { typeName = Strings.removeFromStart(typeName, "java:"); try { // TODO use injected loader? - type = Class.forName(typeName); + result = Class.forName(typeName); } catch (ClassNotFoundException e) { // ignore, this isn't a java type } } - return type; + return result; + } + + /** simplest type def -- an alias for a java class */ + public void put(String typeName, Class javaType) { + put(typeName, javaType, null); + } + public void put(String typeName, Class javaType, List serializers) { + types.put(typeName, new MockRegisteredType(typeName, "java:"+javaType.getName(), javaType, null, null)); } - public void put(String typeName, Class type) { - types.put(typeName, type); + /** takes a simplified yaml definition supporting a map with a key `type` and optionally other keys */ + public void put(String typeName, String yamlDefinition) { + put(typeName, yamlDefinition, null); + } + public void put(String typeName, String yamlDefinition, List serializers) { + Object yamlObject = Iterables.getOnlyElement( Yamls.parseAll(yamlDefinition) ); + if (!(yamlObject instanceof Map)) throw new IllegalArgumentException("Mock only supports map definitions"); + Object type = ((Map)yamlObject).get("type"); + if (!(type instanceof String)) throw new IllegalArgumentException("Mock requires key `type` with string value"); + ((Map)yamlObject).remove("type"); + if (((Map)yamlObject).isEmpty()) yamlObject = null; + Class javaType = getJavaType((String)type); + if (javaType==null) throw new IllegalArgumentException("Mock cannot resolve parent type `"+type+"` in definition of `"+typeName+"`"); + types.put(typeName, new MockRegisteredType(typeName, (String)type, javaType, serializers, yamlObject)); } @Override @@ -72,9 +134,13 @@ public String getTypeName(Object obj) { @Override public String getTypeNameOfClass(Class type) { - for (Map.Entry> t: types.entrySet()) { - if (t.getValue().equals(type)) return t.getKey(); + for (Map.Entry t: types.entrySet()) { + if (type.equals(t.getValue().javaType) && t.getValue().yamlDefinition==null) return t.getKey(); } + return getDefaultTypeNameOfClass(type); + } + + protected String getDefaultTypeNameOfClass(Class type) { Maybe primitive = Boxing.getPrimitiveName(type); if (primitive.isPresent()) return primitive.get(); if (String.class.equals(type)) return "string"; diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java index 2f10fd9ec3..c8eb535a3e 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java @@ -18,9 +18,12 @@ */ package org.apache.brooklyn.util.yorml.tests; +import java.util.List; + import org.apache.brooklyn.util.collections.Jsonya; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yorml.Yorml; +import org.apache.brooklyn.util.yorml.YormlSerializer; import org.testng.Assert; public class YormlTestFixture { @@ -89,8 +92,8 @@ static void assertEqualsIgnoringQuotes(Object s1, Object s2, String message) { Assert.assertEquals(s1, s2, message); } - public YormlTestFixture addType(String name, Class type) { - tr.put(name, type); - return this; - } + public YormlTestFixture addType(String name, Class type) { tr.put(name, type); return this; } + public YormlTestFixture addType(String name, Class type, List serializers) { tr.put(name, type, serializers); return this; } + public YormlTestFixture addType(String name, String yamlDefinition) { tr.put(name, yamlDefinition); return this; } + public YormlTestFixture addType(String name, String yamlDefinition, List serializers) { tr.put(name, yamlDefinition, serializers); return this; } } \ No newline at end of file From ed25f099567f385d30967fb1189053424c4e942b Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 27 Jun 2016 02:23:12 +0100 Subject: [PATCH 07/77] explicit fields basic working for expected types --- .../brooklyn/util/yorml/YormlConverter.java | 15 ++- .../util/yorml/YormlTypeRegistry.java | 7 ++ .../util/yorml/serializers/ExplicitField.java | 113 ++++++++++++++++++ .../serializers/FieldsInMapUnderFields.java | 20 +++- .../yorml/serializers/InstantiateType.java | 6 +- .../YormlSerializerComposition.java | 12 +- .../util/yorml/tests/ExplicitFieldTests.java | 33 +++-- .../yorml/tests/MockYormlTypeRegistry.java | 41 ++++--- .../util/yorml/tests/YormlTestFixture.java | 11 +- 9 files changed, 215 insertions(+), 43 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java index 7a5743f0e3..acf9c98af8 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java @@ -40,7 +40,7 @@ public YormlConverter(YormlConfig config) { * until result is done */ public Object read(YormlContextForRead context) { - List serializers = MutableList.of().appendAll(config.serializersPost); + List serializers = getSerializers(context); int i=0; Map blackboard = MutableMap.of(); ReadingTypeOnBlackboard.get(blackboard); @@ -56,6 +56,15 @@ public Object read(YormlContextForRead context) { return context.getJavaObject(); } + private List getSerializers(YormlContext context) { + MutableList serializers = MutableList.of(); + if (context.getExpectedType()!=null) { + serializers.appendAll(config.typeRegistry.getAllSerializers(context.getExpectedType())); + } + serializers.appendAll(config.serializersPost); + return serializers; + } + protected void checkCompletion(YormlContext context, Map blackboard) { for (Object bo: blackboard.values()) { if (bo instanceof YormlRequirement) { @@ -68,12 +77,14 @@ protected void checkCompletion(YormlContext context, Map blackbo * returns jsonable object (map, list, primitive) */ public Object write(YormlContextForWrite context) { - List serializers = MutableList.of().appendAll(config.serializersPost); + List serializers = getSerializers(context); int i=0; Map blackboard = MutableMap.of(); while (i newInstanceMaybe(String type, Yorml yorml); + Object newInstance(String type, Yorml yorml); Class getJavaType(String typeName); String getTypeName(Object obj); String getTypeNameOfClass(Class type); + + Iterable getAllSerializers(String expectedType); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java new file mode 100644 index 0000000000..f01c3b4a92 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.serializers; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Map; + +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.yorml.YormlContextForRead; +import org.apache.brooklyn.util.yorml.YormlContextForWrite; +import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; + +public class ExplicitField extends YormlSerializerComposition { + + protected YormlSerializerWorker newWorker() { + return new Worker(); + } + + protected String fieldName; + protected String fieldType; + + protected String keyName; + public String getKeyName() { if (keyName!=null) return keyName; return fieldName; } + + public class Worker extends YormlSerializerWorker { + public YormlContinuation read() { + if (!hasJavaObject()) return YormlContinuation.CONTINUE_UNCHANGED; + + String keyName = getKeyName(); + Maybe value = peekFromYamlKeysOnBlackboard(keyName, Object.class); + if (value.isAbsent()) return YormlContinuation.CONTINUE_UNCHANGED; + + Maybe ffm = Reflections.findFieldMaybe(getJavaObject().getClass(), fieldName); + if (ffm.isAbsentOrNull()) { + throw new IllegalStateException("Expected field `"+fieldName+"` in "+getJavaObject()); + } + + Field ff = ffm.get(); + if (Modifier.isStatic(ff.getModifiers())) { + throw new IllegalStateException("Cannot set static `"+fieldName+"` in "+getJavaObject()); + } + + String fieldType = ExplicitField.this.fieldType; + if (fieldType==null) fieldType = config.getTypeRegistry().getTypeNameOfClass(ff.getType()); + + YormlContextForRead subcontext = new YormlContextForRead(context.getJsonPath()+"/"+keyName, fieldType); + subcontext.setYamlObject(value.get()); + Object v2 = converter.read(subcontext); + + ff.setAccessible(true); + try { + ff.set(getJavaObject(), v2); + } catch (Exception e) { + // TODO do we need to say where? + Exceptions.propagate(e); + } + removeFromYamlKeysOnBlackboard(keyName); + + return YormlContinuation.CONTINUE_CHANGED; + } + + public YormlContinuation write() { + if (!isYamlMap()) return YormlContinuation.CONTINUE_UNCHANGED; + if (getFromYamlMap("fields", Map.class)!=null) return YormlContinuation.CONTINUE_UNCHANGED; + JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); + if (fib==null || fib.fieldsToWriteFromJava.isEmpty()) return YormlContinuation.CONTINUE_UNCHANGED; + + Maybe v = Reflections.getFieldValueMaybe(getJavaObject(), fieldName); + if (v.isAbsent()) { + return YormlContinuation.CONTINUE_UNCHANGED; + } + + fib.fieldsToWriteFromJava.remove(fieldName); + if (v.get()==null) { + // silently drop + return YormlContinuation.CONTINUE_CHANGED; + } else { + Field ff = Reflections.findFieldMaybe(getJavaObject().getClass(), fieldName).get(); + + String fieldType = ExplicitField.this.fieldType; + if (fieldType==null) fieldType = config.getTypeRegistry().getTypeNameOfClass(ff.getType()); + + YormlContextForWrite subcontext = new YormlContextForWrite(context.getJsonPath()+"/"+getKeyName(), fieldType); + subcontext.setJavaObject(v.get()); + + Object v2 = converter.write(subcontext); + setInYamlMap(getKeyName(), v2); + // no reason to restart? + return YormlContinuation.CONTINUE_CHANGED; + } + } + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java index a24f13198d..12c02187c0 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java @@ -34,7 +34,9 @@ public class FieldsInMapUnderFields extends YormlSerializerComposition { - public FieldsInMapUnderFields() { super(Worker.class); } + protected YormlSerializerWorker newWorker() { + return new Worker(); + } public static class Worker extends YormlSerializerWorker { public YormlContinuation read() { @@ -44,6 +46,7 @@ public YormlContinuation read() { Map fields = peekFromYamlKeysOnBlackboard("fields", Map.class).orNull(); if (fields==null) return YormlContinuation.CONTINUE_UNCHANGED; + boolean changed = false; for (Object f: MutableList.copyOf( ((Map)fields).keySet() )) { Object v = ((Map)fields).get(f); try { @@ -63,14 +66,21 @@ public YormlContinuation read() { ff.setAccessible(true); ff.set(getJavaObject(), v2); ((Map)fields).remove(Strings.toString(f)); - if (((Map)fields).isEmpty()) { - removeFromYamlKeysOnBlackboard("fields"); - } + changed = true; } } } catch (Exception e) { throw Exceptions.propagate(e); } } - return YormlContinuation.CONTINUE_CHANGED; + + if (changed) { + if (((Map)fields).isEmpty()) { + removeFromYamlKeysOnBlackboard("fields"); + } + // no reason to restart? + return YormlContinuation.CONTINUE_CHANGED; + } + + return YormlContinuation.CONTINUE_UNCHANGED; } public YormlContinuation write() { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java index 5f243c134e..219699ac2a 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java @@ -34,7 +34,9 @@ public class InstantiateType extends YormlSerializerComposition { - public InstantiateType() { super(Worker.class); } + protected YormlSerializerWorker newWorker() { + return new Worker(); + } public static class Worker extends YormlSerializerWorker { public YormlContinuation read() { @@ -74,7 +76,7 @@ public YormlContinuation read() { if (type==null && result==null) return YormlContinuation.CONTINUE_UNCHANGED; if (result==null) { - result = config.getTypeRegistry().newInstance((String)type, Yorml.newInstance(config)); + result = config.getTypeRegistry().newInstanceMaybe((String)type, Yorml.newInstance(config)).orNull(); } if (result==null) { ReadingTypeOnBlackboard.get(blackboard).addNote("Unknown type '"+type+"'"); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java index aae79856fe..3acd8a49bc 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java @@ -31,13 +31,9 @@ import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; import org.apache.brooklyn.util.yorml.YormlSerializer; -public class YormlSerializerComposition implements YormlSerializer { +public abstract class YormlSerializerComposition implements YormlSerializer { - protected final Class workerType; - - public YormlSerializerComposition(Class workerType) { - this.workerType = workerType; - } + protected abstract YormlSerializerWorker newWorker(); public abstract static class YormlSerializerWorker { @@ -119,7 +115,7 @@ protected void removeFromYamlKeysOnBlackboard(String key) { public YormlContinuation read(YormlContextForRead context, YormlConverter converter, Map blackboard) { YormlSerializerWorker worker; try { - worker = workerType.newInstance(); + worker = newWorker(); } catch (Exception e) { throw Exceptions.propagate(e); } worker.initRead(context, converter, blackboard); return worker.read(); @@ -129,7 +125,7 @@ public YormlContinuation read(YormlContextForRead context, YormlConverter conver public YormlContinuation write(YormlContextForWrite context, YormlConverter converter, Map blackboard) { YormlSerializerWorker worker; try { - worker = workerType.newInstance(); + worker = newWorker(); } catch (Exception e) { throw Exceptions.propagate(e); } worker.initWrite(context, converter, blackboard); return worker.write(); diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java index 0aeb8104dc..302f32e0fb 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java @@ -20,6 +20,7 @@ import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.yorml.YormlSerializer; +import org.apache.brooklyn.util.yorml.serializers.ExplicitField; import org.apache.brooklyn.util.yorml.tests.YormlBasicTests.Shape; import org.testng.annotations.Test; @@ -89,28 +90,44 @@ public class ExplicitFieldTests { */ public static YormlSerializer explicitFieldSerializer(String yaml) { -// return YormlTestFixture.newInstance().read(yaml, "java:"+ExplicitField.class); - return null; - } + return (YormlSerializer) YormlTestFixture.newInstance().read("{ fields: "+yaml+" }", "java:"+ExplicitField.class.getName()).lastReadResult; + } protected static YormlTestFixture simpleExplicitFieldFixture() { return YormlTestFixture.newInstance(). - addType("shape", Shape.class, MutableList.of(explicitFieldSerializer("field-name: name, aliases: [ shape-name ]"))); + addType("shape", Shape.class, MutableList.of(explicitFieldSerializer("{ fieldName: name }"))); } - static String SIMPLE_IN = "{ type: shape, name: diamond, fields: { color: black } }"; + + static String SIMPLE_IN_WITHOUT_TYPE = "{ name: diamond, fields: { color: black } }"; static Shape SIMPLE_OUT = new Shape().name("diamond").color("black"); + @Test public void testReadExplicitField() { simpleExplicitFieldFixture(). - read( SIMPLE_IN, null ). - assertResult( SIMPLE_OUT); + read( SIMPLE_IN_WITHOUT_TYPE, "shape" ). + assertResult( SIMPLE_OUT ); } @Test public void testWriteExplicitField() { + simpleExplicitFieldFixture(). + write( SIMPLE_OUT, "shape" ). + assertResult( SIMPLE_IN_WITHOUT_TYPE ); + } + + static String SIMPLE_IN_WITH_TYPE = "{ type: shape, name: diamond, fields: { color: black } }"; + + @Test + public void testReadExplicitFieldNoExpectedType() { + simpleExplicitFieldFixture(). + read( SIMPLE_IN_WITH_TYPE, null ). + assertResult( SIMPLE_OUT); + } + @Test + public void testWriteExplicitFieldNoExpectedType() { simpleExplicitFieldFixture(). write( SIMPLE_OUT, null ). - assertResult( SIMPLE_IN ); + assertResult( SIMPLE_IN_WITH_TYPE ); } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java index 6b49e3e115..d2ec315eff 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java @@ -21,10 +21,11 @@ import java.util.List; import java.util.Map; +import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Boxing; +import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yaml.Yamls; import org.apache.brooklyn.util.yorml.Yorml; @@ -57,24 +58,22 @@ public MockRegisteredType(String id, String parentType, Class javaType, List< @Override public Object newInstance(String typeName, Yorml yorml) { + return newInstanceMaybe(typeName, yorml).get(); + } + @Override + public Maybe newInstanceMaybe(String typeName, Yorml yorml) { MockRegisteredType type = types.get(typeName); - if (type==null) { - return null; + if (type!=null && type.yamlDefinition!=null) { + String parentTypeName = type.parentType; + if (type.parentType==null && type.javaType!=null) parentTypeName = getDefaultTypeNameOfClass(type.javaType); + return Maybe.of(yorml.readFromYamlObject(type.yamlDefinition, parentTypeName)); } - try { - if (type.yamlDefinition!=null) { - String parentTypeName = type.parentType; - if (type.parentType==null && type.javaType!=null) parentTypeName = getDefaultTypeNameOfClass(type.javaType); - return yorml.readFromYamlObject(type.yamlDefinition, parentTypeName); - } - Class javaType = getJavaType(type, null); - if (javaType==null) { - throw new IllegalStateException("Incomplete hierarchy for `"+typeName+"`"); - } - return javaType.newInstance(); - } catch (Exception e) { - throw Exceptions.propagate(e); + Class javaType = getJavaType(type, typeName); + if (javaType==null) { + if (type==null) return Maybe.absent("Unknown type `"+typeName+"`"); + throw new IllegalStateException("Incomplete hierarchy for `"+typeName+"`"); } + return Reflections.invokeConstructorFromArgsIncludingPrivate(javaType); } @Override @@ -108,7 +107,7 @@ public void put(String typeName, Class javaType) { put(typeName, javaType, null); } public void put(String typeName, Class javaType, List serializers) { - types.put(typeName, new MockRegisteredType(typeName, "java:"+javaType.getName(), javaType, null, null)); + types.put(typeName, new MockRegisteredType(typeName, "java:"+javaType.getName(), javaType, serializers, null)); } /** takes a simplified yaml definition supporting a map with a key `type` and optionally other keys */ @@ -124,6 +123,7 @@ public void put(String typeName, String yamlDefinition, List se if (((Map)yamlObject).isEmpty()) yamlObject = null; Class javaType = getJavaType((String)type); if (javaType==null) throw new IllegalArgumentException("Mock cannot resolve parent type `"+type+"` in definition of `"+typeName+"`"); + types.put(typeName, new MockRegisteredType(typeName, (String)type, javaType, serializers, yamlObject)); } @@ -148,4 +148,11 @@ protected String getDefaultTypeNameOfClass(Class type) { return "java:"+type.getName(); } + + @Override + public Iterable getAllSerializers(String typeName) { + MockRegisteredType rt = types.get(typeName); + if (rt==null || rt.serializers==null) return MutableList.of(); + return rt.serializers; + } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java index c8eb535a3e..f9433ec9db 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.util.yorml.tests; import java.util.List; +import java.util.Map; import org.apache.brooklyn.util.collections.Jsonya; import org.apache.brooklyn.util.text.Strings; @@ -75,7 +76,15 @@ public YormlTestFixture read(String objectToRead, String expectedType) { } public YormlTestFixture assertResult(Object expectation) { - Assert.assertEquals(lastResult, expectation); + if (expectation instanceof String) { + if (lastResult instanceof Map || lastResult instanceof List) { + assertEqualsIgnoringQuotes(Jsonya.newInstance().add(lastResult).toString(), expectation, "Result as JSON string does not match expectation"); + } else if (!(lastResult instanceof String)) { + assertEqualsIgnoringQuotes(Strings.toString(lastResult), expectation, "Result toString does not match expectation"); + } + } else { + Assert.assertEquals(lastResult, expectation); + } return this; } public YormlTestFixture doReadWriteAssertingJsonMatch() { From 39a579258a24928fca19ff2566ed3be0f6507a06 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 27 Jun 2016 03:04:38 +0100 Subject: [PATCH 08/77] explicit fields working also for declared types using serializers on the blackboard and more refactor using now an internal package and cleaning up sketch --- .../org/apache/brooklyn/util/yorml/Yorml.java | 2 + ...lInternals.java => YormlContinuation.java} | 6 +- .../brooklyn/util/yorml/YormlSerializer.java | 2 +- .../internal/SerializersOnBlackboard.java | 63 ++++ .../yorml/{ => internal}/YormlConfig.java | 10 +- .../yorml/{ => internal}/YormlConverter.java | 84 +++--- .../util/yorml/serializers/ExplicitField.java | 4 +- .../serializers/FieldsInMapUnderFields.java | 2 +- .../yorml/serializers/InstantiateType.java | 26 +- .../serializers/YamlKeysOnBlackboard.java | 2 +- .../YormlSerializerComposition.java | 6 +- .../org/apache/brooklyn/util/yorml/sketch.md | 269 ++++++++++++------ .../util/yorml/tests/ExplicitFieldTests.java | 4 + 13 files changed, 324 insertions(+), 156 deletions(-) rename utils/common/src/main/java/org/apache/brooklyn/util/yorml/{YormlInternals.java => YormlContinuation.java} (86%) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java rename utils/common/src/main/java/org/apache/brooklyn/util/yorml/{ => internal}/YormlConfig.java (78%) rename utils/common/src/main/java/org/apache/brooklyn/util/yorml/{ => internal}/YormlConverter.java (55%) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java index 8892ca284c..48bb5e63b9 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java @@ -21,6 +21,8 @@ import java.util.List; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.yorml.internal.YormlConfig; +import org.apache.brooklyn.util.yorml.internal.YormlConverter; import org.apache.brooklyn.util.yorml.serializers.FieldsInMapUnderFields; import org.apache.brooklyn.util.yorml.serializers.InstantiateType; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlInternals.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContinuation.java similarity index 86% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlInternals.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContinuation.java index 0545e3705b..b1bbfc3fd8 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlInternals.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContinuation.java @@ -18,8 +18,4 @@ */ package org.apache.brooklyn.util.yorml; -public class YormlInternals { - - public enum YormlContinuation { RESTART, CONTINUE_CHANGED, CONTINUE_UNCHANGED, FINISHED } - -} +public enum YormlContinuation { RESTART, CONTINUE_CHANGED, CONTINUE_UNCHANGED, FINISHED } \ No newline at end of file diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlSerializer.java index 117af38528..22bf2f9aaf 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlSerializer.java @@ -20,7 +20,7 @@ import java.util.Map; -import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; +import org.apache.brooklyn.util.yorml.internal.YormlConverter; import org.apache.brooklyn.util.yorml.serializers.YormlSerializerComposition; /** Describes a serializer which can be used by {@link YormlConverter}. diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java new file mode 100644 index 0000000000..6848e29feb --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.internal; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.yorml.YormlSerializer; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; + +/** Stores serializers that should be used */ +public class SerializersOnBlackboard { + + private static String KEY = SerializersOnBlackboard.class.getName(); + + public static boolean isPresent(Map blackboard) { + return blackboard.containsKey(KEY); + } + public static SerializersOnBlackboard get(Map blackboard) { + return Preconditions.checkNotNull(peek(blackboard), "Not yet available"); + } + public static SerializersOnBlackboard peek(Map blackboard) { + return (SerializersOnBlackboard) blackboard.get(KEY); + } + public static SerializersOnBlackboard create(Map blackboard) { + if (isPresent(blackboard)) { throw new IllegalStateException("Already present"); } + blackboard.put(KEY, new SerializersOnBlackboard()); + return peek(blackboard); + } + + List preSerializers = MutableList.of(); + List instantiatedTypeSerializers = MutableList.of(); + List expectedTypeSerializers = MutableList.of(); + List postSerializers = MutableList.of(); + + public void addInstantiatedTypeSerializers(Iterable instantiatedTypeSerializers) { + Iterables.addAll(this.instantiatedTypeSerializers, instantiatedTypeSerializers); + } + + public Iterable getSerializers() { + return Iterables.concat(preSerializers, instantiatedTypeSerializers, expectedTypeSerializers, postSerializers); + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConfig.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConfig.java similarity index 78% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConfig.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConfig.java index da30560ce0..0a06e67272 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConfig.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConfig.java @@ -16,20 +16,22 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml; +package org.apache.brooklyn.util.yorml.internal; import java.util.List; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.javalang.coerce.TypeCoercer; import org.apache.brooklyn.util.javalang.coerce.TypeCoercerExtensible; +import org.apache.brooklyn.util.yorml.YormlSerializer; +import org.apache.brooklyn.util.yorml.YormlTypeRegistry; public class YormlConfig { - YormlTypeRegistry typeRegistry; - TypeCoercer coercer = TypeCoercerExtensible.newDefault(); + public YormlTypeRegistry typeRegistry; + public TypeCoercer coercer = TypeCoercerExtensible.newDefault(); - List serializersPost = MutableList.of(); + public List serializersPost = MutableList.of(); public YormlTypeRegistry getTypeRegistry() { return typeRegistry; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java similarity index 55% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java index acf9c98af8..713be8cd71 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java @@ -16,16 +16,21 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml; +package org.apache.brooklyn.util.yorml.internal; -import java.util.List; import java.util.Map; -import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; +import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.YormlContextForRead; +import org.apache.brooklyn.util.yorml.YormlContextForWrite; +import org.apache.brooklyn.util.yorml.YormlContinuation; +import org.apache.brooklyn.util.yorml.YormlRequirement; +import org.apache.brooklyn.util.yorml.YormlSerializer; import org.apache.brooklyn.util.yorml.serializers.ReadingTypeOnBlackboard; +import com.google.common.collect.Iterables; + public class YormlConverter { private final YormlConfig config; @@ -40,29 +45,49 @@ public YormlConverter(YormlConfig config) { * until result is done */ public Object read(YormlContextForRead context) { - List serializers = getSerializers(context); - int i=0; + loopOverSerializers(context); + return context.getJavaObject(); + } + + /** + * returns jsonable object (map, list, primitive) + */ + public Object write(final YormlContextForWrite context) { + loopOverSerializers(context); + return context.getYamlObject(); + } + + protected void loopOverSerializers(YormlContext context) { Map blackboard = MutableMap.of(); - ReadingTypeOnBlackboard.get(blackboard); - while (i getSerializers(YormlContext context) { - MutableList serializers = MutableList.of(); - if (context.getExpectedType()!=null) { - serializers.appendAll(config.typeRegistry.getAllSerializers(context.getExpectedType())); - } - serializers.appendAll(config.serializersPost); - return serializers; } protected void checkCompletion(YormlContext context, Map blackboard) { @@ -73,25 +98,6 @@ protected void checkCompletion(YormlContext context, Map blackbo } } - /** - * returns jsonable object (map, list, primitive) - */ - public Object write(YormlContextForWrite context) { - List serializers = getSerializers(context); - int i=0; - Map blackboard = MutableMap.of(); - while (i expectedJavaType = getExpectedTypeJava(); String type = null; if ((getYamlObject() instanceof String) || Boxing.isPrimitiveOrBoxedObject(getYamlObject())) { // default handling of primitives: // if type is expected, try to coerce + Class expectedJavaType = getExpectedTypeJava(); if (expectedJavaType!=null) { Maybe result = config.getCoercer().tryCoerce(getYamlObject(), expectedJavaType); if (result.isPresent()) { @@ -66,23 +67,18 @@ public YormlContinuation read() { YamlKeysOnBlackboard.create(blackboard).yamlKeysToReadToJava = MutableMap.copyOf(getYamlMap()); if (type==null) type = peekFromYamlKeysOnBlackboard("type", String.class).orNull(); - Object result = null; - if (type==null && expectedJavaType!=null) { - Maybe resultM = Reflections.invokeConstructorFromArgsIncludingPrivate(expectedJavaType); - if (resultM.isPresent()) result = resultM.get(); - else ReadingTypeOnBlackboard.get(blackboard).addNote("Expected type is not no-arg instantiable: '"+expectedJavaType+"' ("+ - ((Maybe.Absent)resultM).getException()+")"); - } - if (type==null && result==null) return YormlContinuation.CONTINUE_UNCHANGED; + if (type==null) type = context.getExpectedType(); + if (type==null) return YormlContinuation.CONTINUE_UNCHANGED; - if (result==null) { - result = config.getTypeRegistry().newInstanceMaybe((String)type, Yorml.newInstance(config)).orNull(); - } + Object result = config.getTypeRegistry().newInstanceMaybe((String)type, Yorml.newInstance(config)).orNull(); if (result==null) { ReadingTypeOnBlackboard.get(blackboard).addNote("Unknown type '"+type+"'"); return YormlContinuation.CONTINUE_UNCHANGED; } removeFromYamlKeysOnBlackboard("type"); + if (!type.equals(context.getExpectedType())) { + SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.getTypeRegistry().getAllSerializers(type)); + } context.setJavaObject(result); return YormlContinuation.RESTART; @@ -113,7 +109,9 @@ public YormlContinuation write() { if (getJavaObject().getClass().equals(getExpectedTypeJava())) { // skip explicitly writing the type } else { - map.put("type", config.getTypeRegistry().getTypeName(getJavaObject()) ); + String typeName = config.getTypeRegistry().getTypeName(getJavaObject()); + map.put("type", typeName); + SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.getTypeRegistry().getAllSerializers(typeName)); } List fields = Reflections.findFields(getJavaObject().getClass(), diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java index fc1a5bfce2..8560dbef76 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java @@ -50,7 +50,7 @@ public static YamlKeysOnBlackboard create(Map blackboard) { @Override public void checkCompletion(YormlContext context) { if (!yamlKeysToReadToJava.isEmpty()) { - // TODO limit to depth 2 ? + // TODO limit toString to depth 2 ? throw new YormlException("Incomplete read of YAML keys: "+yamlKeysToReadToJava, context); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java index 3acd8a49bc..97966f3ca7 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java @@ -23,12 +23,12 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yorml.YormlConfig; import org.apache.brooklyn.util.yorml.YormlContext; import org.apache.brooklyn.util.yorml.YormlContextForRead; import org.apache.brooklyn.util.yorml.YormlContextForWrite; -import org.apache.brooklyn.util.yorml.YormlConverter; -import org.apache.brooklyn.util.yorml.YormlInternals.YormlContinuation; +import org.apache.brooklyn.util.yorml.YormlContinuation; +import org.apache.brooklyn.util.yorml.internal.YormlConfig; +import org.apache.brooklyn.util.yorml.internal.YormlConverter; import org.apache.brooklyn.util.yorml.YormlSerializer; public abstract class YormlSerializerComposition implements YormlSerializer { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md index aa13e1be96..03ac983b37 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md @@ -25,7 +25,7 @@ We want a JSON/YAML schema which allows us to do bi-directional serialization to That is: * It is easy for a user to write the YAML which generates the objects they care about * It is easy for a user to read the YAML generated from data objects -* The syntax of the YAML can be documented automatically from the schema +* The syntax of the YAML can be documented automatically from the schema (including code-point-completion) * JSON can also be read or written (we restrict to the subset of YAML which is isomorphic to JSON) The focus on ease-of-reading and ease-of-writing differentiates this from other JSON/YAML @@ -74,100 +74,150 @@ can be documented automatically. ## Introductory Examples -### Defining types +### Defining types and instances -When defining a type, an `id` (how it is known) and an instantiable `type` (parent type) must be supplied. +You define a type by giving an `id` (how it is known) and an instance definition specifying the parent `type`. These are kept in a type registry and can be used when defining other types or instances. +A "type definition" looks like: ``` - id: shape - type: java:org.acme.Shape # where `class Shape { String name; String color; }` + definition: + type: java:org.acme.Shape # where `class Shape { String name; String color; }` ``` The `java:` prefix is an optional shorthand to allow a Java type to be accessed. For now this assumes a no-arg constructor. -You can also define types with default field values set, and of course you can refer to types -that have been defined: +You can then specify an instance to be created by giving an "instance definition", +referring to a defined `type` and optionally `fields`: ``` -- id: red-square - type: shape - fields: - # any fields here read/written by direct access by default, or fail if not matched +- type: shape + fields: # optionally name: square color: red +``` + +Type definitions can also refer to types already defined types and can give an instance definition: + +``` +- id: red-square + definition: + type: shape + fields: + # any fields here read/written by direct access by default, or fail if not matched + name: square + color: red ``` -There are many syntaxes for defining instances, described below. Most of these can -be used when defining new types. +The power of YORML is the extensible support for other syntaxes available, described below. +These lead to succinct and easy-to-use definitions, both for people to write and for people to read +even when machine-generated. The approach also supports documentation and code-point completion. -### Defining instances +### Instance definitions -You define an instance by referencing a type, and optionally specifying fields: +You define an instance to be created by referencing a type in the registry, and optionally specifying fields: type: red-square Or - - type: shape + type: shape + fields: + name: square + color: red + + +### Type definitions + +You define a new type in the registry by giving an `id` and the instance `definition`: + + id: red-square + definition: + type: shape fields: name: square color: red -TODO - integrate with registry +Where you just want to define a Java class, a shorthand permits providing `type` instead of the `definition`: + + id: shape + type: java:org.acme.Shape ### Overwriting fields -You could do this: +Fields can be overwritten, e.g. to get a pink square: + + type: red-square + fields: + # map of fields is merged with that of parent + color: pink + +You can do this in type definitions, so you could do this: ``` - id: pink-square type: red-square - fields: - # map of fields is merged with that of parent - color: pink + definition: + fields: + # map of fields is merged with that of parent + color: pink ``` Although this would be more sensible: ``` - id: square - type: shape - fields: - name: square + definition: + type: shape + fields: + name: square - id: pink-square - type: square - fields: - color: pink + definition: + type: square + fields: + color: pink ``` -TODO - just take what the parent has set and proceed - ### Allowing fields at root -If we define something like this: +Type definitions also support specifying additional "serializers", the workers which provide +alternate syntaxes. One common one is the `explicit-field` serializer, allowing fields at +the root of an instance definition. With this type defined: - id: ez-square type: square serialization: - type: explicit-field field-name: color - - type: no-others -Then we could skip the `fields` item altogether: +You could skip the `fields` item altogether and write: ``` -- type: ez-square - color: blue +type: ez-square +color: pink ``` -TODO explicit-field -TODO no-others +These are inherited, so we'd probably prefer to have these type definitions: +``` +- id: shape + definition: + type: java:org.acme.Shape # where `class Shape { String name; String color; }` + serialization: + - type: explicit-field + field-name: name + - type: explicit-field + field-name: color +- id: square + definition: + type: shape + name: square +``` ## Intermission: On serializers and implementation (can skip) @@ -175,7 +225,7 @@ Serialization takes a list of serializer types. These are applied in order, bot and deserialization, and re-run from the beginning if any are applied. `explicit-field` says to look at the root as well as in the 'fields' block. It has one required -parameter, field-name, and several optional ones: +parameter, field-name, and several optional ones, so a sample usage might look like: ``` - type: explicit-field @@ -186,23 +236,12 @@ parameter, field-name, and several optional ones: field-type: string # inferred from java field, but you can constrain further to yaml types constraint: required # currently just supports 'required' (and 'null' not allowed) or blank for none (default), but reserved for future use description: The color of the shape # text (markdown) - serialization: # optional additional serialization instructions + serialization: # optional additional serialization instructions for this field - if-string: # (defined below) set-key: field-name ``` -`no-others` says that any unrecognised fields in YAML will force an error prior to the default -deserialization steps (which attempt to write named config and then fields directly, before failing), -and on serialization it will ignore any unnamed fields. - -As a convenience if an entry in the list is a string S, the entry is taken as -`{ type: explicit-field, field-name: S }`. - -Thus the following would also be allowed: - - serialization: - - color - - type: no-others +XXX ### On overloading (really can skip!) @@ -210,49 +249,65 @@ Thus the following would also be allowed: At the heart of this YAML serialization is the idea of heavily overloading to permit the most natural way of writing in different situations. We go a bit overboard in 'serialization' to illustrate below the different strategies. (Feel free to ignore, if you're comfortable with the simple examples.) -If the `serialization` field (which expects a list) is given a map, each pair in that map is -interpreted as follows: -* if V is a map then K is set in that map as 'field-name' (error if field-name is already set) -* if V is not a map then a map is created as { field-name: K, type: V } -Thus you could also write: - serialization: - color: { alias: colour, description: "The color of the shape", constraint: required } +First, if the `serialization` field (which expects a list) is given a map, +the `convert-map-to-list` serializer converts each pair in that map to a list entry as follows: + +* if V is a non-empty map, then the corresponding list entry is the map V with `{ .key: K }` added +* otherwise, the corresponding list entry is `{ .key: K, .value: V }` + +Next, each entry in the list is interpreted as a `serialization` instance, +and the serializations defined for that type specify: + +* If the key `.value` is present and `type` is not defined, that key is renamed to `type` (ignored if `type` is already present) +* If it is a map of size exactly one, it is converted to a map as done with `convert-map-to-list` above +* If the key `.key` is present and `type` is not defined, that key is renamed to `type` (ignored if `type` is already present) +* If the item is a primitive V, it is converted to `{ .value: V }` +* If it is a map with no `type` defined, `type: explicit-field` is added -(Note that some serialization types, such as 'no-others', cannot be expressed in this way, -because `field-name` is not supported on that type. This syntax is intended for the common -case when all fields are settable and we are defining top-level fields.) +This allows the serialization rules defined on the specific type to kick in to handle `.key` or `.value` entries +introduced but not removed. In the case of `explicit-field` (the default type, as shown in the rules above), +this will rename either such key `.value` to `field-name` (and give an error if `field-name` is already present). -Finally if the serialization is given a list, and any entry in the list is a map which -does not define a type, the following rules apply: -* If the entry is a map of size larger than one, the type defaults to explicit-field. -* If the entry is a map of size one the key is taken as the type and merged with the value - if the value is a map (or it can be interpreted as such with an if-string on the type) Thus we can write: + serialization: + # explicit fields + color: { alias: colour, description: "The color of the shape", constraint: required } + name + +Or + serialization: - field-name: color alias: colour - - no-others: + - name -Note: this has some surprising side-effects in occasional edge cases; consider: +This can have some surprising side-effects in occasional edge cases; consider: ``` - # BAD: this would try to load a serialization type 'field-name' + # BAD: this would try to load a type called 'color' serialization: - - field-name: color + - color: {} # GOOD options serialization: - color # or serialization: - color: + color: {} # or + serialization: + color: explicit-field + + # BAD: this would try to load a type called 'field-name' + serialization: + - field-name: color + # GOOD options are those in the previous block or to add another field serialization: - field-name: color alias: colour - # BAD: this would define a field `explicitField`, then fail because that field-name is in use + # BAD: this ultimately takes "explicit-field" as the "field-name", giving a conflict serialization: explicit-field: { field-name: color } # GOOD options (in addition to those in previous section, but assuming you wanted to say the type explicitly) @@ -262,11 +317,11 @@ Note: this has some surprising side-effects in occasional edge cases; consider: - explicit-field: color ``` -It does the right thing in most cases, and it serves to illustrate the flexibility of this -approach. In most cases it's probably a bad idea to do this much overloading! However the -descriptions here will normally be taken from java annotations and not written by hand, -so emphasis is on making it easy-to-read (which overloading does nicely) rather than -easy-to-write. +It does the right thing in most cases, and it serves to illustrate the flexibility of this approach. +Of course in most cases it's probably a bad idea to do this much overloading! +However the descriptions here will normally be taken from java annotations and not written by hand, +so emphasis is on making type definitions easy-to-read (which overloading does nicely), and +instance definitions both easy-to-read and -write, rather than type definitions easy-to-write. Of course if you have any doubt, simply use the long-winded syntax and avoid any convenience syntax: @@ -274,9 +329,9 @@ Of course if you have any doubt, simply use the long-winded syntax and avoid any serialization: - type: explicit-field field-name: color - alias: colour ``` + ## Further Behaviours ### Name Mangling and Aliases @@ -356,10 +411,10 @@ schema: # (and same for shorthand `k: x`; however if just `k` is supplied it # takes a default type `explicit-field`) - type: convert-map-to-map-list - key-for-key: field-name - key-for-string-value: type # note, only applies if x non-blank - default: - type: explicit-field # note: needed to prevent collision with `convert-single-key-in-list` + key-for-key: field-name + key-for-string-value: type # note, only applies if x non-blank + default: + type: explicit-field # note: needed to prevent collision with `convert-single-key-in-list` # if yaml is a list containing all maps swith a single key, treat the key specially # transforms `- x: k` or `- x: { field-name: k }` to `- { type: x, field-name: k }` @@ -459,19 +514,62 @@ So the general process is: * reading the fields, removing from a list in the blackboard and writing to the object and on write from java : * creating any complex structure for the YAML data object - * writing the fields (which might be a map on blackboard, maybe referring to the YAML map, maybe referring to an object within it) - ?? but if something is primitive-or-map depending on fields? add CONTINUE_UNCHANGED_BUT_RERUN_IF_CHANGED? + * writing the fields (which might be a map on blackboard, maybe referring to the YAML map, maybe referring to an object within it), + but if something is primitive-or-map depending on fields? add CONTINUE_UNCHANGED_BUT_RERUN_IF_CHANGED? * check that everything that needed to be done was done + +### Text Above WIP + +Another serializer, `restrict-fields`, throws an error if there is an attempt +to set -- or a need to write -- any fields not whitelisted. +So if we wanted to prevent the `name: square` from being overwritable, +we could have defined `square` as follows: + +``` +- id: square + definition: + type: shape + name: square + serialization: + - type: restrict-fields + fields: [ name ] +``` + +As a convenience if an entry in the list is a string S, the entry is taken as +`{ type: explicit-field, field-name: S }`. + +Thus the following would also be allowed: + +``` +- id: square + serialization: + - color + - type: restrict-fields + fields: color + definition: + type: shape + fields: + name: square +``` + + ### TODO -* explicit-field reads fields at root * explicit-field aliases -* defining serializers? +* no-others / suppression (including fixing this doc) * complex syntax, type as key, etc -* maps and lists +* maps and lists, with generics + +* defining serializers and linking to brooklyn + +* infinite loop detection: in serialize loop +* handle references, solve infinite loop detection in self-referential writes, with `.reference: ../../OBJ` * best-serialization vs first-serialization +* documentation +* yaml segment information and code-point completion + ## Real World Use Cases @@ -503,3 +601,4 @@ So the general process is: field: effectors - type: effector ``` + diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java index 302f32e0fb..ac2c58131a 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java @@ -130,4 +130,8 @@ public void testWriteExplicitFieldNoExpectedType() { assertResult( SIMPLE_IN_WITH_TYPE ); } + /* + * TODO + * aliases + */ } From c562d5ccb0c6557a5bb4e947cff031cfec7d92d0 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 27 Jun 2016 11:04:12 +0100 Subject: [PATCH 09/77] WIP handling defaults, before switching to phases --- .../util/yorml/YormlContinuation.java | 2 +- .../util/yorml/internal/YormlConverter.java | 11 +- .../util/yorml/serializers/ExplicitField.java | 170 ++++++++++++------ .../serializers/ExplicitFieldsBlackboard.java | 93 ++++++++++ .../serializers/FieldsInMapUnderFields.java | 5 +- .../serializers/YamlKeysOnBlackboard.java | 2 +- .../org/apache/brooklyn/util/yorml/sketch.md | 9 +- .../util/yorml/tests/ExplicitFieldTests.java | 3 + 8 files changed, 233 insertions(+), 62 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContinuation.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContinuation.java index b1bbfc3fd8..a88412024d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContinuation.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContinuation.java @@ -18,4 +18,4 @@ */ package org.apache.brooklyn.util.yorml; -public enum YormlContinuation { RESTART, CONTINUE_CHANGED, CONTINUE_UNCHANGED, FINISHED } \ No newline at end of file +public enum YormlContinuation { RESTART, CONTINUE_THEN_RERUN, CONTINUE_UNCHANGED, FINISHED } \ No newline at end of file diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java index 713be8cd71..630171147e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java @@ -73,6 +73,7 @@ protected void loopOverSerializers(YormlContext context) { } int i=0; + boolean rerunNeeded = false; while (i=Iterables.size(serializers.getSerializers()) && rerunNeeded) { + rerunNeeded = false; + i=0; + } } checkCompletion(context, blackboard); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java index 0dfde72046..d581fd60c9 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java @@ -19,15 +19,22 @@ package org.apache.brooklyn.util.yorml.serializers; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; +import java.util.Map; -import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; -import org.apache.brooklyn.util.yorml.YormlContextForRead; import org.apache.brooklyn.util.yorml.YormlContextForWrite; import org.apache.brooklyn.util.yorml.YormlContinuation; +import com.google.common.base.Objects; + +/* On read, after InstantiateType populates the `fields` key in YamlKeysOnBlackboard, + * look for any field(s) matching known aliases and rename them there as the fieldName, + * so FieldsInMapUnderFields will then set it in the java object correctly. + *

+ * On write, after FieldsInMapUnderFields sets the `fields` map, + * look for the field name, and rewrite under the preferred alias at the root. */ public class ExplicitField extends YormlSerializerComposition { protected YormlSerializerWorker newWorker() { @@ -38,73 +45,124 @@ protected YormlSerializerWorker newWorker() { protected String fieldType; protected String keyName; - public String getKeyName() { if (keyName!=null) return keyName; return fieldName; } + public String getPreferredKeyName() { + if (keyName!=null) return keyName; + // TODO mangle + return fieldName; + } + public Iterable getKeyNameAndAliases() { + // TODO make transient + MutableSet keyNameAndAliases = MutableSet.of(); + keyNameAndAliases.addIfNotNull(keyName); + keyNameAndAliases.addIfNotNull(fieldName); + return keyNameAndAliases; + } + + Boolean required; + Maybe defaultValue; public class Worker extends YormlSerializerWorker { + + // TODO not used + Map getFieldsFromYamlsKeyOnBlackboard() { + @SuppressWarnings("unchecked") + Map fields = peekFromYamlKeysOnBlackboard("fields", Map.class).orNull(); + if (fields==null) { + // should have been created by InstantiateType + throw new IllegalStateException("fields should be set as a yaml key on the blackboard"); + } + return fields; + } + public YormlContinuation read() { if (!hasJavaObject()) return YormlContinuation.CONTINUE_UNCHANGED; - - String keyName = getKeyName(); - Maybe value = peekFromYamlKeysOnBlackboard(keyName, Object.class); - if (value.isAbsent()) return YormlContinuation.CONTINUE_UNCHANGED; - - Maybe ffm = Reflections.findFieldMaybe(getJavaObject().getClass(), fieldName); - if (ffm.isAbsentOrNull()) { - throw new IllegalStateException("Expected field `"+fieldName+"` in "+getJavaObject()); - } - - Field ff = ffm.get(); - if (Modifier.isStatic(ff.getModifiers())) { - throw new IllegalStateException("Cannot set static `"+fieldName+"` in "+getJavaObject()); - } - - String fieldType = ExplicitField.this.fieldType; - if (fieldType==null) fieldType = config.getTypeRegistry().getTypeNameOfClass(ff.getType()); + @SuppressWarnings("unchecked") + Map fields = peekFromYamlKeysOnBlackboard("fields", Map.class).orNull(); + /* + * if fields is null either we are too early (not yet set by instantiate-type) + * or too late (already read in to java), so we bail and this yaml key cannot be handled + */ + if (fields==null) return YormlContinuation.CONTINUE_UNCHANGED; - YormlContextForRead subcontext = new YormlContextForRead(context.getJsonPath()+"/"+keyName, fieldType); - subcontext.setYamlObject(value.get()); - Object v2 = converter.read(subcontext); - - ff.setAccessible(true); - try { - ff.set(getJavaObject(), v2); - } catch (Exception e) { - // TODO do we need to say where? - Exceptions.propagate(e); + if (ExplicitFieldsBlackboard.get(blackboard).isFieldDone(fieldName)) return YormlContinuation.CONTINUE_UNCHANGED; + ExplicitFieldsBlackboard.get(blackboard).setRequiredIfUnset(fieldName, required); + + int keysMatched = 0; + boolean rerunNeeded = false; + for (String alias: getKeyNameAndAliases()) { + Maybe value = peekFromYamlKeysOnBlackboard(alias, Object.class); + boolean fieldAlreadyHandled = fields.containsKey(fieldName); + if (value.isAbsent() && !fieldAlreadyHandled) { + // should we take this as the default + if (ExplicitFieldsBlackboard.get(blackboard).shouldUseDefaultFrom(fieldName, ExplicitField.this)) { + // this was determined as the serializer to use for defaults on a previous pass + value = defaultValue==null ? Maybe.absentNoTrace("no default value") : defaultValue;; + } + if (value.isAbsent()) { + // use this as default on subsequent run if appropriate + if (!ExplicitFieldsBlackboard.get(blackboard).hasDefault(fieldName) && defaultValue!=null && defaultValue.isPresent()) { + ExplicitFieldsBlackboard.get(blackboard).setUseDefaultFrom(fieldName, ExplicitField.this); + rerunNeeded = true; + } + continue; + } + } + if (value.isPresent() && fieldAlreadyHandled) { + // already present + // TODO throw if different + continue; + } + // value present, field not yet handled + ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); + removeFromYamlKeysOnBlackboard(alias); + fields.put(fieldName, value.get()); + keysMatched++; } - removeFromYamlKeysOnBlackboard(keyName); - - return YormlContinuation.CONTINUE_CHANGED; + return keysMatched > 0 || rerunNeeded ? YormlContinuation.CONTINUE_THEN_RERUN : YormlContinuation.CONTINUE_UNCHANGED; } public YormlContinuation write() { + if (ExplicitFieldsBlackboard.get(blackboard).isFieldDone(fieldName)) return YormlContinuation.CONTINUE_UNCHANGED; + + // first pass determines what is required and what the default is + // (could run this on other passes so not completely efficient) + ExplicitFieldsBlackboard.get(blackboard).setRequiredIfUnset(fieldName, required); + if (ExplicitFieldsBlackboard.get(blackboard).getDefault(fieldName).isAbsent() && defaultValue!=null && defaultValue.isPresent()) { + ExplicitFieldsBlackboard.get(blackboard).setUseDefaultFrom(fieldName, ExplicitField.this, defaultValue.get()); + return YormlContinuation.CONTINUE_THEN_RERUN; + } + if (!isYamlMap()) return YormlContinuation.CONTINUE_UNCHANGED; - JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); - if (fib==null || fib.fieldsToWriteFromJava.isEmpty()) return YormlContinuation.CONTINUE_UNCHANGED; - Maybe v = Reflections.getFieldValueMaybe(getJavaObject(), fieldName); - if (v.isAbsent()) { - return YormlContinuation.CONTINUE_UNCHANGED; + @SuppressWarnings("unchecked") + Map fields = peekFromYamlKeysOnBlackboard("fields", Map.class).orNull(); + if (fields==null) return YormlContinuation.CONTINUE_UNCHANGED; + + Maybe dv = ExplicitFieldsBlackboard.get(blackboard).getDefault(fieldName); + + if (!fields.containsKey(fieldName)) { + // field not present, so null (or not known) + if ((dv.isPresent() && dv.isNull()) || (!ExplicitFieldsBlackboard.get(blackboard).isRequired(fieldName) && dv.isAbsent())) { + // if default is null, or if not required and no default, we can suppress + ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); + return YormlContinuation.CONTINUE_UNCHANGED; + } + // default is non-null or field is required, so write the explicit null + fields.put(getPreferredKeyName(), null); + ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); + return YormlContinuation.CONTINUE_THEN_RERUN; } - fib.fieldsToWriteFromJava.remove(fieldName); - if (v.get()==null) { - // silently drop - return YormlContinuation.CONTINUE_CHANGED; - } else { - Field ff = Reflections.findFieldMaybe(getJavaObject().getClass(), fieldName).get(); - - String fieldType = ExplicitField.this.fieldType; - if (fieldType==null) fieldType = config.getTypeRegistry().getTypeNameOfClass(ff.getType()); - - YormlContextForWrite subcontext = new YormlContextForWrite(context.getJsonPath()+"/"+getKeyName(), fieldType); - subcontext.setJavaObject(v.get()); - - Object v2 = converter.write(subcontext); - setInYamlMap(getKeyName(), v2); - // no reason to restart? - return YormlContinuation.CONTINUE_CHANGED; + Object value = fields.remove(fieldName); + ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); + + // field present + if (dv.isPresent() && Objects.equal(dv.get(), value)) { + // suppress if it equals the default + return YormlContinuation.CONTINUE_UNCHANGED; } + fields.put(getPreferredKeyName(), value); + return YormlContinuation.CONTINUE_THEN_RERUN; } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java new file mode 100644 index 0000000000..a8cc849771 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.serializers; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.YormlException; +import org.apache.brooklyn.util.yorml.YormlRequirement; +import org.apache.brooklyn.util.yorml.YormlSerializer; + +public class ExplicitFieldsBlackboard implements YormlRequirement { + + public static final String KEY = ExplicitFieldsBlackboard.class.getCanonicalName(); + + public static ExplicitFieldsBlackboard get(Map blackboard) { + Object v = blackboard.get(KEY); + if (v==null) { + v = new ExplicitFieldsBlackboard(); + blackboard.put(KEY, v); + } + return (ExplicitFieldsBlackboard) v; + } + + private final Set fieldsDone = MutableSet.of(); + private final Map fieldsRequired = MutableMap.of(); + private final Map defaultValueForFieldComesFromSerializer = MutableMap.of(); + private final Map defaultValueOfField = MutableMap.of(); + + @Override + public void checkCompletion(YormlContext context) { + List incompleteRequiredFields = MutableList.of(); + for (Map.Entry fieldRequired: fieldsRequired.entrySet()) { + if (fieldRequired.getValue() && !fieldsDone.contains(fieldRequired.getKey())) { + incompleteRequiredFields.add(fieldRequired.getKey()); + } + } + if (!incompleteRequiredFields.isEmpty()) { + throw new YormlException("Missing one or more explicitly required fields: "+Strings.join(incompleteRequiredFields, ", "), context); + } + } + public boolean isRequired(String fieldName) { + return Maybe.ofDisallowingNull(fieldsRequired.get(fieldName)).or(false); + } + + public boolean isFieldDone(String fieldName) { + return fieldsDone.contains(fieldName); + } + public void setFieldDone(String fieldName) { + fieldsDone.add(fieldName); + } + + public void setRequiredIfUnset(String fieldName, Boolean required) { + if (required==null) return; + if (fieldsRequired.get(fieldName)!=null) return; + fieldsRequired.put(fieldName, required); + } + + public void setUseDefaultFrom(String fieldName, YormlSerializer explicitField, Object defaultValue) { + defaultValueForFieldComesFromSerializer.put(fieldName, explicitField); + defaultValueOfField.put(fieldName, defaultValue); + } + public boolean shouldUseDefaultFrom(String fieldName, YormlSerializer explicitField) { + return explicitField.equals(defaultValueForFieldComesFromSerializer.get(fieldName)); + } + public Maybe getDefault(String fieldName) { + if (!defaultValueOfField.containsKey(fieldName)) return Maybe.absent("no default"); + return Maybe.of(defaultValueOfField.get(fieldName)); + } +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java index fe2625117f..185e5140c1 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java @@ -76,8 +76,8 @@ public YormlContinuation read() { if (((Map)fields).isEmpty()) { removeFromYamlKeysOnBlackboard("fields"); } - // no reason to restart? - return YormlContinuation.CONTINUE_CHANGED; + // restart (there is normally nothing after this so could equally continue with rerun) + return YormlContinuation.RESTART; } return YormlContinuation.CONTINUE_UNCHANGED; @@ -112,6 +112,7 @@ public YormlContinuation write() { if (fields.isEmpty()) return YormlContinuation.CONTINUE_UNCHANGED; setInYamlMap("fields", fields); + // restart in case a serializer moves the `fields` map somewhere else return YormlContinuation.RESTART; } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java index 8560dbef76..d2c40b67b8 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java @@ -44,7 +44,7 @@ public static YamlKeysOnBlackboard create(Map blackboard) { blackboard.put(KEY, new YamlKeysOnBlackboard()); return peek(blackboard); } - + Map yamlKeysToReadToJava; @Override diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md index 03ac983b37..491f443f87 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md @@ -505,7 +505,14 @@ A blackboard is used to share information including suppressing serializers. Serializers do nothing if their preconditions aren't met, and serializers can (and typically do) restart the serialization cycle if they change data. -So the general process is: +So the general process is a set of phases, on read: + +* preparing +* handling-type +* handling-fields +* check-completion + +TODO above better than below? * first r/w the type, and on write note the fields to write * adjust the data until a pass of serializers completes with all CONTINUE or any FINISHED (and nothing requesting a rerun); diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java index ac2c58131a..51549ec8ae 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java @@ -133,5 +133,8 @@ public void testWriteExplicitFieldNoExpectedType() { /* * TODO * aliases + * defaultValue + * if missing required + * type hierarchy chains */ } From 7c9168368a03bcbdc387774499252bbcf2a3b550 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 27 Jun 2016 13:07:48 +0100 Subject: [PATCH 10/77] switching to using phases for YORML conversion fleshing out how explicit fields will handle defaults/aliases, with tests, and fixing a bug in primitive handling --- .../brooklyn/util/collections/MutableSet.java | 3 +- .../brooklyn/util/yorml/YormlContext.java | 53 ++++++ .../util/yorml/YormlContinuation.java | 21 --- .../brooklyn/util/yorml/YormlSerializer.java | 4 +- .../util/yorml/internal/YormlConverter.java | 41 +++-- .../util/yorml/serializers/ExplicitField.java | 151 ++++++++++-------- .../serializers/FieldsInMapUnderFields.java | 32 ++-- .../yorml/serializers/InstantiateType.java | 109 ++++++++----- .../YormlSerializerComposition.java | 26 +-- .../org/apache/brooklyn/util/yorml/sketch.md | 100 ++++-------- .../util/yorml/tests/ExplicitFieldTests.java | 59 ++++++- .../util/yorml/tests/YormlBasicTests.java | 26 ++- 12 files changed, 361 insertions(+), 264 deletions(-) delete mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContinuation.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableSet.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableSet.java index bcadf33372..4b70e37430 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableSet.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableSet.java @@ -18,6 +18,7 @@ */ package org.apache.brooklyn.util.collections; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; @@ -213,7 +214,7 @@ public MutableSet putAll(Iterable setToAdd) { if (setToAdd!=null) addAll(setToAdd); return this; } - + public boolean removeIfNotNull(V item) { if (item==null) return false; return remove(item); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java index 7a2ce8b601..dddb6a88e7 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java @@ -18,6 +18,16 @@ */ package org.apache.brooklyn.util.yorml; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableSet; + +import com.google.common.base.Objects; + public abstract class YormlContext { final String jsonPath; @@ -25,6 +35,18 @@ public abstract class YormlContext { Object javaObject; Object yamlObject; + + String phaseCurrent = null; + int phaseCurrentStep = -1; + Set phasesFollowing = MutableSet.of(StandardPhases.MANIPULATING, StandardPhases.HANDLING_TYPE, StandardPhases.HANDLING_FIELDS); + List phasesPreceding = MutableList.of(); + + public static interface StandardPhases { + String MANIPULATING = "manipulating"; + String HANDLING_TYPE = "handling-type"; + String HANDLING_FIELDS = "handling-fields"; + } + public YormlContext(String jsonPath, String expectedType) { this.jsonPath = jsonPath; this.expectedType = expectedType; @@ -49,4 +71,35 @@ public void setYamlObject(Object yamlObject) { this.yamlObject = yamlObject; } + public boolean isPhase(String phase) { return Objects.equal(phase, phaseCurrent); } + public boolean seenPhase(String phase) { return phasesPreceding.contains(phase); } + public boolean willDoPhase(String phase) { return phasesFollowing.contains(phase); } + public String phaseCurrent() { return phaseCurrent; } + public int phaseCurrentStep() { return phaseCurrentStep; } + public int phaseStepAdvance() { + if (phaseCurrentStep() < Integer.MAX_VALUE) phaseCurrentStep++; + return phaseCurrentStep(); + } + public boolean phaseAdvance() { + if (phaseCurrent!=null) phasesPreceding.add(phaseCurrent); + Iterator fi = phasesFollowing.iterator(); + if (!fi.hasNext()) { + phaseCurrent = null; + phaseCurrentStep = Integer.MAX_VALUE; + return false; + } + phaseCurrent = fi.next(); + phasesFollowing = MutableSet.copyOf(fi); + phaseCurrentStep = -1; + return true; + } + public void phaseRestart() { phaseCurrentStep = -1; } + public void phaseInsert(String nextPhase, String ...otherNextPhases) { + phasesFollowing = MutableSet.of(nextPhase).putAll(Arrays.asList(otherNextPhases)).putAll(phasesFollowing); + } + public void phasesFinished() { + if (phaseCurrent!=null) phasesPreceding.add(phaseCurrent); + phasesFollowing = MutableSet.of(); phaseAdvance(); + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContinuation.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContinuation.java deleted file mode 100644 index a88412024d..0000000000 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContinuation.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.brooklyn.util.yorml; - -public enum YormlContinuation { RESTART, CONTINUE_THEN_RERUN, CONTINUE_UNCHANGED, FINISHED } \ No newline at end of file diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlSerializer.java index 22bf2f9aaf..acf777488b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlSerializer.java @@ -37,7 +37,7 @@ public interface YormlSerializer { * returning true if it did anything (and so should restart the cycle). * implementations must NOT return true indefinitely if passed the same instances! */ - public YormlContinuation read(YormlContextForRead context, YormlConverter converter, Map blackboard); + public void read(YormlContextForRead context, YormlConverter converter, Map blackboard); /** * modifies java object and/or yaml object and/or blackboard as appropriate, @@ -45,7 +45,7 @@ public interface YormlSerializer { * returning true if it did anything (and so should restart the cycle). * implementations must NOT return true indefinitely if passed the same instances! */ - public YormlContinuation write(YormlContextForWrite context, YormlConverter converter, Map blackboard); + public void write(YormlContextForWrite context, YormlConverter converter, Map blackboard); /** * generates human-readable schema for a type using this schema. diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java index 630171147e..006722e4f9 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java @@ -24,15 +24,18 @@ import org.apache.brooklyn.util.yorml.YormlContext; import org.apache.brooklyn.util.yorml.YormlContextForRead; import org.apache.brooklyn.util.yorml.YormlContextForWrite; -import org.apache.brooklyn.util.yorml.YormlContinuation; import org.apache.brooklyn.util.yorml.YormlRequirement; import org.apache.brooklyn.util.yorml.YormlSerializer; import org.apache.brooklyn.util.yorml.serializers.ReadingTypeOnBlackboard; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.common.collect.Iterables; public class YormlConverter { + private static final Logger log = LoggerFactory.getLogger(YormlConverter.class); + private final YormlConfig config; public YormlConverter(YormlConfig config) { @@ -69,33 +72,25 @@ protected void loopOverSerializers(YormlContext context) { if (context instanceof YormlContextForRead) { // read needs instantiated so that these errors display first + // TODO can skip now that we have phases? ReadingTypeOnBlackboard.get(blackboard); } - int i=0; - boolean rerunNeeded = false; - while (i=Iterables.size(serializers.getSerializers()) && rerunNeeded) { - rerunNeeded = false; - i=0; + while (context.phaseAdvance()) { + while (context.phaseStepAdvance() aliases; + public String getPreferredKeyName() { if (keyName!=null) return keyName; // TODO mangle return fieldName; } public Iterable getKeyNameAndAliases() { - // TODO make transient + // TODO use transient cache MutableSet keyNameAndAliases = MutableSet.of(); keyNameAndAliases.addIfNotNull(keyName); keyNameAndAliases.addIfNotNull(fieldName); + keyNameAndAliases.addIfNotNull(alias); + keyNameAndAliases.putAll(aliases); return keyNameAndAliases; } Boolean required; - Maybe defaultValue; + // TODO would be nice to support maybe here, only one reference, but it makes it hard to set + Object defaultValue; public class Worker extends YormlSerializerWorker { - // TODO not used - Map getFieldsFromYamlsKeyOnBlackboard() { - @SuppressWarnings("unchecked") - Map fields = peekFromYamlKeysOnBlackboard("fields", Map.class).orNull(); - if (fields==null) { - // should have been created by InstantiateType - throw new IllegalStateException("fields should be set as a yaml key on the blackboard"); + String PREPARING_EXPLICIT_FIELDS = "preparing-explicit-fields"; + + protected boolean readyForMainEvent() { + if (!context.seenPhase(YormlContext.StandardPhases.HANDLING_TYPE)) return false; + if (!context.seenPhase(PREPARING_EXPLICIT_FIELDS)) { + if (context.isPhase(YormlContext.StandardPhases.MANIPULATING)) { + // interrupt the manipulating phase to do a preparing phase + context.phaseInsert(PREPARING_EXPLICIT_FIELDS, StandardPhases.MANIPULATING); + context.phaseAdvance(); + return false; + } + } + if (context.isPhase(PREPARING_EXPLICIT_FIELDS)) { + // do the pre-main pass to determine what is required for explicit fields and what the default is + ExplicitFieldsBlackboard.get(blackboard).setRequiredIfUnset(fieldName, required); + if (ExplicitFieldsBlackboard.get(blackboard).getDefault(fieldName).isAbsent() && defaultValue!=null) { + ExplicitFieldsBlackboard.get(blackboard).setUseDefaultFrom(fieldName, ExplicitField.this, defaultValue); + } + // TODO combine aliases, other items + return false; } - return fields; + if (ExplicitFieldsBlackboard.get(blackboard).isFieldDone(fieldName)) return false; + if (!context.isPhase(YormlContext.StandardPhases.MANIPULATING)) return false; + return true; } - public YormlContinuation read() { - if (!hasJavaObject()) return YormlContinuation.CONTINUE_UNCHANGED; + public void read() { + if (!readyForMainEvent()) return; + if (!hasJavaObject()) return; + if (!isYamlMap()) return; @SuppressWarnings("unchecked") Map fields = peekFromYamlKeysOnBlackboard("fields", Map.class).orNull(); + /* * if fields is null either we are too early (not yet set by instantiate-type) - * or too late (already read in to java), so we bail and this yaml key cannot be handled + * or too late (already read in to java), so we bail -- this yaml key cannot be handled at this time */ - if (fields==null) return YormlContinuation.CONTINUE_UNCHANGED; + if (fields==null) return; - if (ExplicitFieldsBlackboard.get(blackboard).isFieldDone(fieldName)) return YormlContinuation.CONTINUE_UNCHANGED; - ExplicitFieldsBlackboard.get(blackboard).setRequiredIfUnset(fieldName, required); - int keysMatched = 0; - boolean rerunNeeded = false; for (String alias: getKeyNameAndAliases()) { Maybe value = peekFromYamlKeysOnBlackboard(alias, Object.class); - boolean fieldAlreadyHandled = fields.containsKey(fieldName); - if (value.isAbsent() && !fieldAlreadyHandled) { - // should we take this as the default - if (ExplicitFieldsBlackboard.get(blackboard).shouldUseDefaultFrom(fieldName, ExplicitField.this)) { - // this was determined as the serializer to use for defaults on a previous pass - value = defaultValue==null ? Maybe.absentNoTrace("no default value") : defaultValue;; - } - if (value.isAbsent()) { - // use this as default on subsequent run if appropriate - if (!ExplicitFieldsBlackboard.get(blackboard).hasDefault(fieldName) && defaultValue!=null && defaultValue.isPresent()) { - ExplicitFieldsBlackboard.get(blackboard).setUseDefaultFrom(fieldName, ExplicitField.this); - rerunNeeded = true; - } - continue; - } - } - if (value.isPresent() && fieldAlreadyHandled) { + if (value.isAbsent()) continue; + boolean fieldAlreadyKnown = fields.containsKey(fieldName); + if (value.isPresent() && fieldAlreadyKnown) { // already present // TODO throw if different continue; } // value present, field not yet handled - ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); removeFromYamlKeysOnBlackboard(alias); fields.put(fieldName, value.get()); keysMatched++; } - return keysMatched > 0 || rerunNeeded ? YormlContinuation.CONTINUE_THEN_RERUN : YormlContinuation.CONTINUE_UNCHANGED; + if (keysMatched==0) { + // set a default if there is one + Maybe value = ExplicitFieldsBlackboard.get(blackboard).getDefault(fieldName); + if (value.isPresentAndNonNull()) { + fields.put(fieldName, value.get()); + keysMatched++; + } + } + if (keysMatched>0) { + // repeat the preparing phase if we set any keys, so that remapping can apply + ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); + context.phaseInsert(StandardPhases.MANIPULATING); + } } - public YormlContinuation write() { - if (ExplicitFieldsBlackboard.get(blackboard).isFieldDone(fieldName)) return YormlContinuation.CONTINUE_UNCHANGED; - - // first pass determines what is required and what the default is - // (could run this on other passes so not completely efficient) - ExplicitFieldsBlackboard.get(blackboard).setRequiredIfUnset(fieldName, required); - if (ExplicitFieldsBlackboard.get(blackboard).getDefault(fieldName).isAbsent() && defaultValue!=null && defaultValue.isPresent()) { - ExplicitFieldsBlackboard.get(blackboard).setUseDefaultFrom(fieldName, ExplicitField.this, defaultValue.get()); - return YormlContinuation.CONTINUE_THEN_RERUN; - } - - if (!isYamlMap()) return YormlContinuation.CONTINUE_UNCHANGED; + public void write() { + if (!readyForMainEvent()) return; + if (!isYamlMap()) return; @SuppressWarnings("unchecked") - Map fields = peekFromYamlKeysOnBlackboard("fields", Map.class).orNull(); - if (fields==null) return YormlContinuation.CONTINUE_UNCHANGED; + Map fields = getFromYamlMap("fields", Map.class).orNull(); + if (fields==null) return; Maybe dv = ExplicitFieldsBlackboard.get(blackboard).getDefault(fieldName); + Maybe valueToSet; if (!fields.containsKey(fieldName)) { // field not present, so null (or not known) if ((dv.isPresent() && dv.isNull()) || (!ExplicitFieldsBlackboard.get(blackboard).isRequired(fieldName) && dv.isAbsent())) { // if default is null, or if not required and no default, we can suppress ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); - return YormlContinuation.CONTINUE_UNCHANGED; + return; } // default is non-null or field is required, so write the explicit null - fields.put(getPreferredKeyName(), null); - ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); - return YormlContinuation.CONTINUE_THEN_RERUN; + valueToSet = Maybe.ofAllowingNull(null); + } else { + // field present + valueToSet = Maybe.of(fields.remove(fieldName)); + if (dv.isPresent() && Objects.equal(dv.get(), valueToSet.get())) { + // suppress if it equals the default + valueToSet = Maybe.absent(); + } } - Object value = fields.remove(fieldName); - ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); - - // field present - if (dv.isPresent() && Objects.equal(dv.get(), value)) { - // suppress if it equals the default - return YormlContinuation.CONTINUE_UNCHANGED; + if (valueToSet.isPresent()) { + ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); + Object oldValue = getYamlMap().put(getPreferredKeyName(), valueToSet.get()); + if (oldValue!=null && !oldValue.equals(valueToSet.get())) { + throw new IllegalStateException("Conflicting values for `"+getPreferredKeyName()+"`"); + } + // and move the `fields` object to the end + getYamlMap().remove("fields"); + getYamlMap().put("fields", fields); + // rerun this phase again, as we've changed it + context.phaseInsert(StandardPhases.MANIPULATING); } - fields.put(getPreferredKeyName(), value); - return YormlContinuation.CONTINUE_THEN_RERUN; } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java index 185e5140c1..628841e5b5 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java @@ -28,9 +28,9 @@ import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yorml.YormlContext; import org.apache.brooklyn.util.yorml.YormlContextForRead; import org.apache.brooklyn.util.yorml.YormlContextForWrite; -import org.apache.brooklyn.util.yorml.YormlContinuation; public class FieldsInMapUnderFields extends YormlSerializerComposition { @@ -39,12 +39,13 @@ protected YormlSerializerWorker newWorker() { } public static class Worker extends YormlSerializerWorker { - public YormlContinuation read() { - if (!hasJavaObject()) return YormlContinuation.CONTINUE_UNCHANGED; + public void read() { + if (!context.isPhase(YormlContext.StandardPhases.HANDLING_FIELDS)) return; + if (!hasJavaObject()) return; @SuppressWarnings("unchecked") Map fields = peekFromYamlKeysOnBlackboard("fields", Map.class).orNull(); - if (fields==null) return YormlContinuation.CONTINUE_UNCHANGED; + if (fields==null) return; boolean changed = false; for (Object f: MutableList.copyOf( ((Map)fields).keySet() )) { @@ -77,17 +78,16 @@ public YormlContinuation read() { removeFromYamlKeysOnBlackboard("fields"); } // restart (there is normally nothing after this so could equally continue with rerun) - return YormlContinuation.RESTART; + context.phaseRestart(); } - - return YormlContinuation.CONTINUE_UNCHANGED; } - public YormlContinuation write() { - if (!isYamlMap()) return YormlContinuation.CONTINUE_UNCHANGED; - if (getFromYamlMap("fields", Map.class)!=null) return YormlContinuation.CONTINUE_UNCHANGED; + public void write() { + if (!context.isPhase(YormlContext.StandardPhases.HANDLING_FIELDS)) return; + if (!isYamlMap()) return; + if (getFromYamlMap("fields", Map.class).isPresent()) return; JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); - if (fib==null || fib.fieldsToWriteFromJava.isEmpty()) return YormlContinuation.CONTINUE_UNCHANGED; + if (fib==null || fib.fieldsToWriteFromJava.isEmpty()) return; Map fields = MutableMap.of(); @@ -109,11 +109,11 @@ public YormlContinuation write() { } } - if (fields.isEmpty()) return YormlContinuation.CONTINUE_UNCHANGED; - - setInYamlMap("fields", fields); - // restart in case a serializer moves the `fields` map somewhere else - return YormlContinuation.RESTART; + if (!fields.isEmpty()) { + setInYamlMap("fields", fields); + // restart in case a serializer moves the `fields` map somewhere else + context.phaseRestart(); + } } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java index 78ff67901c..c28e5b6881 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java @@ -30,7 +30,7 @@ import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yorml.Yorml; -import org.apache.brooklyn.util.yorml.YormlContinuation; +import org.apache.brooklyn.util.yorml.YormlContext; import org.apache.brooklyn.util.yorml.internal.SerializersOnBlackboard; public class InstantiateType extends YormlSerializerComposition { @@ -40,9 +40,9 @@ protected YormlSerializerWorker newWorker() { } public static class Worker extends YormlSerializerWorker { - public YormlContinuation read() { - if (hasJavaObject()) return YormlContinuation.CONTINUE_UNCHANGED; - + public void read() { + if (!context.isPhase(YormlContext.StandardPhases.HANDLING_TYPE)) return; + if (hasJavaObject()) return; String type = null; if ((getYamlObject() instanceof String) || Boxing.isPrimitiveOrBoxedObject(getYamlObject())) { @@ -53,10 +53,11 @@ public YormlContinuation read() { Maybe result = config.getCoercer().tryCoerce(getYamlObject(), expectedJavaType); if (result.isPresent()) { context.setJavaObject(result.get()); - return YormlContinuation.RESTART; + context.phaseAdvance(); + return; } ReadingTypeOnBlackboard.get(blackboard).addNote("Cannot interpret '"+getYamlObject()+"' as "+expectedJavaType.getCanonicalName()); - return YormlContinuation.CONTINUE_UNCHANGED; + return; } // if type not expected, treat as a type type = Strings.toString(getYamlObject()); @@ -64,38 +65,60 @@ public YormlContinuation read() { // TODO if map and list? + if (!isYamlMap()) return; + YamlKeysOnBlackboard.create(blackboard).yamlKeysToReadToJava = MutableMap.copyOf(getYamlMap()); if (type==null) type = peekFromYamlKeysOnBlackboard("type", String.class).orNull(); if (type==null) type = context.getExpectedType(); - if (type==null) return YormlContinuation.CONTINUE_UNCHANGED; + if (type==null) return; + + Class javaType = config.getTypeRegistry().getJavaType(type); + boolean primitive = javaType!=null && (Boxing.isPrimitiveOrBoxedClass(javaType) || CharSequence.class.isAssignableFrom(javaType)); - Object result = config.getTypeRegistry().newInstanceMaybe((String)type, Yorml.newInstance(config)).orNull(); - if (result==null) { - ReadingTypeOnBlackboard.get(blackboard).addNote("Unknown type '"+type+"'"); - return YormlContinuation.CONTINUE_UNCHANGED; + Object result; + if (primitive) { + if (!getYamlMap().containsKey("value")) { + ReadingTypeOnBlackboard.get(blackboard).addNote("Primitive '"+type+"' does not declare a 'value'"); + return; + + } + result = config.getCoercer().coerce(getYamlMap().get("value"), javaType); + removeFromYamlKeysOnBlackboard("value"); + + } else { + result = config.getTypeRegistry().newInstanceMaybe((String)type, Yorml.newInstance(config)).orNull(); + if (result==null) { + ReadingTypeOnBlackboard.get(blackboard).addNote("Unknown type '"+type+"'"); + return; + } + if (!type.equals(context.getExpectedType())) { + SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.getTypeRegistry().getAllSerializers(type)); + } + context.phaseInsert(YormlContext.StandardPhases.MANIPULATING, YormlContext.StandardPhases.HANDLING_FIELDS); } + removeFromYamlKeysOnBlackboard("type"); - if (!type.equals(context.getExpectedType())) { - SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.getTypeRegistry().getAllSerializers(type)); - } context.setJavaObject(result); - return YormlContinuation.RESTART; + context.phaseAdvance(); } - public YormlContinuation write() { - if (hasYamlObject()) return YormlContinuation.CONTINUE_UNCHANGED; - if (!hasJavaObject()) return YormlContinuation.CONTINUE_UNCHANGED; - if (JavaFieldsOnBlackboard.isPresent(blackboard)) return YormlContinuation.CONTINUE_UNCHANGED; + public void write() { + if (!context.isPhase(YormlContext.StandardPhases.HANDLING_TYPE)) return; + if (hasYamlObject()) return; + if (!hasJavaObject()) return; + if (JavaFieldsOnBlackboard.isPresent(blackboard)) return; JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.create(blackboard); fib.fieldsToWriteFromJava = MutableList.of(); Object jo = getJavaObject(); - if (Boxing.isPrimitiveOrBoxedObject(jo) || jo instanceof CharSequence) { + boolean primitive = Boxing.isPrimitiveOrBoxedObject(jo) || jo instanceof CharSequence; + if (primitive && getJavaObject().getClass().equals(Boxing.boxedType(getExpectedTypeJava()))) { context.setYamlObject(jo); - return YormlContinuation.FINISHED; + context.phaseAdvance(); + return; } // TODO map+list -- here, or in separate serializers? @@ -111,27 +134,37 @@ public YormlContinuation write() { } else { String typeName = config.getTypeRegistry().getTypeName(getJavaObject()); map.put("type", typeName); - SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.getTypeRegistry().getAllSerializers(typeName)); + if (!primitive) { + SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.getTypeRegistry().getAllSerializers(typeName)); + } } - - List fields = Reflections.findFields(getJavaObject().getClass(), - null, - FieldOrderings.ALPHABETICAL_FIELD_THEN_SUB_BEST_FIRST); - Field lastF = null; - for (Field f: fields) { - Maybe v = Reflections.getFieldValueMaybe(getJavaObject(), f); - if (ReflectionPredicates.IS_FIELD_NON_TRANSIENT.apply(f) && ReflectionPredicates.IS_FIELD_NON_STATIC.apply(f) && v.isPresentAndNonNull()) { - String name = f.getName(); - if (lastF!=null && lastF.getName().equals(f.getName())) { - // if field is shadowed use FQN - name = f.getDeclaringClass().getCanonicalName()+"."+name; + + if (primitive) { + map.put("value", jo); + context.phaseInsert(YormlContext.StandardPhases.MANIPULATING); + + } else { + List fields = Reflections.findFields(getJavaObject().getClass(), + null, + FieldOrderings.ALPHABETICAL_FIELD_THEN_SUB_BEST_FIRST); + Field lastF = null; + for (Field f: fields) { + Maybe v = Reflections.getFieldValueMaybe(getJavaObject(), f); + if (ReflectionPredicates.IS_FIELD_NON_TRANSIENT.apply(f) && ReflectionPredicates.IS_FIELD_NON_STATIC.apply(f) && v.isPresentAndNonNull()) { + String name = f.getName(); + if (lastF!=null && lastF.getName().equals(f.getName())) { + // if field is shadowed use FQN + name = f.getDeclaringClass().getCanonicalName()+"."+name; + } + fib.fieldsToWriteFromJava.add(name); } - fib.fieldsToWriteFromJava.add(name); + lastF = f; } - lastF = f; + context.phaseInsert(YormlContext.StandardPhases.HANDLING_FIELDS, YormlContext.StandardPhases.MANIPULATING); } - - return YormlContinuation.RESTART; + + context.phaseAdvance(); + return; } } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java index 97966f3ca7..104252b163 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java @@ -26,10 +26,9 @@ import org.apache.brooklyn.util.yorml.YormlContext; import org.apache.brooklyn.util.yorml.YormlContextForRead; import org.apache.brooklyn.util.yorml.YormlContextForWrite; -import org.apache.brooklyn.util.yorml.YormlContinuation; +import org.apache.brooklyn.util.yorml.YormlSerializer; import org.apache.brooklyn.util.yorml.internal.YormlConfig; import org.apache.brooklyn.util.yorml.internal.YormlConverter; -import org.apache.brooklyn.util.yorml.YormlSerializer; public abstract class YormlSerializerComposition implements YormlSerializer { @@ -82,12 +81,13 @@ public Class getExpectedTypeJava() { *

* See also {@link #peekFromYamlKeysOnBlackboard(String, Class)} which most read serializers should use. */ @SuppressWarnings("unchecked") - public T getFromYamlMap(String key, Class type) { - if (!isYamlMap()) return null; + public Maybe getFromYamlMap(String key, Class type) { + if (!isYamlMap()) return Maybe.absent("not a yaml map"); + if (!getYamlMap().containsKey(key)) return Maybe.absent("key `"+key+"` not in yaml map"); Object v = getYamlMap().get(key); - if (v==null) return null; - if (!type.isInstance(v)) return null; - return (T) v; + if (v==null) return Maybe.ofAllowingNull(null); + if (!type.isInstance(v)) return Maybe.absent("value of key `"+key+"` is not a "+type); + return Maybe.of((T) v); } protected void setInYamlMap(String key, Object value) { ((Map)getYamlMap()).put(key, value); @@ -107,28 +107,28 @@ protected void removeFromYamlKeysOnBlackboard(String key) { ykb.yamlKeysToReadToJava.remove(key); } - public abstract YormlContinuation read(); - public abstract YormlContinuation write(); + public abstract void read(); + public abstract void write(); } @Override - public YormlContinuation read(YormlContextForRead context, YormlConverter converter, Map blackboard) { + public void read(YormlContextForRead context, YormlConverter converter, Map blackboard) { YormlSerializerWorker worker; try { worker = newWorker(); } catch (Exception e) { throw Exceptions.propagate(e); } worker.initRead(context, converter, blackboard); - return worker.read(); + worker.read(); } @Override - public YormlContinuation write(YormlContextForWrite context, YormlConverter converter, Map blackboard) { + public void write(YormlContextForWrite context, YormlConverter converter, Map blackboard) { YormlSerializerWorker worker; try { worker = newWorker(); } catch (Exception e) { throw Exceptions.propagate(e); } worker.initWrite(context, converter, blackboard); - return worker.write(); + worker.write(); } @Override diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md index 491f443f87..4659e6d679 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md @@ -471,18 +471,9 @@ either an expected type will be known or serialization rules are supplied (or bo ### Default serialization It is possible to set some serializations to be defaults run before or after a supplied list. -The default is to run the following after (but this is suppressed if `no-others` is supplied, -in which case you may want to use some before the `no-others` directive): - - * `expected-type-serializers` runs through the serializers defined on the expected type - (so e.g. a shape might define a default size, then a reference to shape would always have that) - * `instantiate-type` on read, converts a map to the declared type (this can be used explicitly - to give information on which fields should be used as parameters in constructors, - or possibly to reference a static factory method) - * `all-config` reads/writes all declared config keys if there is a `configure(...)` method - * `all-matching-fields` reads any key corresponding to a field into an object, or writes all remaining - non-transient fields - * `fields-in-fields-map` applies all the keys in a `fields` block as fields in the object +This is useful if for instance you want certain different default behaviours across the board. +Note that if interfacing with the existing defaults you wil need to understand that process +in detail; see implementation notes below. ## Even more further behaviours (not part of MVP) @@ -498,67 +489,30 @@ in which case you may want to use some before the `no-others` directive): ## Implementation Notes -We have a `Converter` which runs through `Serializer`s, -where each supports `read`, `write`, and `document`. - -A blackboard is used to share information including suppressing serializers. -Serializers do nothing if their preconditions aren't met, -and serializers can (and typically do) restart the serialization cycle if they change data. - -So the general process is a set of phases, on read: - -* preparing -* handling-type -* handling-fields -* check-completion - -TODO above better than below? - -* first r/w the type, and on write note the fields to write -* adjust the data until a pass of serializers completes with all CONTINUE or any FINISHED (and nothing requesting a rerun); - on read from yaml to java, this is: - * unpacking any complex structure in the YAML data object - * reading the fields, removing from a list in the blackboard and writing to the object - and on write from java : - * creating any complex structure for the YAML data object - * writing the fields (which might be a map on blackboard, maybe referring to the YAML map, maybe referring to an object within it), - but if something is primitive-or-map depending on fields? add CONTINUE_UNCHANGED_BUT_RERUN_IF_CHANGED? -* check that everything that needed to be done was done - - -### Text Above WIP - -Another serializer, `restrict-fields`, throws an error if there is an attempt -to set -- or a need to write -- any fields not whitelisted. -So if we wanted to prevent the `name: square` from being overwritable, -we could have defined `square` as follows: - -``` -- id: square - definition: - type: shape - name: square - serialization: - - type: restrict-fields - fields: [ name ] -``` - -As a convenience if an entry in the list is a string S, the entry is taken as -`{ type: explicit-field, field-name: S }`. - -Thus the following would also be allowed: - -``` -- id: square - serialization: - - color - - type: restrict-fields - fields: color - definition: - type: shape - fields: - name: square -``` +We have a `Converter` which runs through phases, running through all `Serializer` instances on each phase. +Each `Serializer` exposes methods to `read`, `write`, and `document`, and the appropriate method is invoked +depending on what the `Converter` is doing. + +A `Serializer` can detect the phase and bail out if it isn't appropriate; +or they can end the current phase, and/or insert one or more phases to follow the current phase. +In addition, they use a shared blackboard to store local information and communicate state. +These are the mechanisms by which serializers do the right things in the right order, +whilst allowing them to be extended. + +The general phases are: + +* `manipulating` (any custom serializers operate in this phase) +* `handling-type` (default to instantaiate the java type, on read, or set the `type` field, on write), + which if successful inserts: + * when reading: + * `manipulating` (custom serializers again, now with the object created, fields known, and other serializers loaded) + * `handling-fields` (write the fields to the java object) + * and when writing: + * `handling-fields` (collect the fields to write from the java object) + * `manipulating` (custom serializers again, now with the type set and other serializers loaded) + +Afterwards, a completion check runs across all blackboard items to enable the most appropriate error +to be shown. ### TODO diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java index 51549ec8ae..3b248a7365 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java @@ -130,11 +130,62 @@ public void testWriteExplicitFieldNoExpectedType() { assertResult( SIMPLE_IN_WITH_TYPE ); } + protected static YormlTestFixture commonExplicitFieldFixtureKeyNameAliasAndDefault() { + return YormlTestFixture.newInstance(). + addType("shape", Shape.class, MutableList.of( + explicitFieldSerializer("{ fieldName: name, keyName: shape-name, alias: my-name, defaultValue: { type: string, value: bob } }"))); + } + + static String COMMON_IN_KEY_NAME = "{ shape-name: diamond, fields: { color: black } }"; + static String COMMON_IN_ALIAS = "{ my-name: diamond, fields: { color: black } }"; + static Shape COMMON_OUT = new Shape().name("diamond").color("black"); + static String COMMON_IN_DEFAULT = "{ fields: { color: black } }"; + static Shape COMMON_OUT_DEFAULT = new Shape().name("bob").color("black"); + + @Test + public void testCommonKeyName() { + commonExplicitFieldFixtureKeyNameAliasAndDefault(). + reading( COMMON_IN_KEY_NAME, "shape" ). + writing( COMMON_OUT, "shape" ). + doReadWriteAssertingJsonMatch(); + } + + @Test + public void testCommonAlias() { + commonExplicitFieldFixtureKeyNameAliasAndDefault(). + read( COMMON_IN_ALIAS, "shape" ).assertResult(COMMON_OUT). + write( COMMON_OUT, "shape" ).assertResult(COMMON_IN_KEY_NAME); + } + + @Test + public void testCommonDefault() { + commonExplicitFieldFixtureKeyNameAliasAndDefault(). + reading( COMMON_IN_DEFAULT, "shape" ). + writing( COMMON_OUT_DEFAULT, "shape" ). + doReadWriteAssertingJsonMatch(); + } + + protected static YormlTestFixture noKeyNameExplicitFieldFixture() { + return YormlTestFixture.newInstance(). + addType("shape", Shape.class, MutableList.of( + explicitFieldSerializer("{ fieldName: name, keyName: shape-name, aliases: [ my-name ], defaultValue: bob }"))); + } + + // TODO + // aliases-inherited + // aliases-exclude-name + // no-mangle /* - * TODO + * TODO sketch for phases + * TODO tests for: * aliases - * defaultValue - * if missing required - * type hierarchy chains + * aliases inherited + * setting a defaultValue, including null + * if missing required required + * + * then FIX + * type: my-serializer as singleton map in list + * using serializers declared on ancestor above immediate */ + } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java index 4ec14be65a..64ed5fde7e 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java @@ -20,7 +20,10 @@ import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.Jsonya; +import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.yorml.Yorml; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.Test; @@ -32,6 +35,8 @@ */ public class YormlBasicTests { + private static final Logger log = LoggerFactory.getLogger(YormlBasicTests.class); + static class Shape { String name; String color; @@ -105,11 +110,17 @@ public void testFieldInFieldsUsingTestFixture() { } @Test - public void testStringPrimitiveOnItsOwn() { + public void testStringPrimitiveWhereTypeKnown() { + YormlTestFixture.newInstance(). + write("hello", "string").assertResult("hello"). + read("hello", "string").assertResult("hello"); + } + + @Test + public void testStringPrimitiveWhereTypeUnknown() { YormlTestFixture.newInstance(). - write("hello").assertResult("hello"). - read("hello", "string"). - assertResult("hello"); + write("hello").assertResult("{ type: string, value: hello }"). + read("{ type: string, value: hello }", null).assertResult("hello"); } @Test @@ -184,7 +195,12 @@ public void testFailOnUnknownType() { YormlTestFixture ytc = YormlTestFixture.newInstance().read("{ type: shape }", null); Asserts.shouldHaveFailedPreviously("Got "+ytc.lastReadResult+" when we should have failed due to unknown type shape"); } catch (Exception e) { - Asserts.expectedFailureContainsIgnoreCase(e, "shape", "unknown type"); + try { + Asserts.expectedFailureContainsIgnoreCase(e, "shape", "unknown type"); + } catch (Throwable e2) { + log.warn("Failure detail: "+e, e); + throw Exceptions.propagate(e2); + } } } From cd73104de97ddb030f08e357607c92bb032c2b4b Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 27 Jun 2016 17:01:10 +0100 Subject: [PATCH 11/77] serializers are inherited and explicit-field respects multiple/inherited also supporting multiple supertypes in the tests --- .../util/yorml/YormlTypeRegistry.java | 5 +- .../util/yorml/internal/YormlConverter.java | 6 +- .../util/yorml/serializers/ExplicitField.java | 39 ++-- .../serializers/ExplicitFieldsBlackboard.java | 47 +++- .../yorml/serializers/InstantiateType.java | 11 +- .../util/yorml/tests/ExplicitFieldTests.java | 203 +++++++++++------- .../yorml/tests/MockYormlTypeRegistry.java | 38 +++- .../util/yorml/tests/YormlBasicTests.java | 9 + 8 files changed, 242 insertions(+), 116 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java index 8808493218..049541db1b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java @@ -18,6 +18,9 @@ */ package org.apache.brooklyn.util.yorml; +import java.util.Collection; +import java.util.Set; + import org.apache.brooklyn.util.guava.Maybe; public interface YormlTypeRegistry { @@ -32,6 +35,6 @@ public interface YormlTypeRegistry { String getTypeName(Object obj); String getTypeNameOfClass(Class type); - Iterable getAllSerializers(String expectedType); + void collectSerializers(String typeName, Collection serializers, Set typesVisited); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java index 006722e4f9..309acd9df6 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java @@ -21,6 +21,7 @@ import java.util.Map; import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.yorml.YormlContext; import org.apache.brooklyn.util.yorml.YormlContextForRead; import org.apache.brooklyn.util.yorml.YormlContextForWrite; @@ -66,13 +67,12 @@ protected void loopOverSerializers(YormlContext context) { // find the serializers known so far; store on blackboard so they could be edited SerializersOnBlackboard serializers = SerializersOnBlackboard.create(blackboard); if (context.getExpectedType()!=null) { - Iterables.addAll(serializers.expectedTypeSerializers, config.typeRegistry.getAllSerializers(context.getExpectedType())); + config.typeRegistry.collectSerializers(context.getExpectedType(), serializers.expectedTypeSerializers, MutableSet.of()); } serializers.postSerializers.addAll(config.serializersPost); if (context instanceof YormlContextForRead) { - // read needs instantiated so that these errors display first - // TODO can skip now that we have phases? + // read needs instantiated so that these errors display before manipulating errors and others ReadingTypeOnBlackboard.get(blackboard); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java index a13eb3c521..1988b97d33 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java @@ -47,29 +47,30 @@ protected YormlSerializerWorker newWorker() { protected String alias; protected List aliases; - public String getPreferredKeyName() { - if (keyName!=null) return keyName; - // TODO mangle - return fieldName; - } - public Iterable getKeyNameAndAliases() { - // TODO use transient cache - MutableSet keyNameAndAliases = MutableSet.of(); - keyNameAndAliases.addIfNotNull(keyName); - keyNameAndAliases.addIfNotNull(fieldName); - keyNameAndAliases.addIfNotNull(alias); - keyNameAndAliases.putAll(aliases); - return keyNameAndAliases; - } + transient MutableSet keyNameAndAliases; Boolean required; - // TODO would be nice to support maybe here, only one reference, but it makes it hard to set + // TODO would be nice to support maybe here, not hard here, but it makes it hard to set from yaml Object defaultValue; public class Worker extends YormlSerializerWorker { String PREPARING_EXPLICIT_FIELDS = "preparing-explicit-fields"; + protected String getPreferredKeyName() { + String result = ExplicitFieldsBlackboard.get(blackboard).getKeyName(fieldName); + if (result!=null) return result; + return fieldName; + } + + protected Iterable getKeyNameAndAliases() { + MutableSet keyNameAndAliases = MutableSet.of(); + keyNameAndAliases.addIfNotNull(ExplicitFieldsBlackboard.get(blackboard).getKeyName(fieldName)); + keyNameAndAliases.addIfNotNull(fieldName); + keyNameAndAliases.addAll(ExplicitFieldsBlackboard.get(blackboard).getAliases(fieldName)); + return keyNameAndAliases; + } + protected boolean readyForMainEvent() { if (!context.seenPhase(YormlContext.StandardPhases.HANDLING_TYPE)) return false; if (!context.seenPhase(PREPARING_EXPLICIT_FIELDS)) { @@ -82,6 +83,9 @@ protected boolean readyForMainEvent() { } if (context.isPhase(PREPARING_EXPLICIT_FIELDS)) { // do the pre-main pass to determine what is required for explicit fields and what the default is + ExplicitFieldsBlackboard.get(blackboard).setKeyNameIfUnset(fieldName, keyName); + ExplicitFieldsBlackboard.get(blackboard).addAlias(fieldName, alias); + ExplicitFieldsBlackboard.get(blackboard).addAliases(fieldName, aliases); ExplicitFieldsBlackboard.get(blackboard).setRequiredIfUnset(fieldName, required); if (ExplicitFieldsBlackboard.get(blackboard).getDefault(fieldName).isAbsent() && defaultValue!=null) { ExplicitFieldsBlackboard.get(blackboard).setUseDefaultFrom(fieldName, ExplicitField.this, defaultValue); @@ -114,7 +118,9 @@ public void read() { boolean fieldAlreadyKnown = fields.containsKey(fieldName); if (value.isPresent() && fieldAlreadyKnown) { // already present - // TODO throw if different + if (!Objects.equal(value.get(), fields.get(fieldName))) { + throw new IllegalStateException("Cannot set '"+fieldName+"' to '"+value.get()+"' supplied in '"+alias+"' because this conflicts with '"+fields.get(fieldName)+"' already set"); + } continue; } // value present, field not yet handled @@ -162,6 +168,7 @@ public void write() { valueToSet = Maybe.of(fields.remove(fieldName)); if (dv.isPresent() && Objects.equal(dv.get(), valueToSet.get())) { // suppress if it equals the default + ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); valueToSet = Maybe.absent(); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java index a8cc849771..abca5f57e2 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java @@ -18,6 +18,7 @@ */ package org.apache.brooklyn.util.yorml.serializers; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -45,11 +46,47 @@ public static ExplicitFieldsBlackboard get(Map blackboard) { return (ExplicitFieldsBlackboard) v; } + private final Map keyNames = MutableMap.of(); + private final Map> aliases = MutableMap.of(); private final Set fieldsDone = MutableSet.of(); private final Map fieldsRequired = MutableMap.of(); private final Map defaultValueForFieldComesFromSerializer = MutableMap.of(); private final Map defaultValueOfField = MutableMap.of(); + public String getKeyName(String fieldName) { + return Maybe.ofDisallowingNull(keyNames.get(fieldName)).orNull(); + } + public void setKeyNameIfUnset(String fieldName, String keyName) { + if (keyName==null) return; + if (keyNames.get(fieldName)!=null) return; + keyNames.put(fieldName, keyName); + } + public void addAlias(String fieldName, String alias) { + addAliases(fieldName, MutableList.of(alias)); + } + public void addAliases(String fieldName, List aliases) { + Set aa = this.aliases.get(fieldName); + if (aa==null) { + aa = MutableSet.of(); + this.aliases.put(fieldName, aa); + } + if (aliases==null) return; + for (String alias: aliases) aa.add(alias); + } + public Collection getAliases(String fieldName) { + Set aa = this.aliases.get(fieldName); + if (aa==null) return MutableSet.of(); + return aa; + } + + public boolean isRequired(String fieldName) { + return Maybe.ofDisallowingNull(fieldsRequired.get(fieldName)).or(false); + } + public void setRequiredIfUnset(String fieldName, Boolean required) { + if (required==null) return; + if (fieldsRequired.get(fieldName)!=null) return; + fieldsRequired.put(fieldName, required); + } @Override public void checkCompletion(YormlContext context) { List incompleteRequiredFields = MutableList.of(); @@ -62,9 +99,6 @@ public void checkCompletion(YormlContext context) { throw new YormlException("Missing one or more explicitly required fields: "+Strings.join(incompleteRequiredFields, ", "), context); } } - public boolean isRequired(String fieldName) { - return Maybe.ofDisallowingNull(fieldsRequired.get(fieldName)).or(false); - } public boolean isFieldDone(String fieldName) { return fieldsDone.contains(fieldName); @@ -73,12 +107,6 @@ public void setFieldDone(String fieldName) { fieldsDone.add(fieldName); } - public void setRequiredIfUnset(String fieldName, Boolean required) { - if (required==null) return; - if (fieldsRequired.get(fieldName)!=null) return; - fieldsRequired.put(fieldName, required); - } - public void setUseDefaultFrom(String fieldName, YormlSerializer explicitField, Object defaultValue) { defaultValueForFieldComesFromSerializer.put(fieldName, explicitField); defaultValueOfField.put(fieldName, defaultValue); @@ -90,4 +118,5 @@ public Maybe getDefault(String fieldName) { if (!defaultValueOfField.containsKey(fieldName)) return Maybe.absent("no default"); return Maybe.of(defaultValueOfField.get(fieldName)); } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java index c28e5b6881..ab8d3ade1d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java @@ -20,9 +20,11 @@ import java.lang.reflect.Field; import java.util.List; +import java.util.Set; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Boxing; import org.apache.brooklyn.util.javalang.FieldOrderings; @@ -31,6 +33,7 @@ import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yorml.Yorml; import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.YormlSerializer; import org.apache.brooklyn.util.yorml.internal.SerializersOnBlackboard; public class InstantiateType extends YormlSerializerComposition { @@ -93,7 +96,9 @@ public void read() { return; } if (!type.equals(context.getExpectedType())) { - SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.getTypeRegistry().getAllSerializers(type)); + Set serializers = MutableSet.of(); + config.typeRegistry.collectSerializers(type, serializers, MutableSet.of()); + SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(serializers); } context.phaseInsert(YormlContext.StandardPhases.MANIPULATING, YormlContext.StandardPhases.HANDLING_FIELDS); } @@ -135,7 +140,9 @@ public void write() { String typeName = config.getTypeRegistry().getTypeName(getJavaObject()); map.put("type", typeName); if (!primitive) { - SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.getTypeRegistry().getAllSerializers(typeName)); + Set serializers = MutableSet.of(); + config.typeRegistry.collectSerializers(typeName, serializers, MutableSet.of()); + SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(serializers); } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java index 3b248a7365..582ad8e2a7 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java @@ -18,77 +18,22 @@ */ package org.apache.brooklyn.util.yorml.tests; +import java.util.List; +import java.util.Set; + +import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.yorml.YormlSerializer; import org.apache.brooklyn.util.yorml.serializers.ExplicitField; import org.apache.brooklyn.util.yorml.tests.YormlBasicTests.Shape; +import org.apache.brooklyn.util.yorml.tests.YormlBasicTests.ShapeWithSize; +import org.testng.Assert; import org.testng.annotations.Test; /** Tests that explicit fields can be set at the outer level in yaml. */ public class ExplicitFieldTests { - /* - -serializers can come from: - * default pre (any?) - * declared type - * calling context - * expected type - * default post - fields in fields and instantiate type - - -- id: java-shape-defaulting-square - type: java:Shape - serializers: - - type: explicit-field - field-name: name - aliases: [ shape-name ] - default: square # alternative to above - definition: # body, yaml, item, content - ... - -- id: shape - type: java:Shape - serializers: - - type: explicit-field - field-name: name - aliases: [ shape-name ] - - type: explicit-field - field-name: color - aliases: [ shape-color ] -- id: yaml-shape-defaulting-square - type: shape - serializers: - - type: explicit-field - field-name: name - default: square - # ensure if shape-name set it overrides 'square' -- id: red-square - type: square - fields: - shape-color: red - # read red-square, but writes { type: shape, name: square, color: red } - # except in context expecting shape it writes { name: square, color: red } - # and in context expecting square it writes { color: red } - - -# sub-type serializers go first, also before expected-type serializers -# but... -# 1) if we read something with shape-name do we do a setField("name", "square") ? -# NO: defaults request RERUN_IF_CHANGED if there are fields to read present, only apply when no fields to read present -# 2) if java fields contains 'name: square' do we write it? -# NO: defaults write to a defaults map if not present -# and field writers don't write if a defaults map contains the default value -# so on write explicit-fields will -# * populate a key in the DEFAULTS map if not present -# and on init -# * keep a list of mangles/aliases -# and on read -# * look up all mangles/aliases once the type is known -# * error if there are multiple mangles/aliases with different values -# (inefficient for that to run multiple times but we'll live with that) - */ - public static YormlSerializer explicitFieldSerializer(String yaml) { return (YormlSerializer) YormlTestFixture.newInstance().read("{ fields: "+yaml+" }", "java:"+ExplicitField.class.getName()).lastReadResult; } @@ -130,10 +75,13 @@ public void testWriteExplicitFieldNoExpectedType() { assertResult( SIMPLE_IN_WITH_TYPE ); } - protected static YormlTestFixture commonExplicitFieldFixtureKeyNameAliasAndDefault() { + protected static YormlTestFixture commonExplicitFieldFixtureKeyNameAlias() { + return commonExplicitFieldFixtureKeyNameAlias(""); + } + protected static YormlTestFixture commonExplicitFieldFixtureKeyNameAlias(String extra) { return YormlTestFixture.newInstance(). addType("shape", Shape.class, MutableList.of( - explicitFieldSerializer("{ fieldName: name, keyName: shape-name, alias: my-name, defaultValue: { type: string, value: bob } }"))); + explicitFieldSerializer("{ fieldName: name, keyName: shape-name, alias: my-name"+extra+" }"))); } static String COMMON_IN_KEY_NAME = "{ shape-name: diamond, fields: { color: black } }"; @@ -141,10 +89,12 @@ protected static YormlTestFixture commonExplicitFieldFixtureKeyNameAliasAndDefau static Shape COMMON_OUT = new Shape().name("diamond").color("black"); static String COMMON_IN_DEFAULT = "{ fields: { color: black } }"; static Shape COMMON_OUT_DEFAULT = new Shape().name("bob").color("black"); + static String COMMON_IN_NO_NAME = "{ fields: { color: black } }"; + static Shape COMMON_OUT_NO_NAME = new Shape().color("black"); @Test public void testCommonKeyName() { - commonExplicitFieldFixtureKeyNameAliasAndDefault(). + commonExplicitFieldFixtureKeyNameAlias(). reading( COMMON_IN_KEY_NAME, "shape" ). writing( COMMON_OUT, "shape" ). doReadWriteAssertingJsonMatch(); @@ -152,35 +102,138 @@ public void testCommonKeyName() { @Test public void testCommonAlias() { - commonExplicitFieldFixtureKeyNameAliasAndDefault(). + commonExplicitFieldFixtureKeyNameAlias(). read( COMMON_IN_ALIAS, "shape" ).assertResult(COMMON_OUT). write( COMMON_OUT, "shape" ).assertResult(COMMON_IN_KEY_NAME); } @Test public void testCommonDefault() { - commonExplicitFieldFixtureKeyNameAliasAndDefault(). + commonExplicitFieldFixtureKeyNameAlias(", defaultValue: { type: string, value: bob }"). reading( COMMON_IN_DEFAULT, "shape" ). writing( COMMON_OUT_DEFAULT, "shape" ). doReadWriteAssertingJsonMatch(); } - protected static YormlTestFixture noKeyNameExplicitFieldFixture() { - return YormlTestFixture.newInstance(). - addType("shape", Shape.class, MutableList.of( - explicitFieldSerializer("{ fieldName: name, keyName: shape-name, aliases: [ my-name ], defaultValue: bob }"))); + @Test + public void testNameNotRequired() { + commonExplicitFieldFixtureKeyNameAlias(). + reading( COMMON_IN_NO_NAME, "shape" ). + writing( COMMON_OUT_NO_NAME, "shape" ). + doReadWriteAssertingJsonMatch(); + } + + @Test + public void testNameRequired() { + try { + YormlTestFixture x = commonExplicitFieldFixtureKeyNameAlias(", required: true") + .read( COMMON_IN_NO_NAME, "shape" ); + Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "name", "required"); + } + } + + @Test + public void testAliasConflictNiceError() { + try { + YormlTestFixture x = commonExplicitFieldFixtureKeyNameAlias().read( + "{ my-name: name-from-alias, shape-name: name-from-key }", "shape" ); + Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "name-from-alias", "my-name", "name-from-key"); + } + } + + protected static YormlTestFixture extended0ExplicitFieldFixture(List extras) { + return commonExplicitFieldFixtureKeyNameAlias(", defaultValue: { type: string, value: bob }"). + addType("shape-with-size", "{ type: \"java:"+ShapeWithSize.class.getName()+"\", interfaceTypes: [ shape ] }", + MutableList.copyOf(extras).append(explicitFieldSerializer("{ fieldName: size, alias: shape-size }")) ); } + + protected static YormlTestFixture extended1ExplicitFieldFixture() { + return extended0ExplicitFieldFixture( MutableList.of( + explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name }")) ); + } + + @Test + public void testExplicitFieldSerializersAreCollected() { + YormlTestFixture ytc = extended1ExplicitFieldFixture(); + Set serializers = MutableSet.of(); + ytc.tr.collectSerializers("shape-with-size", serializers, MutableSet.of()); + Assert.assertEquals(serializers.size(), 3, "Wrong serializers: "+serializers); + } + + String EXTENDED_IN_1 = "{ type: shape-with-size, shape-w-size-name: diamond, size: 2, fields: { color: black } }"; + Object EXTENDED_OUT_1 = new ShapeWithSize().size(2).name("diamond").color("black"); + + @Test + public void testExtendedKeyNameIsUsed() { + extended1ExplicitFieldFixture(). + reading( EXTENDED_IN_1, null ). + writing( EXTENDED_OUT_1, "shape"). + doReadWriteAssertingJsonMatch(); + } + + @Test + public void testInheritedAliasIsUsed() { + String json = "{ type: shape-with-size, my-name: diamond, size: 2, fields: { color: black } }"; + extended1ExplicitFieldFixture(). + read( json, null ).assertResult( EXTENDED_OUT_1 ). + write( EXTENDED_OUT_1, "shape-w-size" ).assertResult(EXTENDED_IN_1); + } + + String EXTENDED_IN_ORIGINAL_KEYNAME = "{ type: shape-with-size, shape-name: diamond, size: 2, fields: { color: black } }"; + + @Test + public void testOverriddenKeyNameNotUsed() { + try { + YormlTestFixture x = extended1ExplicitFieldFixture().read(EXTENDED_IN_ORIGINAL_KEYNAME, null); + Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "shape-name", "diamond"); + } + } + + String EXTENDED_TYPEDEF_NEW_ALIAS = "{ fieldName: name, alias: new-name }"; + + @Test + public void testInheritedKeyNameIsUsed() { + extended0ExplicitFieldFixture( MutableList.of( + explicitFieldSerializer(EXTENDED_TYPEDEF_NEW_ALIAS)) ) + .read(EXTENDED_IN_ORIGINAL_KEYNAME, null).assertResult(EXTENDED_OUT_1) + .write(EXTENDED_OUT_1).assertResult(EXTENDED_IN_ORIGINAL_KEYNAME); + } + + @Test + public void testOverriddenAliasIsRecognised() { + String json = "{ type: shape-with-size, new-name: diamond, size: 2, fields: { color: black } }"; + extended0ExplicitFieldFixture( MutableList.of( + explicitFieldSerializer(EXTENDED_TYPEDEF_NEW_ALIAS)) ) + .read( json, null ).assertResult( EXTENDED_OUT_1 ) + .write( EXTENDED_OUT_1, "shape-w-size" ).assertResult(EXTENDED_IN_ORIGINAL_KEYNAME); + } + + String EXTENDED_TYPEDEF_NEW_DEFAULT = "{ fieldName: name, defaultValue: { type: string, value: bob } }"; + Object EXTENDED_OUT_NEW_DEFAULT = new ShapeWithSize().size(2).name("bob").color("black"); + + @Test + public void testInheritedKeyNameIsUsedWithNewDefault() { + String json = "{ size: 2, fields: { color: black } }"; + extended0ExplicitFieldFixture( MutableList.of( + explicitFieldSerializer(EXTENDED_TYPEDEF_NEW_DEFAULT)) ) + .write(EXTENDED_OUT_NEW_DEFAULT, "shape-with-size").assertResult(json) + .read(json, "shape-with-size").assertResult(EXTENDED_OUT_NEW_DEFAULT); + } + // TODO - // aliases-inherited - // aliases-exclude-name + // aliases-exclude-name true/false // no-mangle /* * TODO sketch for phases * TODO tests for: - * aliases * aliases inherited - * setting a defaultValue, including null * if missing required required * * then FIX diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java index d2ec315eff..965d021d64 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java @@ -18,11 +18,14 @@ */ package org.apache.brooklyn.util.yorml.tests; +import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Boxing; import org.apache.brooklyn.util.javalang.Reflections; @@ -39,17 +42,19 @@ public class MockYormlTypeRegistry implements YormlTypeRegistry { static class MockRegisteredType { final String id; final String parentType; + final Set interfaceTypes; final Class javaType; final List serializers; final Object yamlDefinition; - public MockRegisteredType(String id, String parentType, Class javaType, List serializers, Object yamlDefinition) { + public MockRegisteredType(String id, String parentType, Class javaType, Collection interfaceTypes, List serializers, Object yamlDefinition) { super(); this.id = id; this.parentType = parentType; this.javaType = javaType; - this.serializers = serializers; + this.interfaceTypes = MutableSet.copyOf(interfaceTypes); + this.serializers = MutableList.copyOf(serializers); this.yamlDefinition = yamlDefinition; } } @@ -107,24 +112,28 @@ public void put(String typeName, Class javaType) { put(typeName, javaType, null); } public void put(String typeName, Class javaType, List serializers) { - types.put(typeName, new MockRegisteredType(typeName, "java:"+javaType.getName(), javaType, serializers, null)); + types.put(typeName, new MockRegisteredType(typeName, "java:"+javaType.getName(), javaType, MutableSet.of(), serializers, null)); } /** takes a simplified yaml definition supporting a map with a key `type` and optionally other keys */ public void put(String typeName, String yamlDefinition) { put(typeName, yamlDefinition, null); } + @SuppressWarnings("unchecked") public void put(String typeName, String yamlDefinition, List serializers) { Object yamlObject = Iterables.getOnlyElement( Yamls.parseAll(yamlDefinition) ); if (!(yamlObject instanceof Map)) throw new IllegalArgumentException("Mock only supports map definitions"); - Object type = ((Map)yamlObject).get("type"); + + Object type = ((Map)yamlObject).remove("type"); if (!(type instanceof String)) throw new IllegalArgumentException("Mock requires key `type` with string value"); - ((Map)yamlObject).remove("type"); - if (((Map)yamlObject).isEmpty()) yamlObject = null; + Class javaType = getJavaType((String)type); if (javaType==null) throw new IllegalArgumentException("Mock cannot resolve parent type `"+type+"` in definition of `"+typeName+"`"); - types.put(typeName, new MockRegisteredType(typeName, (String)type, javaType, serializers, yamlObject)); + Object interfaceTypes = ((Map)yamlObject).remove("interfaceTypes"); + if (((Map)yamlObject).isEmpty()) yamlObject = null; + + types.put(typeName, new MockRegisteredType(typeName, (String)type, javaType, (Collection)interfaceTypes, serializers, yamlObject)); } @Override @@ -150,9 +159,18 @@ protected String getDefaultTypeNameOfClass(Class type) { } @Override - public Iterable getAllSerializers(String typeName) { + public void collectSerializers(String typeName, Collection serializers, Set typesVisited) { + if (typeName==null || !typesVisited.add(typeName)) return; MockRegisteredType rt = types.get(typeName); - if (rt==null || rt.serializers==null) return MutableList.of(); - return rt.serializers; + if (rt==null || rt.serializers==null) return; + serializers.addAll(rt.serializers); + if (rt.parentType!=null) { + collectSerializers(rt.parentType, serializers, typesVisited); + } + if (rt.interfaceTypes!=null) { + for (String interfaceType: rt.interfaceTypes) { + collectSerializers(interfaceType, serializers, typesVisited); + } + } } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java index 64ed5fde7e..c2d5f09fb7 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java @@ -47,6 +47,10 @@ public boolean equals(Object xo) { Shape x = (Shape) xo; return Objects.equal(name, x.name) && Objects.equal(color, x.color); } + @Override + public String toString() { + return Objects.toStringHelper(this).add("name", name).add("color", color).omitNullValues().toString(); + } public Shape name(String name) { this.name = name; return this; } public Shape color(String color) { this.color = color; return this; } @@ -167,6 +171,11 @@ public boolean equals(Object xo) { public ShapeWithSize size(int size) { this.size = size; return this; } public ShapeWithSize name(String name) { return (ShapeWithSize)super.name(name); } public ShapeWithSize color(String color) { return (ShapeWithSize)super.color(color); } + + @Override + public String toString() { + return Objects.toStringHelper(this).add("name", name).add("color", color).add("size", size).omitNullValues().toString(); + } } @Test From e51310efc259c5e5b1186ec54f06787eaba16fdc Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 27 Jun 2016 17:18:54 +0100 Subject: [PATCH 12/77] explicit-field alias control: inherit and exclude field name --- .../util/yorml/serializers/ExplicitField.java | 31 +++++++++--- .../serializers/ExplicitFieldsBlackboard.java | 25 ++++++++-- .../util/yorml/tests/ExplicitFieldTests.java | 49 ++++++++++++++----- 3 files changed, 85 insertions(+), 20 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java index 1988b97d33..bfa4779a31 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java @@ -40,17 +40,32 @@ protected YormlSerializerWorker newWorker() { return new Worker(); } + /** field in java class to read/write */ protected String fieldName; - protected String fieldType; + // not used at present, but would simplify expressing default values + // TODO we could also conceivably infer the expected field type better +// protected String fieldType; + + /** key to write at root in yaml */ protected String keyName; + /** convenience if supplying a single item in {@link #aliases} */ protected String alias; + /** aliases to recognise at root in yaml when reading, in addition to {@link #keyName} and normally {@link #fieldName} */ protected List aliases; - transient MutableSet keyNameAndAliases; - + /** by default when multiple explicit-field serializers are supplied for the same {@link #fieldName}, all aliases are accepted; + * set this false to restrict to those in the first such serializer */ + Boolean aliasesInherited; + /** by default the {@link #fieldName} is recognised as an alias; + * set false to allow only explicit {@link #keyName} and {@link #aliases} */ + Boolean aliasesExcludeFieldName; + /** by default fields can be left null; set true to require a value to be supplied (or a default set) */ Boolean required; + + /** a default value to use when reading (and to use to determine whether to omit the field when writing) */ // TODO would be nice to support maybe here, not hard here, but it makes it hard to set from yaml + // also keyword `default` as alias Object defaultValue; public class Worker extends YormlSerializerWorker { @@ -66,7 +81,9 @@ protected String getPreferredKeyName() { protected Iterable getKeyNameAndAliases() { MutableSet keyNameAndAliases = MutableSet.of(); keyNameAndAliases.addIfNotNull(ExplicitFieldsBlackboard.get(blackboard).getKeyName(fieldName)); - keyNameAndAliases.addIfNotNull(fieldName); + if (!ExplicitFieldsBlackboard.get(blackboard).isAliasesExcludingFieldName(fieldName)) { + keyNameAndAliases.addIfNotNull(fieldName); + } keyNameAndAliases.addAll(ExplicitFieldsBlackboard.get(blackboard).getAliases(fieldName)); return keyNameAndAliases; } @@ -84,8 +101,10 @@ protected boolean readyForMainEvent() { if (context.isPhase(PREPARING_EXPLICIT_FIELDS)) { // do the pre-main pass to determine what is required for explicit fields and what the default is ExplicitFieldsBlackboard.get(blackboard).setKeyNameIfUnset(fieldName, keyName); - ExplicitFieldsBlackboard.get(blackboard).addAlias(fieldName, alias); - ExplicitFieldsBlackboard.get(blackboard).addAliases(fieldName, aliases); + ExplicitFieldsBlackboard.get(blackboard).addAliasIfNotDisinherited(fieldName, alias); + ExplicitFieldsBlackboard.get(blackboard).addAliasesIfNotDisinherited(fieldName, aliases); + ExplicitFieldsBlackboard.get(blackboard).setAliasesInheritedIfUnset(fieldName, aliasesInherited); + ExplicitFieldsBlackboard.get(blackboard).setAliasesExcludeFieldNameIfUnset(fieldName, aliasesExcludeFieldName); ExplicitFieldsBlackboard.get(blackboard).setRequiredIfUnset(fieldName, required); if (ExplicitFieldsBlackboard.get(blackboard).getDefault(fieldName).isAbsent() && defaultValue!=null) { ExplicitFieldsBlackboard.get(blackboard).setUseDefaultFrom(fieldName, ExplicitField.this, defaultValue); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java index abca5f57e2..7697847cec 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java @@ -47,6 +47,8 @@ public static ExplicitFieldsBlackboard get(Map blackboard) { } private final Map keyNames = MutableMap.of(); + private final Map aliasesInheriteds = MutableMap.of(); + private final Map aliasesExcludesFieldName = MutableMap.of(); private final Map> aliases = MutableMap.of(); private final Set fieldsDone = MutableSet.of(); private final Map fieldsRequired = MutableMap.of(); @@ -61,10 +63,27 @@ public void setKeyNameIfUnset(String fieldName, String keyName) { if (keyNames.get(fieldName)!=null) return; keyNames.put(fieldName, keyName); } - public void addAlias(String fieldName, String alias) { - addAliases(fieldName, MutableList.of(alias)); + public void setAliasesInheritedIfUnset(String fieldName, Boolean aliasesInherited) { + if (aliasesInherited==null) return; + if (aliasesInheriteds.get(fieldName)!=null) return; + aliasesInheriteds.put(fieldName, aliasesInherited); } - public void addAliases(String fieldName, List aliases) { + public boolean isAliasesExcludingFieldName(String fieldName) { + return Boolean.TRUE.equals(aliasesExcludesFieldName.get(fieldName)); + } + public void setAliasesExcludeFieldNameIfUnset(String fieldName, Boolean aliasesExcludeFieldName) { + if (aliasesExcludeFieldName==null) return; + if (aliasesExcludesFieldName.get(fieldName)!=null) return; + aliasesExcludesFieldName.put(fieldName, aliasesExcludeFieldName); + } + public void addAliasIfNotDisinherited(String fieldName, String alias) { + addAliasesIfNotDisinherited(fieldName, MutableList.of(alias)); + } + public void addAliasesIfNotDisinherited(String fieldName, List aliases) { + if (Boolean.FALSE.equals(aliasesInheriteds.get(fieldName))) { + // no longer heritable + return; + } Set aa = this.aliases.get(fieldName); if (aa==null) { aa = MutableSet.of(); diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java index 582ad8e2a7..63a9de2d8b 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java @@ -226,19 +226,46 @@ public void testInheritedKeyNameIsUsedWithNewDefault() { .read(json, "shape-with-size").assertResult(EXTENDED_OUT_NEW_DEFAULT); } + @Test + public void testInheritedAliasIsNotUsedIfRestricted() { + // same as testInheritedAliasIsUsed -- except fails because we say aliases-inherited: false + String json = "{ type: shape-with-size, my-name: diamond, size: 2, fields: { color: black } }"; + try { + YormlTestFixture x = extended0ExplicitFieldFixture( MutableList.of( + explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name, aliasesInherited: false }")) ) + .read( json, null ); + Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "my-name", "diamond"); + } + } + + @Test + public void testFieldNameAsAlias() { + String json = "{ type: shape-with-size, name: diamond, size: 2, fields: { color: black } }"; + extended0ExplicitFieldFixture( MutableList.of( + explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name }")) ) + .read( json, null ).assertResult( EXTENDED_OUT_1 ) + .write( EXTENDED_OUT_1 ).assertResult( EXTENDED_IN_1 ); + } + + @Test + public void testFieldNameAsAliasExcluded() { + String json = "{ type: shape-with-size, name: diamond, size: 2, fields: { color: black } }"; + try { + YormlTestFixture x = extended0ExplicitFieldFixture( MutableList.of( + explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name, aliasesExcludeFieldName: true }")) ) + .read( json, null ); + Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "name", "diamond"); + } + } - // TODO - // aliases-exclude-name true/false - // no-mangle /* - * TODO sketch for phases - * TODO tests for: - * aliases inherited - * if missing required required - * - * then FIX - * type: my-serializer as singleton map in list - * using serializers declared on ancestor above immediate + * TODO + * mangle/no-mangle + * maps/etc */ } From dc65327d0f8409713891ce49df4374175aa92eca Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 27 Jun 2016 18:15:53 +0100 Subject: [PATCH 13/77] name mangling for explicit-field, and tidies around constraint --- .../util/yorml/serializers/ExplicitField.java | 60 +++++++++++-------- .../serializers/ExplicitFieldsBlackboard.java | 36 +++++------ .../serializers/YamlKeysOnBlackboard.java | 16 +++++ .../YormlSerializerComposition.java | 14 +++++ .../org/apache/brooklyn/util/yorml/sketch.md | 24 ++++---- .../util/yorml/tests/ExplicitFieldTests.java | 32 +++++++--- 6 files changed, 121 insertions(+), 61 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java index bfa4779a31..c9a50cb25d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java @@ -18,8 +18,10 @@ */ package org.apache.brooklyn.util.yorml.serializers; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; @@ -57,11 +59,16 @@ protected YormlSerializerWorker newWorker() { /** by default when multiple explicit-field serializers are supplied for the same {@link #fieldName}, all aliases are accepted; * set this false to restrict to those in the first such serializer */ Boolean aliasesInherited; - /** by default the {@link #fieldName} is recognised as an alias; - * set false to allow only explicit {@link #keyName} and {@link #aliases} */ - Boolean aliasesExcludeFieldName; - /** by default fields can be left null; set true to require a value to be supplied (or a default set) */ - Boolean required; + /** by default aliases are taken case-insensitive, with mangling supported, + * and including the {@link #fieldName} as an alias; + * set false to disallow all these, recognising only the explicitly noted + * {@link #keyName} and {@link #aliases} as keys (but still defaulting to {@link #fieldName} if {@link #keyName} is absent) */ + Boolean aliasesStrict; + + public static enum FieldConstraint { REQUIRED } + /** by default fields can be left null; set {@link FieldConstraint#REQUIRED} to require a value to be supplied (or a default set); + * other constraints may be introduded, and API may change, but keyword `required` will be coercible to this */ + FieldConstraint constraint; /** a default value to use when reading (and to use to determine whether to omit the field when writing) */ // TODO would be nice to support maybe here, not hard here, but it makes it hard to set from yaml @@ -80,8 +87,8 @@ protected String getPreferredKeyName() { protected Iterable getKeyNameAndAliases() { MutableSet keyNameAndAliases = MutableSet.of(); - keyNameAndAliases.addIfNotNull(ExplicitFieldsBlackboard.get(blackboard).getKeyName(fieldName)); - if (!ExplicitFieldsBlackboard.get(blackboard).isAliasesExcludingFieldName(fieldName)) { + keyNameAndAliases.addIfNotNull(getPreferredKeyName()); + if (!ExplicitFieldsBlackboard.get(blackboard).isAliasesStrict(fieldName)) { keyNameAndAliases.addIfNotNull(fieldName); } keyNameAndAliases.addAll(ExplicitFieldsBlackboard.get(blackboard).getAliases(fieldName)); @@ -104,8 +111,8 @@ protected boolean readyForMainEvent() { ExplicitFieldsBlackboard.get(blackboard).addAliasIfNotDisinherited(fieldName, alias); ExplicitFieldsBlackboard.get(blackboard).addAliasesIfNotDisinherited(fieldName, aliases); ExplicitFieldsBlackboard.get(blackboard).setAliasesInheritedIfUnset(fieldName, aliasesInherited); - ExplicitFieldsBlackboard.get(blackboard).setAliasesExcludeFieldNameIfUnset(fieldName, aliasesExcludeFieldName); - ExplicitFieldsBlackboard.get(blackboard).setRequiredIfUnset(fieldName, required); + ExplicitFieldsBlackboard.get(blackboard).setAliasesStrictIfUnset(fieldName, aliasesStrict); + ExplicitFieldsBlackboard.get(blackboard).setConstraintIfUnset(fieldName, constraint); if (ExplicitFieldsBlackboard.get(blackboard).getDefault(fieldName).isAbsent() && defaultValue!=null) { ExplicitFieldsBlackboard.get(blackboard).setUseDefaultFrom(fieldName, ExplicitField.this, defaultValue); } @@ -131,21 +138,25 @@ public void read() { if (fields==null) return; int keysMatched = 0; - for (String alias: getKeyNameAndAliases()) { - Maybe value = peekFromYamlKeysOnBlackboard(alias, Object.class); - if (value.isAbsent()) continue; - boolean fieldAlreadyKnown = fields.containsKey(fieldName); - if (value.isPresent() && fieldAlreadyKnown) { - // already present - if (!Objects.equal(value.get(), fields.get(fieldName))) { - throw new IllegalStateException("Cannot set '"+fieldName+"' to '"+value.get()+"' supplied in '"+alias+"' because this conflicts with '"+fields.get(fieldName)+"' already set"); + for (String aliasO: getKeyNameAndAliases()) { + Set aliasMangles = ExplicitFieldsBlackboard.get(blackboard).isAliasesStrict(fieldName) ? + Collections.singleton(aliasO) : findAllKeyManglesYamlKeys(aliasO); + for (String alias: aliasMangles) { + Maybe value = peekFromYamlKeysOnBlackboard(alias, Object.class); + if (value.isAbsent()) continue; + boolean fieldAlreadyKnown = fields.containsKey(fieldName); + if (value.isPresent() && fieldAlreadyKnown) { + // already present + if (!Objects.equal(value.get(), fields.get(fieldName))) { + throw new IllegalStateException("Cannot set '"+fieldName+"' to '"+value.get()+"' supplied in '"+alias+"' because this conflicts with '"+fields.get(fieldName)+"' already set"); + } + continue; } - continue; + // value present, field not yet handled + removeFromYamlKeysOnBlackboard(alias); + fields.put(fieldName, value.get()); + keysMatched++; } - // value present, field not yet handled - removeFromYamlKeysOnBlackboard(alias); - fields.put(fieldName, value.get()); - keysMatched++; } if (keysMatched==0) { // set a default if there is one @@ -174,8 +185,9 @@ public void write() { Maybe valueToSet; if (!fields.containsKey(fieldName)) { - // field not present, so null (or not known) - if ((dv.isPresent() && dv.isNull()) || (!ExplicitFieldsBlackboard.get(blackboard).isRequired(fieldName) && dv.isAbsent())) { + // field not present, so omit (if field is not required and no default, or if default value is present and null) + // else write an explicit null + if ((dv.isPresent() && dv.isNull()) || (ExplicitFieldsBlackboard.get(blackboard).getConstraint(fieldName).orNull()!=FieldConstraint.REQUIRED && dv.isAbsent())) { // if default is null, or if not required and no default, we can suppress ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); return; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java index 7697847cec..91ff410857 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java @@ -32,6 +32,7 @@ import org.apache.brooklyn.util.yorml.YormlException; import org.apache.brooklyn.util.yorml.YormlRequirement; import org.apache.brooklyn.util.yorml.YormlSerializer; +import org.apache.brooklyn.util.yorml.serializers.ExplicitField.FieldConstraint; public class ExplicitFieldsBlackboard implements YormlRequirement { @@ -48,10 +49,10 @@ public static ExplicitFieldsBlackboard get(Map blackboard) { private final Map keyNames = MutableMap.of(); private final Map aliasesInheriteds = MutableMap.of(); - private final Map aliasesExcludesFieldName = MutableMap.of(); + private final Map aliasesStricts = MutableMap.of(); private final Map> aliases = MutableMap.of(); private final Set fieldsDone = MutableSet.of(); - private final Map fieldsRequired = MutableMap.of(); + private final Map fieldsConstraints = MutableMap.of(); private final Map defaultValueForFieldComesFromSerializer = MutableMap.of(); private final Map defaultValueOfField = MutableMap.of(); @@ -68,13 +69,13 @@ public void setAliasesInheritedIfUnset(String fieldName, Boolean aliasesInherite if (aliasesInheriteds.get(fieldName)!=null) return; aliasesInheriteds.put(fieldName, aliasesInherited); } - public boolean isAliasesExcludingFieldName(String fieldName) { - return Boolean.TRUE.equals(aliasesExcludesFieldName.get(fieldName)); + public boolean isAliasesStrict(String fieldName) { + return Boolean.TRUE.equals(aliasesStricts.get(fieldName)); } - public void setAliasesExcludeFieldNameIfUnset(String fieldName, Boolean aliasesExcludeFieldName) { - if (aliasesExcludeFieldName==null) return; - if (aliasesExcludesFieldName.get(fieldName)!=null) return; - aliasesExcludesFieldName.put(fieldName, aliasesExcludeFieldName); + public void setAliasesStrictIfUnset(String fieldName, Boolean aliasesStrict) { + if (aliasesStrict==null) return; + if (aliasesStricts.get(fieldName)!=null) return; + aliasesStricts.put(fieldName, aliasesStrict); } public void addAliasIfNotDisinherited(String fieldName, String alias) { addAliasesIfNotDisinherited(fieldName, MutableList.of(alias)); @@ -98,20 +99,21 @@ public Collection getAliases(String fieldName) { return aa; } - public boolean isRequired(String fieldName) { - return Maybe.ofDisallowingNull(fieldsRequired.get(fieldName)).or(false); + public Maybe getConstraint(String fieldName) { + return Maybe.ofDisallowingNull(fieldsConstraints.get(fieldName)); } - public void setRequiredIfUnset(String fieldName, Boolean required) { - if (required==null) return; - if (fieldsRequired.get(fieldName)!=null) return; - fieldsRequired.put(fieldName, required); + public void setConstraintIfUnset(String fieldName, FieldConstraint constraint) { + if (constraint==null) return; + if (fieldsConstraints.get(fieldName)!=null) return; + fieldsConstraints.put(fieldName, constraint); } @Override public void checkCompletion(YormlContext context) { List incompleteRequiredFields = MutableList.of(); - for (Map.Entry fieldRequired: fieldsRequired.entrySet()) { - if (fieldRequired.getValue() && !fieldsDone.contains(fieldRequired.getKey())) { - incompleteRequiredFields.add(fieldRequired.getKey()); + for (Map.Entry fieldConstraint: fieldsConstraints.entrySet()) { + FieldConstraint v = fieldConstraint.getValue(); + if (v!=null && FieldConstraint.REQUIRED==v && !fieldsDone.contains(fieldConstraint.getKey())) { + incompleteRequiredFields.add(fieldConstraint.getKey()); } } if (!incompleteRequiredFields.isEmpty()) { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java index d2c40b67b8..892f3496f9 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java @@ -20,10 +20,13 @@ import java.util.Map; +import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yorml.YormlContext; import org.apache.brooklyn.util.yorml.YormlException; import org.apache.brooklyn.util.yorml.YormlRequirement; +import com.google.common.annotations.Beta; + /** Keys from a YAML map that still need to be handled */ public class YamlKeysOnBlackboard implements YormlRequirement { @@ -54,4 +57,17 @@ public void checkCompletion(YormlContext context) { throw new YormlException("Incomplete read of YAML keys: "+yamlKeysToReadToJava, context); } } + + /** true iff k1 and k2 are case-insensitively equal after removing all - and _. + * Note that the definition of mangling may change. + * TODO it should be stricter so that "ab" and "a-b" don't match but "aB" and "a-b" and "a_b" do */ + @Beta + public static boolean mangleable(String k1, String k2) { + if (k1==null || k2==null) return k1==k2; + k1 = Strings.replaceAllNonRegex(k1, "-", ""); + k1 = Strings.replaceAllNonRegex(k1, "_", ""); + k2 = Strings.replaceAllNonRegex(k2, "-", ""); + k2 = Strings.replaceAllNonRegex(k2, "_", ""); + return k1.toLowerCase().equals(k2.toLowerCase()); + } } \ No newline at end of file diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java index 104252b163..9e44d83506 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java @@ -19,7 +19,9 @@ package org.apache.brooklyn.util.yorml.serializers; import java.util.Map; +import java.util.Set; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; @@ -106,6 +108,18 @@ protected void removeFromYamlKeysOnBlackboard(String key) { YamlKeysOnBlackboard ykb = YamlKeysOnBlackboard.peek(blackboard); ykb.yamlKeysToReadToJava.remove(key); } + /** looks for all keys in {@link YamlKeysOnBlackboard} which can be mangled/ignore-case + * to match the given key */ + protected Set findAllKeyManglesYamlKeys(String targetKey) { + Set result = MutableSet.of(); + YamlKeysOnBlackboard ykb = YamlKeysOnBlackboard.peek(blackboard); + for (Object k: ykb.yamlKeysToReadToJava.keySet()) { + if (k instanceof String && YamlKeysOnBlackboard.mangleable(targetKey, (String)k)) { + result.add((String)k); + } + } + return result; + } public abstract void read(); public abstract void write(); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md index 4659e6d679..b66400212c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md @@ -230,19 +230,19 @@ parameter, field-name, and several optional ones, so a sample usage might look l ``` - type: explicit-field field-name: color - key-name: color # this is used in yaml - aliases: [ colour ] # things to accept in yaml as synonyms for key-name; `alias` also accepted - disable-default-aliases: boolean # if true, means only exact matches on key-name and aliases are accepted, otherwise a set of mangles are applied - field-type: string # inferred from java field, but you can constrain further to yaml types - constraint: required # currently just supports 'required' (and 'null' not allowed) or blank for none (default), but reserved for future use + key-name: color # this is used in yaml + aliases: [ colour ] # things to accept in yaml as synonyms for key-name; `alias` also accepted + aliases-strict: false # if true, means only exact matches on key-name and aliases are accepted, otherwise a set of mangles are applied + aliases-inherited: true # if false, means only take aliases from the first explicit-field serializer for this field-name, otherwise any can be used + # TODO items below here are still WIP/planned + field-type: string # inferred from java field, but you can constrain further to yaml types + constraint: required # currently just supports 'required' (and 'null' not allowed) or blank for none (default), but reserved for future use description: The color of the shape # text (markdown) - serialization: # optional additional serialization instructions for this field - - if-string: # (defined below) + serialization: # optional additional serialization instructions for this field + - if-string: # (defined below) set-key: field-name ``` -XXX - ### On overloading (really can skip!) @@ -478,6 +478,7 @@ in detail; see implementation notes below. ## Even more further behaviours (not part of MVP) +* preventing fields from being set * type overloading, if string, if number, if map, if list... inferring type, or setting diff fields * super-types and abstract types (underlying java of `supertypes` must be assignable from underying java of `type`) * merging ... deep? field-based? @@ -517,10 +518,9 @@ to be shown. ### TODO -* explicit-field aliases -* no-others / suppression (including fixing this doc) -* complex syntax, type as key, etc +* Config/data keys * maps and lists, with generics +* complex syntax, type as key, etc * defining serializers and linking to brooklyn diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java index 63a9de2d8b..b9f032c3b7 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java @@ -126,7 +126,7 @@ public void testNameNotRequired() { @Test public void testNameRequired() { try { - YormlTestFixture x = commonExplicitFieldFixtureKeyNameAlias(", required: true") + YormlTestFixture x = commonExplicitFieldFixtureKeyNameAlias(", constraint: required") .read( COMMON_IN_NO_NAME, "shape" ); Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); } catch (Exception e) { @@ -250,11 +250,11 @@ public void testFieldNameAsAlias() { } @Test - public void testFieldNameAsAliasExcluded() { + public void testFieldNameAsAliasExcludedWhenStrict() { String json = "{ type: shape-with-size, name: diamond, size: 2, fields: { color: black } }"; try { YormlTestFixture x = extended0ExplicitFieldFixture( MutableList.of( - explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name, aliasesExcludeFieldName: true }")) ) + explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name, aliasesStrict: true }")) ) .read( json, null ); Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); } catch (Exception e) { @@ -262,10 +262,26 @@ public void testFieldNameAsAliasExcluded() { } } - /* - * TODO - * mangle/no-mangle - * maps/etc - */ + String EXTENDED_IN_1_MANGLED = "{ type: shape-with-size, shapeWSize_Name: diamond, size: 2, fields: { color: black } }"; + @Test + public void testFieldNameMangled() { + extended0ExplicitFieldFixture( MutableList.of( + explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name }")) ) + .read( EXTENDED_IN_1_MANGLED, null ).assertResult( EXTENDED_OUT_1 ) + .write( EXTENDED_OUT_1 ).assertResult( EXTENDED_IN_1 ); + } + + @Test + public void testFieldNameManglesExcludedWhenStrict() { + try { + YormlTestFixture x = extended0ExplicitFieldFixture( MutableList.of( + explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name, aliasesStrict: true }")) ) + .read( EXTENDED_IN_1_MANGLED, null ); + Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "shapeWSize_Name", "diamond"); + } + } + } From 493deadb13f3f708410f8ea4944c8d3e98a421f2 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 28 Jun 2016 17:38:43 +0100 Subject: [PATCH 14/77] support lists/sets, and tidy --- .../org/apache/brooklyn/util/yorml/Yorml.java | 14 +- .../brooklyn/util/yorml/YormlContext.java | 2 + .../util/yorml/internal/YormlUtils.java | 132 +++++++ .../yorml/serializers/InstantiateType.java | 177 --------- .../InstantiateTypeFromRegistry.java | 108 +++++ .../serializers/InstantiateTypeList.java | 372 ++++++++++++++++++ .../serializers/InstantiateTypePrimitive.java | 108 +++++ .../InstantiateTypeWorkerAbstract.java | 130 ++++++ .../serializers/YamlKeysOnBlackboard.java | 24 +- .../YormlSerializerComposition.java | 9 +- .../org/apache/brooklyn/util/yorml/sketch.md | 2 +- .../util/yorml/tests/MapListTests.java | 102 +++++ .../yorml/tests/MockYormlTypeRegistry.java | 9 +- .../util/yorml/tests/YormlBasicTests.java | 6 + .../util/yorml/tests/YormlTestFixture.java | 5 +- 15 files changed, 994 insertions(+), 206 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java delete mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypePrimitive.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java index 48bb5e63b9..9fc8b3b888 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java @@ -24,7 +24,9 @@ import org.apache.brooklyn.util.yorml.internal.YormlConfig; import org.apache.brooklyn.util.yorml.internal.YormlConverter; import org.apache.brooklyn.util.yorml.serializers.FieldsInMapUnderFields; -import org.apache.brooklyn.util.yorml.serializers.InstantiateType; +import org.apache.brooklyn.util.yorml.serializers.InstantiateTypeFromRegistry; +import org.apache.brooklyn.util.yorml.serializers.InstantiateTypeList; +import org.apache.brooklyn.util.yorml.serializers.InstantiateTypePrimitive; public class Yorml { @@ -40,10 +42,12 @@ public static Yorml newInstance(YormlConfig config) { public static Yorml newInstance(YormlTypeRegistry typeRegistry) { return newInstance(typeRegistry, MutableList.of( new FieldsInMapUnderFields(), - new InstantiateType() )); + new InstantiateTypePrimitive(), + new InstantiateTypeList(), + new InstantiateTypeFromRegistry() )); } - public static Yorml newInstance(YormlTypeRegistry typeRegistry, List serializers) { + private static Yorml newInstance(YormlTypeRegistry typeRegistry, List serializers) { YormlConfig config = new YormlConfig(); config.typeRegistry = typeRegistry; config.serializersPost.addAll(serializers); @@ -51,6 +55,10 @@ public static Yorml newInstance(YormlTypeRegistry typeRegistry, List)o)) { + if (!isPureJson(oi)) return false; + } + return true; + } + if (o instanceof Map) { + for (Map.Entry oi: ((Map)o).entrySet()) { + if (!(oi.getKey() instanceof String)) return false; + if (!isPureJson(oi.getValue())) return false; + } + } + return false; + } + } + + public static class GenericsParse { + public String warning; + public boolean isGeneric = false; + public String baseType; + public List subTypes = MutableList.of(); + + public GenericsParse(String type) { + if (type==null) return; + + baseType = type.trim(); + int genericStart = baseType.indexOf('<'); + if (genericStart > 0) { + isGeneric = true; + + if (!parse(baseType.substring(genericStart))) { + warning = "Invalid generic type "+baseType; + return; + } + + baseType = baseType.substring(0, genericStart); + } + } + + private boolean parse(String s) { + int depth = 0; + boolean inWord = false; + int lastWordStart = -1; + for (int i=0; i') { + if (c==',' && depth==0) return false; + if (c=='>') { depth--; } + if (depth>1) continue; + // depth 1 word end, either due to , or due to > + if (c==',' && !inWord) return false; + subTypes.add(s.substring(lastWordStart, i).trim()); + inWord = false; + continue; + } + if (!inWord) { + if (depth!=1) return false; + inWord = true; + lastWordStart = i; + } + } + // finished. expect depth 0 and not in word + return depth==0 && !inWord; + } + + public boolean isGeneric() { return isGeneric; } + public int subTypeCount() { return subTypes.size(); } + } +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java deleted file mode 100644 index ab8d3ade1d..0000000000 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateType.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.brooklyn.util.yorml.serializers; - -import java.lang.reflect.Field; -import java.util.List; -import java.util.Set; - -import org.apache.brooklyn.util.collections.MutableList; -import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.collections.MutableSet; -import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.javalang.Boxing; -import org.apache.brooklyn.util.javalang.FieldOrderings; -import org.apache.brooklyn.util.javalang.ReflectionPredicates; -import org.apache.brooklyn.util.javalang.Reflections; -import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yorml.Yorml; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.YormlSerializer; -import org.apache.brooklyn.util.yorml.internal.SerializersOnBlackboard; - -public class InstantiateType extends YormlSerializerComposition { - - protected YormlSerializerWorker newWorker() { - return new Worker(); - } - - public static class Worker extends YormlSerializerWorker { - public void read() { - if (!context.isPhase(YormlContext.StandardPhases.HANDLING_TYPE)) return; - if (hasJavaObject()) return; - - String type = null; - if ((getYamlObject() instanceof String) || Boxing.isPrimitiveOrBoxedObject(getYamlObject())) { - // default handling of primitives: - // if type is expected, try to coerce - Class expectedJavaType = getExpectedTypeJava(); - if (expectedJavaType!=null) { - Maybe result = config.getCoercer().tryCoerce(getYamlObject(), expectedJavaType); - if (result.isPresent()) { - context.setJavaObject(result.get()); - context.phaseAdvance(); - return; - } - ReadingTypeOnBlackboard.get(blackboard).addNote("Cannot interpret '"+getYamlObject()+"' as "+expectedJavaType.getCanonicalName()); - return; - } - // if type not expected, treat as a type - type = Strings.toString(getYamlObject()); - } - - // TODO if map and list? - - if (!isYamlMap()) return; - - YamlKeysOnBlackboard.create(blackboard).yamlKeysToReadToJava = MutableMap.copyOf(getYamlMap()); - - if (type==null) type = peekFromYamlKeysOnBlackboard("type", String.class).orNull(); - if (type==null) type = context.getExpectedType(); - if (type==null) return; - - Class javaType = config.getTypeRegistry().getJavaType(type); - boolean primitive = javaType!=null && (Boxing.isPrimitiveOrBoxedClass(javaType) || CharSequence.class.isAssignableFrom(javaType)); - - Object result; - if (primitive) { - if (!getYamlMap().containsKey("value")) { - ReadingTypeOnBlackboard.get(blackboard).addNote("Primitive '"+type+"' does not declare a 'value'"); - return; - - } - result = config.getCoercer().coerce(getYamlMap().get("value"), javaType); - removeFromYamlKeysOnBlackboard("value"); - - } else { - result = config.getTypeRegistry().newInstanceMaybe((String)type, Yorml.newInstance(config)).orNull(); - if (result==null) { - ReadingTypeOnBlackboard.get(blackboard).addNote("Unknown type '"+type+"'"); - return; - } - if (!type.equals(context.getExpectedType())) { - Set serializers = MutableSet.of(); - config.typeRegistry.collectSerializers(type, serializers, MutableSet.of()); - SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(serializers); - } - context.phaseInsert(YormlContext.StandardPhases.MANIPULATING, YormlContext.StandardPhases.HANDLING_FIELDS); - } - - removeFromYamlKeysOnBlackboard("type"); - - context.setJavaObject(result); - context.phaseAdvance(); - } - - public void write() { - if (!context.isPhase(YormlContext.StandardPhases.HANDLING_TYPE)) return; - if (hasYamlObject()) return; - if (!hasJavaObject()) return; - if (JavaFieldsOnBlackboard.isPresent(blackboard)) return; - - JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.create(blackboard); - fib.fieldsToWriteFromJava = MutableList.of(); - - Object jo = getJavaObject(); - boolean primitive = Boxing.isPrimitiveOrBoxedObject(jo) || jo instanceof CharSequence; - if (primitive && getJavaObject().getClass().equals(Boxing.boxedType(getExpectedTypeJava()))) { - context.setYamlObject(jo); - context.phaseAdvance(); - return; - } - - // TODO map+list -- here, or in separate serializers? - - MutableMap map = MutableMap.of(); - context.setYamlObject(map); - - // TODO look up registry type - // TODO support osgi - - if (getJavaObject().getClass().equals(getExpectedTypeJava())) { - // skip explicitly writing the type - } else { - String typeName = config.getTypeRegistry().getTypeName(getJavaObject()); - map.put("type", typeName); - if (!primitive) { - Set serializers = MutableSet.of(); - config.typeRegistry.collectSerializers(typeName, serializers, MutableSet.of()); - SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(serializers); - } - } - - if (primitive) { - map.put("value", jo); - context.phaseInsert(YormlContext.StandardPhases.MANIPULATING); - - } else { - List fields = Reflections.findFields(getJavaObject().getClass(), - null, - FieldOrderings.ALPHABETICAL_FIELD_THEN_SUB_BEST_FIRST); - Field lastF = null; - for (Field f: fields) { - Maybe v = Reflections.getFieldValueMaybe(getJavaObject(), f); - if (ReflectionPredicates.IS_FIELD_NON_TRANSIENT.apply(f) && ReflectionPredicates.IS_FIELD_NON_STATIC.apply(f) && v.isPresentAndNonNull()) { - String name = f.getName(); - if (lastF!=null && lastF.getName().equals(f.getName())) { - // if field is shadowed use FQN - name = f.getDeclaringClass().getCanonicalName()+"."+name; - } - fib.fieldsToWriteFromJava.add(name); - } - lastF = f; - } - context.phaseInsert(YormlContext.StandardPhases.HANDLING_FIELDS, YormlContext.StandardPhases.MANIPULATING); - } - - context.phaseAdvance(); - return; - } - } -} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java new file mode 100644 index 0000000000..121b86a589 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.serializers; + +import java.lang.reflect.Field; +import java.util.List; + +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.javalang.FieldOrderings; +import org.apache.brooklyn.util.javalang.ReflectionPredicates; +import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yorml.Yorml; +import org.apache.brooklyn.util.yorml.YormlContext; + +public class InstantiateTypeFromRegistry extends YormlSerializerComposition { + + protected YormlSerializerWorker newWorker() { + return new Worker(); + } + + public class Worker extends InstantiateTypeWorkerAbstract { + public void read() { + if (!canDoRead()) return; + + String type = null; + if (getYamlObject() instanceof CharSequence) { + // string on its own interpreted as a type + type = Strings.toString(getYamlObject()); + } else { + // otherwise should be map + if (!isYamlMap()) return; + + type = readingTypeFromFieldOrExpected(); + } + + if (type==null) return; + + Object result = config.getTypeRegistry().newInstanceMaybe((String)type, Yorml.newInstance(config)).orNull(); + if (result==null) { + warn("Unknown type '"+type+"'"); + return; + } + + addSerializers(type); + storeReadObjectAndAdvance(result, true); + + if (isYamlMap()) { + removeFromYamlKeysOnBlackboard("type"); + } + } + + public void write() { + if (!context.isPhase(YormlContext.StandardPhases.HANDLING_TYPE)) return; + if (hasYamlObject()) return; + if (!hasJavaObject()) return; + if (JavaFieldsOnBlackboard.isPresent(blackboard)) return; + + // common primitives and maps/lists will have been handled + // TODO support osgi + + MutableMap map = writingMapWithType( + // explicitly write the type unless it is the expected one + getJavaObject().getClass().equals(getExpectedTypeJava()) ? null : config.getTypeRegistry().getTypeName(getJavaObject())); + + // collect fields + JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); + List fields = Reflections.findFields(getJavaObject().getClass(), + null, + FieldOrderings.ALPHABETICAL_FIELD_THEN_SUB_BEST_FIRST); + Field lastF = null; + for (Field f: fields) { + Maybe v = Reflections.getFieldValueMaybe(getJavaObject(), f); + if (ReflectionPredicates.IS_FIELD_NON_TRANSIENT.apply(f) && ReflectionPredicates.IS_FIELD_NON_STATIC.apply(f) && v.isPresentAndNonNull()) { + String name = f.getName(); + if (lastF!=null && lastF.getName().equals(f.getName())) { + // if field is shadowed use FQN + name = f.getDeclaringClass().getCanonicalName()+"."+name; + } + fib.fieldsToWriteFromJava.add(name); + } + lastF = f; + } + + context.phaseInsert(YormlContext.StandardPhases.HANDLING_FIELDS, YormlContext.StandardPhases.MANIPULATING); + storeWriteObjectAndAdvance(map); + return; + } + + } +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java new file mode 100644 index 0000000000..522fc5482d --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java @@ -0,0 +1,372 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.serializers; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.yorml.Yorml; +import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.YormlContextForRead; +import org.apache.brooklyn.util.yorml.YormlContextForWrite; +import org.apache.brooklyn.util.yorml.internal.YormlUtils; +import org.apache.brooklyn.util.yorml.internal.YormlUtils.GenericsParse; +import org.apache.brooklyn.util.yorml.internal.YormlUtils.JsonMarker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Objects; +import com.google.common.collect.Iterables; + +/* + * if expecting a coll + * and it's not a coll + * if primitive, try as type + * if map, new conversion phase + * and it is a coll + * instantiate then go through coll + * if not expecting a coll + * and it's not a coll + * do nothing + * and it is a coll + * try conversion to map + * + * repeat with value + */ +public class InstantiateTypeList extends YormlSerializerComposition { + + private static final Logger log = LoggerFactory.getLogger(InstantiateTypeList.class); + + private static final String LIST = YormlUtils.TYPE_LIST; + private static final String SET = YormlUtils.TYPE_SET; + + @SuppressWarnings("rawtypes") + Map> basicCollectionTypes = MutableMap.>of( + LIST, MutableList.class, + SET, MutableSet.class + ); + + @SuppressWarnings("rawtypes") + Map, String> typesMappedToBasic = MutableMap.,String>of( + MutableList.class, LIST, + ArrayList.class, LIST, + List.class, LIST, + MutableSet.class, SET, + LinkedHashSet.class, SET, + Set.class, SET + ); + + @SuppressWarnings("rawtypes") + Set> typesAllowedAsCollections = MutableSet.>of( + // TODO does anything fit this category? we serialize as a json list, including the type, and use xxx.add(...) to read in + ); + + protected YormlSerializerWorker newWorker() { + return new Worker(); + } + + public class Worker extends InstantiateTypeWorkerAbstract { + + String genericSubType = null; + Class expectedJavaType; + + public void read() { + if (!canDoRead()) return; + Object yo = getYamlObject(); + expectedJavaType = getExpectedTypeJava(); + + if (context.getExpectedType()!=null && !parseExpectedTypeAndDetermineIfNoBadProblems(context.getExpectedType())) return; + + if (expectedJavaType!=null && !Iterable.class.isAssignableFrom(expectedJavaType)) { + // expecting something other than a collection + if (!(yo instanceof Iterable)) { + // and not given a collection -- just exit + return; + } else { + // but we have a collection + // spawn manipulate-convert-from-list phase + context.phaseInsert(YormlContext.StandardPhases.MANIPULATING_FROM_LIST, YormlContext.StandardPhases.HANDLING_TYPE); + context.phaseAdvance(); + return; + } + } else if (!(yo instanceof Iterable)) { + // no expectation or expecting a collection, but not given a collection + if (yo instanceof Map) { + String type = readingTypeFromFieldOrExpected(); + Object value = readingValueFromTypeValueMap().orNull(); + Class oldExpectedType = expectedJavaType; + + // get any new generic type set - slightly messy + if (!parseExpectedTypeAndDetermineIfNoBadProblems(type)) return; + Class javaType = config.getTypeRegistry().getJavaType(type); + if (javaType==null) { javaType = expectedJavaType; } + expectedJavaType = oldExpectedType; + + if (javaType==null || value==null || !Collection.class.isAssignableFrom(javaType) || !Iterable.class.isInstance(value)) { + // only apply if it's a list type and a list value + return; + } + // looks like a list in a type-value map + Object jo = newInstance(expectedJavaType, type); + if (jo==null) return; + + context.setJavaObject(jo); + + readIterableInto((Collection)jo, (Iterable)value); + context.phaseAdvance(); + removeTypeAndValueKeys(); + return; + } + if (expectedJavaType!=null) { + // collection definitely expected but not received + context.phaseInsert(YormlContext.StandardPhases.MANIPULATING_TO_LIST, YormlContext.StandardPhases.HANDLING_TYPE); + context.phaseAdvance(); + return; + } + // otherwise standard InstantiateType will do it + return; + } else { + // given a collection, when expecting a collection or no expectation -- read as list + Object jo; + if (hasJavaObject()) { + // populating previous java object + jo = context.getJavaObject(); + + } else { + jo = newInstance(expectedJavaType, null); + if (jo==null) return; + + context.setJavaObject(jo); + } + + readIterableInto((Collection)jo, (Iterable)yo); + context.phaseAdvance(); + } + } + + protected boolean parseExpectedTypeAndDetermineIfNoBadProblems(String type) { + if (isJsonMarkerType(type)) { + genericSubType = YormlUtils.TYPE_JSON; + } else { + GenericsParse gp = new GenericsParse(type); + if (gp.warning!=null) { + warn(gp.warning); + return false; + } + if (gp.isGeneric()) { + if (gp.subTypeCount()!=1) { + // not a list + return false; + } + genericSubType = Iterables.getOnlyElement(gp.subTypes); + } + if (expectedJavaType==null) { + expectedJavaType = basicCollectionTypes.get(gp.baseType); + } + } + return true; + } + + private Object newInstance(Class javaType, String explicitTypeName) { + if (explicitTypeName!=null) { + GenericsParse gp = new GenericsParse(explicitTypeName); + if (gp.warning!=null) { + warn(gp.warning); + return null; + } + + Class locallyWantedType = basicCollectionTypes.get(gp.baseType); + + if (locallyWantedType==null) { + // rely on type registry + return config.getTypeRegistry().newInstance(explicitTypeName, Yorml.newInstance(config)); + } + + // create it ourselves, but first assert it matches expected + if (javaType!=null) { + if (locallyWantedType.isAssignableFrom(javaType)) { + // prefer the java type + } else if (javaType.isAssignableFrom(locallyWantedType)) { + // prefer locally wanted + javaType = locallyWantedType; + } + } else { + javaType = locallyWantedType; + } + + // and set the subtype + if (gp.subTypeCount()!=1) return null; + + String subType = Iterables.getOnlyElement(gp.subTypes); + if (genericSubType!=null && !genericSubType.equals(subType)) { + log.debug("Got different generic subtype, expected "+context.getExpectedType()+" but declared "+explicitTypeName+"; preferring declared"); + } + genericSubType = subType; + } + + Class concreteJavaType = null; + if (javaType==null || javaType.isInterface() || Modifier.isAbstract(javaType.getModifiers())) { + // take first from default types that matches + for (Class candidate : basicCollectionTypes.values()) { + if (javaType==null || javaType.isAssignableFrom(candidate)) { + concreteJavaType = candidate; + break; + } + } + if (concreteJavaType==null) { + // fallback, if given interface create as list + warn("No information to instantiate list "+javaType); + return null; + } + + } else { + concreteJavaType = javaType; + } + if (!Collection.class.isAssignableFrom(concreteJavaType)) { + warn("No information to add items to list "+concreteJavaType); + return null; + } + + try { + return concreteJavaType.newInstance(); + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + + protected void readIterableInto(Collection joq, Iterable yo) { + // go through collection, creating from children + + @SuppressWarnings("unchecked") + Collection jo = (Collection) joq; + int index = 0; + + for (Object yi: yo) { + YormlContextForRead subcontext = new YormlContextForRead(context.getJsonPath()+"["+index+"]", genericSubType); + subcontext.setYamlObject(yi); + jo.add(converter.read(subcontext)); + + index++; + } + } + + public void write() { + if (!canDoWrite()) return; + if (!(getJavaObject() instanceof Iterable)) return; + + boolean isPureJson = YormlUtils.JsonMarker.isPureJson(getJavaObject()); + + // if expecting json then: + if (isJsonMarkerTypeExpected()) { + if (!isPureJson) { + warn("Cannot write "+getJavaObject()+" as pure JSON"); + return; + } + storeWriteObjectAndAdvance(getJavaObject()); + return; + } + + Class expectedJavaType = getExpectedTypeJava(); + GenericsParse gp = new GenericsParse(context.getExpectedType()); + if (gp.warning!=null) { + warn(gp.warning); + return; + } + if (gp.isGeneric()) { + if (gp.subTypeCount()!=1) { + // not a list + return; + } + genericSubType = Iterables.getOnlyElement(gp.subTypes); + } + if (expectedJavaType==null) { + expectedJavaType = basicCollectionTypes.get(gp.baseType); + } + + String actualTypeName = typesMappedToBasic.get(getJavaObject().getClass()); + boolean isBasicCollectionType = (actualTypeName!=null); + if (actualTypeName==null) actualTypeName = config.getTypeRegistry().getTypeName(getJavaObject()); + if (actualTypeName==null) return; + boolean isAllowedCollectionType = isBasicCollectionType || typesAllowedAsCollections.contains(getJavaObject().getClass()); + + Class reconstructedJavaType = basicCollectionTypes.get(actualTypeName); + if (reconstructedJavaType==null) reconstructedJavaType = getJavaObject().getClass(); + + Object result; + Collection list = MutableList.of(); + + boolean writeWithoutTypeInformation = Objects.equal(reconstructedJavaType, expectedJavaType); + if (!writeWithoutTypeInformation) { + @SuppressWarnings("rawtypes") + Class defaultCollectionType = basicCollectionTypes.isEmpty() ? null : basicCollectionTypes.values().iterator().next(); + if (Objects.equal(reconstructedJavaType, defaultCollectionType)) { + // actual type is the default - typically can omit saying the type + if (context.getExpectedType()==null) writeWithoutTypeInformation = true; + else if (expectedJavaType!=null && expectedJavaType.isAssignableFrom(defaultCollectionType)) writeWithoutTypeInformation = true; + else { + // possibly another problem -- expecting something different to default + // don't fret, just include the type specifically + // likely they're just expecting an explicit collection type other than our default + actualTypeName = config.getTypeRegistry().getTypeName(getJavaObject()); + } + } + } + if ((YormlUtils.TYPE_LIST.equals(actualTypeName) || (YormlUtils.TYPE_SET.equals(actualTypeName))) && genericSubType==null) { + if (JsonMarker.isPureJson(getJavaObject()) && !Iterables.isEmpty((Iterable)getJavaObject())) { + writeWithoutTypeInformation = false; + actualTypeName = actualTypeName+"<"+YormlUtils.TYPE_JSON+">"; + genericSubType = YormlUtils.TYPE_JSON; + } + } + + if (writeWithoutTypeInformation) { + // add directly if we are expecting this + result = list; + } else if (!isAllowedCollectionType) { + // not to be written with this serializer + return; + } else { + // need to include the type name + if (actualTypeName==null) return; + result = MutableMap.of("type", actualTypeName, "value", list); + } + + int index = 0; + for (Object ji: (Iterable)getJavaObject()) { + YormlContextForWrite subcontext = new YormlContextForWrite(context.getJsonPath()+"["+index+"]", genericSubType); + subcontext.setJavaObject(ji); + list.add(converter.write(subcontext)); + + index++; + } + + storeWriteObjectAndAdvance(result); + } + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypePrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypePrimitive.java new file mode 100644 index 0000000000..d815916afa --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypePrimitive.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.serializers; + +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.internal.YormlUtils; + +public class InstantiateTypePrimitive extends YormlSerializerComposition { + + protected YormlSerializerWorker newWorker() { + return new Worker(); + } + + public class Worker extends InstantiateTypeWorkerAbstract { + + @Override + public Class getExpectedTypeJava() { + Class result = super.getExpectedTypeJava(); + if (result!=null) return result; + return getSpecialKnownTypeName(context.getExpectedType()); + } + + public void read() { + if (!canDoRead()) return; + + Class expectedJavaType; + Maybe value; + + if (isJsonPrimitiveObject(getYamlObject())) { + // pure primitive - we must know the type and then we should simply be able to coerce + + expectedJavaType = getExpectedTypeJava(); + if (expectedJavaType==null && !isJsonMarkerTypeExpected()) return; + + // type should be coercible + value = tryCoerceAndNoteError(getYamlObject(), expectedJavaType); + if (value.isAbsent()) return; + + } else { + // not primitive; it should be of {type: ..., value: ...} format with type being the primitive + + String typeName = readingTypeFromFieldOrExpected(); + if (typeName==null) return; + expectedJavaType = config.getTypeRegistry().getJavaType(typeName); + if (expectedJavaType==null) expectedJavaType = getSpecialKnownTypeName(typeName); + if (!isJsonPrimitiveType(expectedJavaType) && !isJsonMarkerType(typeName)) return; + + value = readingValueFromTypeValueMap(expectedJavaType); + if (value.isAbsent()) return; + if (tryCoerceAndNoteError(value.get(), expectedJavaType).isAbsent()) return; + + removeTypeAndValueKeys(); + } + + storeReadObjectAndAdvance(value.get(), false); + } + + protected Maybe tryCoerceAndNoteError(Object value, Class expectedJavaType) { + if (expectedJavaType==null) return Maybe.of(value); + Maybe coerced = config.getCoercer().tryCoerce(value, expectedJavaType); + if (coerced.isAbsent()) { + // type present but not coercible - error + ReadingTypeOnBlackboard.get(blackboard).addNote("Cannot interpret '"+value+"' as primitive "+expectedJavaType); + } + return coerced; + } + + public void write() { + if (!canDoWrite()) return; + + if (!YormlUtils.JsonMarker.isPureJson(getJavaObject())) return; + + if (isJsonPrimitiveType(getExpectedTypeJava()) || isJsonMarkerTypeExpected()) { + storeWriteObjectAndAdvance(getJavaObject()); + return; + } + + // not expecting a primitive/json; bail out if it's not a primitive (map/list might decide to write `json` as the type) + if (!isJsonPrimitiveObject(getJavaObject())) return; + + MutableMap map = writingMapWithTypeAndLiteralValue( + config.getTypeRegistry().getTypeName(getJavaObject()), + getJavaObject()); + + context.phaseInsert(YormlContext.StandardPhases.MANIPULATING); + storeWriteObjectAndAdvance(map); + } + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java new file mode 100644 index 0000000000..4f331da726 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java @@ -0,0 +1,130 @@ +package org.apache.brooklyn.util.yorml.serializers; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.javalang.Boxing; +import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.YormlSerializer; +import org.apache.brooklyn.util.yorml.internal.SerializersOnBlackboard; +import org.apache.brooklyn.util.yorml.internal.YormlUtils; +import org.apache.brooklyn.util.yorml.serializers.YormlSerializerComposition.YormlSerializerWorker; + +public abstract class InstantiateTypeWorkerAbstract extends YormlSerializerWorker { + + protected boolean isJsonPrimitiveObject(Object o) { + if (o==null) return true; + if (o instanceof String) return true; + if (Boxing.isPrimitiveOrBoxedObject(o)) return true; + return false; + } + protected boolean isJsonPrimitiveType(Class type) { + if (type==null) return false; + if (String.class.isAssignableFrom(type)) return true; + if (Boxing.isPrimitiveOrBoxedClass(type)) return true; + return false; + } + protected boolean isJsonTypeName(String typename) { + if (isJsonMarkerType(typename)) return true; + return getSpecialKnownTypeName(typename)!=null; + } + protected boolean isJsonMarkerTypeExpected() { + return isJsonMarkerType(context.getExpectedType()); + } + protected boolean isJsonMarkerType(String typeName) { + return YormlUtils.TYPE_JSON.equals(typeName); + } + protected Class getSpecialKnownTypeName(String typename) { + if (YormlUtils.TYPE_STRING.equals(typename)) return String.class; + if (YormlUtils.TYPE_LIST.equals(typename)) return List.class; + if (YormlUtils.TYPE_SET.equals(typename)) return Set.class; + if (YormlUtils.TYPE_MAP.equals(typename)) return Map.class; + return Boxing.boxedType( Boxing.getPrimitiveType(typename).orNull() ); + } + + protected boolean canDoRead() { + if (!context.isPhase(YormlContext.StandardPhases.HANDLING_TYPE)) return false; + if (hasJavaObject()) return false; + return true; + } + + protected boolean canDoWrite() { + if (!context.isPhase(YormlContext.StandardPhases.HANDLING_TYPE)) return false; + if (hasYamlObject()) return false; + if (!hasJavaObject()) return false; + if (JavaFieldsOnBlackboard.isPresent(blackboard)) return false; + return true; + } + + protected void addSerializers(String type) { + if (!type.equals(context.getExpectedType())) { + Set serializers = MutableSet.of(); + config.typeRegistry.collectSerializers(type, serializers, MutableSet.of()); + SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(serializers); + } + } + + protected void storeReadObjectAndAdvance(Object result, boolean addPhases) { + if (addPhases) { + context.phaseInsert(YormlContext.StandardPhases.MANIPULATING, YormlContext.StandardPhases.HANDLING_FIELDS); + } + context.setJavaObject(result); + context.phaseAdvance(); + } + + protected void storeWriteObjectAndAdvance(Object jo) { + context.setYamlObject(jo); + context.phaseAdvance(); + } + + protected String readingTypeFromFieldOrExpected() { + String type = null; + if (isYamlMap()) { + YamlKeysOnBlackboard.getOrCreate(blackboard, getYamlMap()); + type = peekFromYamlKeysOnBlackboard("type", String.class).orNull(); + } + if (type==null) type = context.getExpectedType(); + return type; + } + protected Maybe readingValueFromTypeValueMap() { + return readingValueFromTypeValueMap(null); + } + protected Maybe readingValueFromTypeValueMap(Class requiredType) { + if (!isYamlMap()) return Maybe.absent(); + if (YamlKeysOnBlackboard.peek(blackboard).yamlKeysToReadToJava.size()>2) return Maybe.absent(); + if (!MutableSet.of("type", "value").containsAll(YamlKeysOnBlackboard.peek(blackboard).yamlKeysToReadToJava.keySet())) { + return Maybe.absent(); + } + return peekFromYamlKeysOnBlackboard("value", requiredType); + } + protected void removeTypeAndValueKeys() { + removeFromYamlKeysOnBlackboard("type", "value"); + } + + protected MutableMap writingMapWithType(String typeName) { + JavaFieldsOnBlackboard.create(blackboard).fieldsToWriteFromJava = MutableList.of(); + MutableMap map = MutableMap.of(); + + if (typeName!=null) { + map.put("type", typeName); + addSerializers(typeName); + } + return map; + } + protected MutableMap writingMapWithTypeAndLiteralValue(String typeName, Object value) { + MutableMap map = writingMapWithType(typeName); + if (value!=null) { + map.put("value", value); + } + return map; + } + + protected void warn(String message) { + ReadingTypeOnBlackboard.get(blackboard).addNote(message); + } +} \ No newline at end of file diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java index 892f3496f9..601c962423 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java @@ -20,13 +20,11 @@ import java.util.Map; -import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.yorml.YormlContext; import org.apache.brooklyn.util.yorml.YormlException; import org.apache.brooklyn.util.yorml.YormlRequirement; -import com.google.common.annotations.Beta; - /** Keys from a YAML map that still need to be handled */ public class YamlKeysOnBlackboard implements YormlRequirement { @@ -38,8 +36,12 @@ public static boolean isPresent(Map blackboard) { public static YamlKeysOnBlackboard peek(Map blackboard) { return (YamlKeysOnBlackboard) blackboard.get(KEY); } - public static YamlKeysOnBlackboard getOrCreate(Map blackboard) { - if (!isPresent(blackboard)) { blackboard.put(KEY, new YamlKeysOnBlackboard()); } + public static YamlKeysOnBlackboard getOrCreate(Map blackboard, Map keys) { + if (!isPresent(blackboard)) { + YamlKeysOnBlackboard ykb = new YamlKeysOnBlackboard(); + blackboard.put(KEY, ykb); + ykb.yamlKeysToReadToJava = MutableMap.copyOf(keys); + } return peek(blackboard); } public static YamlKeysOnBlackboard create(Map blackboard) { @@ -58,16 +60,4 @@ public void checkCompletion(YormlContext context) { } } - /** true iff k1 and k2 are case-insensitively equal after removing all - and _. - * Note that the definition of mangling may change. - * TODO it should be stricter so that "ab" and "a-b" don't match but "aB" and "a-b" and "a_b" do */ - @Beta - public static boolean mangleable(String k1, String k2) { - if (k1==null || k2==null) return k1==k2; - k1 = Strings.replaceAllNonRegex(k1, "-", ""); - k1 = Strings.replaceAllNonRegex(k1, "_", ""); - k2 = Strings.replaceAllNonRegex(k2, "-", ""); - k2 = Strings.replaceAllNonRegex(k2, "_", ""); - return k1.toLowerCase().equals(k2.toLowerCase()); - } } \ No newline at end of file diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java index 9e44d83506..317f61be4b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java @@ -31,6 +31,7 @@ import org.apache.brooklyn.util.yorml.YormlSerializer; import org.apache.brooklyn.util.yorml.internal.YormlConfig; import org.apache.brooklyn.util.yorml.internal.YormlConverter; +import org.apache.brooklyn.util.yorml.internal.YormlUtils; public abstract class YormlSerializerComposition implements YormlSerializer { @@ -104,9 +105,11 @@ protected Maybe peekFromYamlKeysOnBlackboard(String key, Class expecte if (expectedType!=null && !expectedType.isInstance(v)) return Maybe.absent(); return Maybe.of((T)v); } - protected void removeFromYamlKeysOnBlackboard(String key) { + protected void removeFromYamlKeysOnBlackboard(String ...keys) { YamlKeysOnBlackboard ykb = YamlKeysOnBlackboard.peek(blackboard); - ykb.yamlKeysToReadToJava.remove(key); + for (String key: keys) { + ykb.yamlKeysToReadToJava.remove(key); + } } /** looks for all keys in {@link YamlKeysOnBlackboard} which can be mangled/ignore-case * to match the given key */ @@ -114,7 +117,7 @@ protected Set findAllKeyManglesYamlKeys(String targetKey) { Set result = MutableSet.of(); YamlKeysOnBlackboard ykb = YamlKeysOnBlackboard.peek(blackboard); for (Object k: ykb.yamlKeysToReadToJava.keySet()) { - if (k instanceof String && YamlKeysOnBlackboard.mangleable(targetKey, (String)k)) { + if (k instanceof String && YormlUtils.mangleable(targetKey, (String)k)) { result.add((String)k); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md index b66400212c..41051bcf26 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md @@ -518,8 +518,8 @@ to be shown. ### TODO -* Config/data keys * maps and lists, with generics +* Config/data keys * complex syntax, type as key, etc * defining serializers and linking to brooklyn diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java new file mode 100644 index 0000000000..6371b5b9f4 --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.tests; + +import java.util.List; +import java.util.Set; + +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableSet; +import org.testng.annotations.Test; + +/** Tests that explicit fields can be set at the outer level in yaml. */ +public class MapListTests { + + YormlTestFixture y = YormlTestFixture.newInstance(); + +// String MAP1_JSON = "{ a: 1, b: bbb }"; +// Map MAP1_OBJ = MutableMap.of("a", 1, "b", "bbb"); +// +// @Test public void testReadMap() { y.read(MAP1_JSON, "json").assertResult(MAP1_OBJ); } +// @Test public void testWriteMap() { y.write(MAP1_OBJ, "json").assertResult(MAP1_JSON); } +// +// String MAP1_JSON_EXPLICIT_TYPE = "{ type: json, value: "+MAP1_JSON+" }"; +// @Test public void testReadMapNoTypeExpected() { y.read(MAP1_JSON_EXPLICIT_TYPE, null).assertResult(MAP1_OBJ); } +// @Test public void testWriteMapNoTypeExpected() { y.write(MAP1_OBJ, null).assertResult(MAP1_JSON_EXPLICIT_TYPE); } +// +// String MAP1_JSON_OBJECT_OBJECT = "[ { key: { type: string, value: a }, value: { type: int, value: 1 }, "+ +// "{ key: { type: string, value: b }, value: { type: string, value: bbb } ]"; +// @Test public void testReadMapVerbose() { y.read(MAP1_JSON_OBJECT_OBJECT, "map").assertResult(MAP1_OBJ); } +// @Test public void testWriteMapVerbose() { y.write(MAP1_OBJ, "map").assertResult(MAP1_JSON_OBJECT_OBJECT); } +// +// String MAP1_JSON_STRING_OBJECT = "{ a: { type: int, value: 1 }, b: { type: string, value: bbb } ]"; +// @Test public void testReadMapVerboseStringKey() { y.read(MAP1_JSON_STRING_OBJECT, "map").assertResult(MAP1_OBJ); } +// @Test public void testWriteMapVerboseStringKey() { y.write(MAP1_OBJ, "map").assertResult(MAP1_JSON_STRING_OBJECT); } +// @Test public void testReadMapVerboseJsonKey() { y.read(MAP1_JSON_STRING_OBJECT, "map").assertResult(MAP1_OBJ); } +// @Test public void testWriteMapVerboseJsonKey() { y.write(MAP1_OBJ, "map").assertResult(MAP1_JSON_STRING_OBJECT); } + + + String LIST1_JSON = "[ a, 1, b ]"; + List LIST1_OBJ = MutableList.of("a", 1, "b"); + + @Test public void testReadList() { y.read(LIST1_JSON, "json").assertResult(LIST1_OBJ); } + @Test public void testWriteList() { y.write(LIST1_OBJ, "json").assertResult(LIST1_JSON); } + @Test public void testReadListListJson() { y.read(LIST1_JSON, "list").assertResult(LIST1_OBJ); } + @Test public void testWriteListListJson() { y.write(LIST1_OBJ, "list").assertResult(LIST1_JSON); } + + @Test public void testReadListNoTypeAnywhereIsError() { + try { + y.read(LIST1_JSON, null); + Asserts.shouldHaveFailedPreviously("Got "+y.lastResult+" when should have failed"); + } catch (Exception e) { Asserts.expectedFailureContainsIgnoreCase(e, "'a'", "unknown type"); } + } + + String LIST1_JSON_LIST_TYPE = "{ type: json, value: "+LIST1_JSON+" }"; + String LIST1_JSON_LIST_JSON_TYPE = "{ type: list, value: "+LIST1_JSON+" }"; + @Test public void testReadListNoTypeExpected() { y.read(LIST1_JSON_LIST_TYPE, null).assertResult(LIST1_OBJ); } + @Test public void testReadListExplicitTypeNoTypeExpected() { y.read(LIST1_JSON_LIST_TYPE, null).assertResult(LIST1_OBJ); } + @Test public void testWriteListNoTypeExpected() { y.write(LIST1_OBJ, null).assertResult(LIST1_JSON_LIST_JSON_TYPE); } + // write prefers type: json syntax above if appropriate; otherwise will to LIST_OBJECT syntax below + + String LIST1_JSON_OBJECT = "[ { type: string, value: a }, { type: int, value: 1 }, { type: string, value: b } ]"; + @Test public void testReadListObject() { y.read(LIST1_JSON_OBJECT, "list").assertResult(LIST1_OBJ); } + @Test public void testWriteListObject() { y.write(LIST1_OBJ, "list").assertResult(LIST1_JSON_OBJECT); } + @Test public void testReadListRawType() { y.read(LIST1_JSON_OBJECT, "list").assertResult(LIST1_OBJ); } + @Test public void testWriteListRawType() { y.write(LIST1_OBJ, "list").assertResult(LIST1_JSON_LIST_JSON_TYPE); } + @Test public void testReadListJsonType() { y.read(LIST1_JSON_OBJECT, "list").assertResult(LIST1_OBJ); } + @Test public void testReadListJsonTypeInBody() { y.read(LIST1_JSON_LIST_JSON_TYPE, null).assertResult(LIST1_OBJ); } + @Test public void testReadListJsonTypeDeclaredAndInBody() { y.read(LIST1_JSON_LIST_JSON_TYPE, "list").assertResult(LIST1_OBJ); } + + Set SET1_OBJ = MutableSet.of("a", 1, "b"); + String SET1_JSON = "{ type: set, value: [ a, 1, b ] }"; + @Test public void testWriteSet() { y.write(SET1_OBJ, "set").assertResult(LIST1_JSON); } + @Test public void testWriteSetNoType() { y.write(SET1_OBJ, null).assertResult(SET1_JSON); } + @Test public void testReadSet() { y.read(SET1_JSON, "set").assertResult(SET1_OBJ); } + @Test public void testWriteSetJson() { y.write(SET1_OBJ, "json").assertResult(LIST1_JSON); } + + + // TODO + // map tests + // passing generics from fields + // poor man: if field is compatible to mutable list or mutable set then make list<..> or set<..> + // rich man: registry can handle generics + // maps w generics + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java index 965d021d64..e1ab45efff 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java @@ -34,6 +34,7 @@ import org.apache.brooklyn.util.yorml.Yorml; import org.apache.brooklyn.util.yorml.YormlSerializer; import org.apache.brooklyn.util.yorml.YormlTypeRegistry; +import org.apache.brooklyn.util.yorml.internal.YormlUtils; import com.google.common.collect.Iterables; @@ -92,9 +93,11 @@ protected Class getJavaType(MockRegisteredType registeredType, String typeNam if (result==null && registeredType!=null) result = registeredType.javaType; if (result==null && registeredType!=null) result = getJavaType(registeredType.parentType); - if (result==null) result = Boxing.getPrimitiveType(typeName).orNull(); - if (result==null) result = Boxing.getPrimitiveType(typeName).orNull(); - if (result==null && "string".equals(typeName)) result = String.class; + if (result==null && typeName==null) return null; + + if (result==null) result = Boxing.boxedType(Boxing.getPrimitiveType(typeName).orNull()); + if (result==null && YormlUtils.TYPE_STRING.equals(typeName)) result = String.class; + if (result==null && typeName.startsWith("java:")) { typeName = Strings.removeFromStart(typeName, "java:"); try { diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java index c2d5f09fb7..c5d492deda 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java @@ -126,6 +126,12 @@ public void testStringPrimitiveWhereTypeUnknown() { write("hello").assertResult("{ type: string, value: hello }"). read("{ type: string, value: hello }", null).assertResult("hello"); } + @Test + public void testIntPrimitiveWhereTypeUnknown() { + YormlTestFixture.newInstance(). + write(42).assertResult("{ type: int, value: 42 }"). + read("{ type: int, value: 42 }", null).assertResult(42); + } @Test public void testRegisteredType() { diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java index f9433ec9db..d138d1e838 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java @@ -18,6 +18,7 @@ */ package org.apache.brooklyn.util.yorml.tests; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -77,9 +78,9 @@ public YormlTestFixture read(String objectToRead, String expectedType) { public YormlTestFixture assertResult(Object expectation) { if (expectation instanceof String) { - if (lastResult instanceof Map || lastResult instanceof List) { + if (lastResult instanceof Map || lastResult instanceof Collection) { assertEqualsIgnoringQuotes(Jsonya.newInstance().add(lastResult).toString(), expectation, "Result as JSON string does not match expectation"); - } else if (!(lastResult instanceof String)) { + } else { assertEqualsIgnoringQuotes(Strings.toString(lastResult), expectation, "Result toString does not match expectation"); } } else { From 80b0feb13d8c39197d5f9256fd9242d3de5472a8 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 29 Jun 2016 03:45:30 +0100 Subject: [PATCH 15/77] misc utils minor improvements mainly around findMethod --- .../java/org/apache/brooklyn/util/collections/MutableSet.java | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableSet.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableSet.java index 4b70e37430..749e84dd9c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableSet.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableSet.java @@ -18,7 +18,6 @@ */ package org.apache.brooklyn.util.collections; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; From 0172445881f08f93ef2b801a34d9b65b7ff502f3 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 29 Jun 2016 03:46:17 +0100 Subject: [PATCH 16/77] add yorml map support, fix list support, other minor fixes --- .../org/apache/brooklyn/util/yorml/Yorml.java | 12 +- .../brooklyn/util/yorml/YormlContext.java | 1 + .../util/yorml/YormlContextForRead.java | 3 +- .../util/yorml/YormlContextForWrite.java | 3 +- .../util/yorml/internal/YormlUtils.java | 1 + .../serializers/FieldsInMapUnderFields.java | 9 +- .../InstantiateTypeFromRegistry.java | 5 + .../serializers/InstantiateTypeList.java | 51 +-- .../yorml/serializers/InstantiateTypeMap.java | 341 ++++++++++++++++++ .../InstantiateTypeWorkerAbstract.java | 18 + .../serializers/ReadingTypeOnBlackboard.java | 11 +- .../util/javalang/ReflectionsTest.java | 41 +++ .../util/yorml/tests/MapListTests.java | 61 ++-- .../util/yorml/tests/YormlBasicTests.java | 2 + 14 files changed, 493 insertions(+), 66 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeMap.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java index 9fc8b3b888..02419257f2 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java @@ -26,6 +26,7 @@ import org.apache.brooklyn.util.yorml.serializers.FieldsInMapUnderFields; import org.apache.brooklyn.util.yorml.serializers.InstantiateTypeFromRegistry; import org.apache.brooklyn.util.yorml.serializers.InstantiateTypeList; +import org.apache.brooklyn.util.yorml.serializers.InstantiateTypeMap; import org.apache.brooklyn.util.yorml.serializers.InstantiateTypePrimitive; @@ -44,6 +45,7 @@ public static Yorml newInstance(YormlTypeRegistry typeRegistry) { new FieldsInMapUnderFields(), new InstantiateTypePrimitive(), new InstantiateTypeList(), + new InstantiateTypeMap(), new InstantiateTypeFromRegistry() )); } @@ -66,20 +68,14 @@ public Object read(String yaml, String expectedType) { return readFromYamlObject(new org.yaml.snakeyaml.Yaml().load(yaml), expectedType); } public Object readFromYamlObject(Object yamlObject, String type) { - YormlContextForRead context = new YormlContextForRead("", type); - context.setYamlObject(yamlObject); - new YormlConverter(config).read(context); - return context.getJavaObject(); + return new YormlConverter(config).read( new YormlContextForRead(yamlObject, "", type) ); } public Object write(Object java) { return write(java, null); } public Object write(Object java, String expectedType) { - YormlContextForWrite context = new YormlContextForWrite("", expectedType); - context.setJavaObject(java); - new YormlConverter(config).write(context); - return context.getYamlObject(); + return new YormlConverter(config).write( new YormlContextForWrite(java, "", expectedType) ); } // public T read(String yaml, Class type) { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java index 891a748756..ab757ba779 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java @@ -66,6 +66,7 @@ public Object getJavaObject() { public void setJavaObject(Object javaObject) { this.javaObject = javaObject; } + public Object getYamlObject() { return yamlObject; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForRead.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForRead.java index d5763a7956..74ccf599f7 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForRead.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForRead.java @@ -22,8 +22,9 @@ public class YormlContextForRead extends YormlContext { - public YormlContextForRead(String jsonPath, String expectedType) { + public YormlContextForRead(Object yamlObject, String jsonPath, String expectedType) { super(jsonPath, expectedType); + setYamlObject(yamlObject); } String origin; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForWrite.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForWrite.java index 8ac92fd60b..b165e0e3de 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForWrite.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForWrite.java @@ -20,8 +20,9 @@ public class YormlContextForWrite extends YormlContext { - public YormlContextForWrite(String jsonPath, String expectedType) { + public YormlContextForWrite(Object javaObject, String jsonPath, String expectedType) { super(jsonPath, expectedType); + setJavaObject(javaObject); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java index 093634d5b9..e1e940a56d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java @@ -70,6 +70,7 @@ public static boolean isPureJson(Object o) { if (!(oi.getKey() instanceof String)) return false; if (!isPureJson(oi.getValue())) return false; } + return true; } return false; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java index 628841e5b5..3f15c3d8e4 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java @@ -60,9 +60,7 @@ public void read() { // as above } else { String fieldType = config.getTypeRegistry().getTypeNameOfClass(ff.getType()); - YormlContextForRead subcontext = new YormlContextForRead(context.getJsonPath()+"/"+f, fieldType); - subcontext.setYamlObject(v); - Object v2 = converter.read(subcontext); + Object v2 = converter.read( new YormlContextForRead(v, context.getJsonPath()+"/"+f, fieldType) ); ff.setAccessible(true); ff.set(getJavaObject(), v2); @@ -100,10 +98,7 @@ public void write() { } else { Field ff = Reflections.findFieldMaybe(getJavaObject().getClass(), f).get(); String fieldType = config.getTypeRegistry().getTypeNameOfClass(ff.getType()); - YormlContextForWrite subcontext = new YormlContextForWrite(context.getJsonPath()+"/"+f, fieldType); - subcontext.setJavaObject(v.get()); - - Object v2 = converter.write(subcontext); + Object v2 = converter.write(new YormlContextForWrite(v.get(), context.getJsonPath()+"/"+f, fieldType) ); fields.put(f, v2); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java index 121b86a589..667f282243 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java @@ -73,6 +73,11 @@ public void write() { if (!hasJavaObject()) return; if (JavaFieldsOnBlackboard.isPresent(blackboard)) return; + if (Reflections.hasSpecialSerializationMethods(getJavaObject().getClass())) { + warn("Cannot write "+getJavaObject().getClass()+" using default strategy as it has custom serializaton methods"); + return; + } + // common primitives and maps/lists will have been handled // TODO support osgi diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java index 522fc5482d..3ee209af4c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java @@ -30,6 +30,7 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.yorml.Yorml; import org.apache.brooklyn.util.yorml.YormlContext; import org.apache.brooklyn.util.yorml.YormlContextForRead; @@ -66,13 +67,13 @@ public class InstantiateTypeList extends YormlSerializerComposition { private static final String SET = YormlUtils.TYPE_SET; @SuppressWarnings("rawtypes") - Map> basicCollectionTypes = MutableMap.>of( + Map> typeAliases = MutableMap.>of( LIST, MutableList.class, SET, MutableSet.class ); @SuppressWarnings("rawtypes") - Map, String> typesMappedToBasic = MutableMap.,String>of( + Map, String> typesAliased = MutableMap.,String>of( MutableList.class, LIST, ArrayList.class, LIST, List.class, LIST, @@ -82,7 +83,7 @@ public class InstantiateTypeList extends YormlSerializerComposition { ); @SuppressWarnings("rawtypes") - Set> typesAllowedAsCollections = MutableSet.>of( + Set> typesAllowed = MutableSet.>of( // TODO does anything fit this category? we serialize as a json list, including the type, and use xxx.add(...) to read in ); @@ -110,8 +111,10 @@ public void read() { } else { // but we have a collection // spawn manipulate-convert-from-list phase - context.phaseInsert(YormlContext.StandardPhases.MANIPULATING_FROM_LIST, YormlContext.StandardPhases.HANDLING_TYPE); - context.phaseAdvance(); + if (!context.seenPhase(YormlContext.StandardPhases.MANIPULATING_FROM_LIST)) { + context.phaseInsert(YormlContext.StandardPhases.MANIPULATING_FROM_LIST, YormlContext.StandardPhases.HANDLING_TYPE); + context.phaseAdvance(); + } return; } } else if (!(yo instanceof Iterable)) { @@ -144,8 +147,10 @@ public void read() { } if (expectedJavaType!=null) { // collection definitely expected but not received - context.phaseInsert(YormlContext.StandardPhases.MANIPULATING_TO_LIST, YormlContext.StandardPhases.HANDLING_TYPE); - context.phaseAdvance(); + if (!context.seenPhase(YormlContext.StandardPhases.MANIPULATING_TO_LIST)) { + context.phaseInsert(YormlContext.StandardPhases.MANIPULATING_TO_LIST, YormlContext.StandardPhases.HANDLING_TYPE); + context.phaseAdvance(); + } return; } // otherwise standard InstantiateType will do it @@ -186,7 +191,7 @@ protected boolean parseExpectedTypeAndDetermineIfNoBadProblems(String type) { genericSubType = Iterables.getOnlyElement(gp.subTypes); } if (expectedJavaType==null) { - expectedJavaType = basicCollectionTypes.get(gp.baseType); + expectedJavaType = typeAliases.get(gp.baseType); } } return true; @@ -200,7 +205,7 @@ private Object newInstance(Class javaType, String explicitTypeName) { return null; } - Class locallyWantedType = basicCollectionTypes.get(gp.baseType); + Class locallyWantedType = typeAliases.get(gp.baseType); if (locallyWantedType==null) { // rely on type registry @@ -232,7 +237,7 @@ private Object newInstance(Class javaType, String explicitTypeName) { Class concreteJavaType = null; if (javaType==null || javaType.isInterface() || Modifier.isAbstract(javaType.getModifiers())) { // take first from default types that matches - for (Class candidate : basicCollectionTypes.values()) { + for (Class candidate : typeAliases.values()) { if (javaType==null || javaType.isAssignableFrom(candidate)) { concreteJavaType = candidate; break; @@ -267,9 +272,7 @@ protected void readIterableInto(Collection joq, Iterable yo) { int index = 0; for (Object yi: yo) { - YormlContextForRead subcontext = new YormlContextForRead(context.getJsonPath()+"["+index+"]", genericSubType); - subcontext.setYamlObject(yi); - jo.add(converter.read(subcontext)); + jo.add(converter.read( new YormlContextForRead(yi, context.getJsonPath()+"["+index+"]", genericSubType) )); index++; } @@ -277,7 +280,6 @@ protected void readIterableInto(Collection joq, Iterable yo) { public void write() { if (!canDoWrite()) return; - if (!(getJavaObject() instanceof Iterable)) return; boolean isPureJson = YormlUtils.JsonMarker.isPureJson(getJavaObject()); @@ -287,7 +289,10 @@ public void write() { warn("Cannot write "+getJavaObject()+" as pure JSON"); return; } - storeWriteObjectAndAdvance(getJavaObject()); + @SuppressWarnings("unchecked") + Collection l = Reflections.invokeConstructorFromArgsIncludingPrivate(typesAliased.keySet().iterator().next()).get(); + Iterables.addAll(l, (Iterable)getJavaObject()); + storeWriteObjectAndAdvance(l); return; } @@ -305,16 +310,17 @@ public void write() { genericSubType = Iterables.getOnlyElement(gp.subTypes); } if (expectedJavaType==null) { - expectedJavaType = basicCollectionTypes.get(gp.baseType); + expectedJavaType = typeAliases.get(gp.baseType); } - String actualTypeName = typesMappedToBasic.get(getJavaObject().getClass()); + String actualTypeName = typesAliased.get(getJavaObject().getClass()); boolean isBasicCollectionType = (actualTypeName!=null); if (actualTypeName==null) actualTypeName = config.getTypeRegistry().getTypeName(getJavaObject()); if (actualTypeName==null) return; - boolean isAllowedCollectionType = isBasicCollectionType || typesAllowedAsCollections.contains(getJavaObject().getClass()); + boolean isAllowedCollectionType = isBasicCollectionType || typesAllowed.contains(getJavaObject().getClass()); + if (!isAllowedCollectionType) return; - Class reconstructedJavaType = basicCollectionTypes.get(actualTypeName); + Class reconstructedJavaType = typeAliases.get(actualTypeName); if (reconstructedJavaType==null) reconstructedJavaType = getJavaObject().getClass(); Object result; @@ -323,7 +329,7 @@ public void write() { boolean writeWithoutTypeInformation = Objects.equal(reconstructedJavaType, expectedJavaType); if (!writeWithoutTypeInformation) { @SuppressWarnings("rawtypes") - Class defaultCollectionType = basicCollectionTypes.isEmpty() ? null : basicCollectionTypes.values().iterator().next(); + Class defaultCollectionType = typeAliases.isEmpty() ? null : typeAliases.values().iterator().next(); if (Objects.equal(reconstructedJavaType, defaultCollectionType)) { // actual type is the default - typically can omit saying the type if (context.getExpectedType()==null) writeWithoutTypeInformation = true; @@ -358,10 +364,7 @@ public void write() { int index = 0; for (Object ji: (Iterable)getJavaObject()) { - YormlContextForWrite subcontext = new YormlContextForWrite(context.getJsonPath()+"["+index+"]", genericSubType); - subcontext.setJavaObject(ji); - list.add(converter.write(subcontext)); - + list.add(converter.write( new YormlContextForWrite(ji, context.getJsonPath()+"["+index+"]", genericSubType) )); index++; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeMap.java new file mode 100644 index 0000000000..28ac0109d5 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeMap.java @@ -0,0 +1,341 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.serializers; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.yorml.Yorml; +import org.apache.brooklyn.util.yorml.YormlContextForRead; +import org.apache.brooklyn.util.yorml.YormlContextForWrite; +import org.apache.brooklyn.util.yorml.internal.YormlUtils; +import org.apache.brooklyn.util.yorml.internal.YormlUtils.GenericsParse; + +import com.google.common.base.Objects; + + +public class InstantiateTypeMap extends YormlSerializerComposition { + + private static final String MAP = YormlUtils.TYPE_MAP; + + // these mapping class settings are slightly OTT but keeping for consistency with list and in case we have 'alias' + + @SuppressWarnings("rawtypes") + Map> typeAliases = MutableMap.>of( + MAP, MutableMap.class + ); + + @SuppressWarnings("rawtypes") + Map, String> typesAliased = MutableMap.,String>of( + MutableMap.class, MAP, + LinkedHashMap.class, MAP + ); + + @SuppressWarnings("rawtypes") + Set> typesAllowed = MutableSet.>of( + // TODO does anything fit this category? we serialize as a json map, including the type, and use xxx.put(...) to read in + ); + + protected YormlSerializerWorker newWorker() { + return new Worker(); + } + + public class Worker extends InstantiateTypeWorkerAbstract { + + String genericKeySubType = null, genericValueSubType = null; + Class expectedJavaType; + String expectedBaseTypeName; + + public void read() { + if (!canDoRead()) return; + Object yo = getYamlObject(); + + expectedJavaType = getExpectedTypeJava(); + if (context.getExpectedType()!=null && !parseExpectedTypeAndDetermineIfNoBadProblems(context.getExpectedType())) return; + + // if expected type is json then we just dump what we have / read what's there, end + // if expected type in the allows list or compatible with "map" and j.u.Map, look at that value + // else try read/write as { type: mapxxx, value: valuexxx } + // else end, it isn't for us + // then for value + // if all keys are primitives then write as map { keyxxx: valuexxx } + // else write as list of kv pairs - [ { key: keyxxx , value: valuexxx } ] + // (in both cases using generics if available) + + if (isJsonMarkerTypeExpected()) { + // json is pass-through + context.setJavaObject( context.getYamlObject() ); + context.phaseAdvance(); + YamlKeysOnBlackboard.getOrCreate(blackboard, null).yamlKeysToReadToJava.clear(); + return; + } + + if (expectedJavaType!=null && !Map.class.isAssignableFrom(expectedJavaType)) return; // not a map expected + + if (isJsonPrimitiveObject(yo)) return; + + String actualBaseTypeName; + Class actualType; + Object value; + String alias = getAlias(expectedJavaType); + if (alias!=null || typesAllowed.contains(expectedJavaType)) { + // type would not have been written + actualBaseTypeName = expectedBaseTypeName; + value = getYamlObject(); + + } else { + // the type must have been made explicit + if (!isYamlMap()) return; + actualBaseTypeName = readingTypeFromFieldOrExpected(); + Maybe valueM = readingValueFromTypeValueMap(); + if (actualBaseTypeName==null && valueM.isAbsent()) return; + + Class oldExpectedJavaType = expectedJavaType; expectedJavaType = null; + String oldExpectedBaseTypeName = expectedBaseTypeName; expectedBaseTypeName = null; + parseExpectedTypeAndDetermineIfNoBadProblems(actualBaseTypeName); + // above will overwrite baseType + alias = getAlias(expectedJavaType); + actualType = expectedJavaType; + actualBaseTypeName = expectedBaseTypeName; + expectedJavaType = oldExpectedJavaType; + expectedBaseTypeName = oldExpectedBaseTypeName; + if (actualType==null) actualType = config.getTypeRegistry().getJavaType(actualBaseTypeName); + if (actualType==null) return; //we don't recognise the type + if (!Map.class.isAssignableFrom(actualType)) return; //it's not a map + + value = valueM.get(); + } + + // create actualType, then populate from value + Map jo = null; + if (typeAliases.get(actualBaseTypeName)!=null) { + jo = (Map) Reflections.invokeConstructorFromArgsIncludingPrivate(typeAliases.get(actualBaseTypeName)).get(); + } else { + jo = (Map) config.getTypeRegistry().newInstance(actualBaseTypeName, Yorml.newInstance(config)); + } + @SuppressWarnings("unchecked") + Map jom = (Map)jo; + + if (value instanceof Iterable) { + int i=0; + for (Object o: (Iterable) value) { + String newPath = context.getJsonPath()+"["+i+"]"; + if (o instanceof Map) { + Map m = (Map) o; + if (m.isEmpty() || m.size()>2) { + warn("Invalid map-entry in list for map at "+newPath); + return; + } + Object ek1=null, ev1, ek2=null, ev2; + if (m.size()==1) { + ek2 = m.keySet().iterator().next(); + ev1 = m.values().iterator().next(); + } else { + if (!MutableSet.of("key", "value").containsAll(m.keySet())) { + warn("Invalid key-value entry in list for map at "+newPath); + return; + } + ek1 = m.get("key"); + ev1 = m.get("value"); + } + if (ek2==null) { + ek2 = converter.read(new YormlContextForRead(ek1, newPath+"/key", genericKeySubType)); + } + ev2 = converter.read(new YormlContextForRead(ev1, newPath+"/value", genericValueSubType)); + jom.put(ek2, ev2); + } else { + // must be an entry set, so invalid + // however at this point we are committed to it being a map, so throw + warn("Invalid non-map map-entry in list for map at "+newPath); + return; + } + i++; + } + + } else if (value instanceof Map) { + for (Map.Entry me: ((Map)value).entrySet()) { + Object v = converter.read(new YormlContextForRead(me.getValue(), context.getJsonPath()+"/"+me.getKey(), genericValueSubType)); + jom.put(me.getKey(), v); + } + + } else { + // can't deal with primitive - but should have exited earlier + return; + } + + context.setJavaObject(jo); + context.phaseAdvance(); + YamlKeysOnBlackboard.getOrCreate(blackboard, null).yamlKeysToReadToJava.clear(); + } + + private String getAlias(Class type) { + if (type==null || !Map.class.isAssignableFrom(type)) return null; + for (Class t: typesAliased.keySet()) { + if (type.isAssignableFrom(t)) { + return typesAliased.get(t); + } + } + return null; + } + + protected boolean parseExpectedTypeAndDetermineIfNoBadProblems(String type) { + if (isJsonMarkerType(type)) { + genericKeySubType = YormlUtils.TYPE_JSON; + genericValueSubType = YormlUtils.TYPE_JSON; + } else { + GenericsParse gp = new GenericsParse(type); + if (gp.warning!=null) { + warn(gp.warning); + return false; + } + if (gp.isGeneric()) { + if (gp.subTypeCount()!=2) { + // not a list + return false; + } + genericKeySubType = gp.subTypes.get(0); + genericValueSubType = gp.subTypes.get(1); + + if ("?".equals(genericKeySubType)) genericKeySubType = null; + if ("?".equals(genericValueSubType)) genericValueSubType = null; + } + if (expectedBaseTypeName==null) { + expectedBaseTypeName = gp.baseType; + } + if (expectedJavaType==null) { + expectedJavaType = typeAliases.get(gp.baseType); + } + } + return true; + } + + public void write() { + if (!canDoWrite()) return; + if (!(getJavaObject() instanceof Map)) return; + Map jo = (Map)getJavaObject(); + + expectedJavaType = getExpectedTypeJava(); + if (context.getExpectedType()!=null && !parseExpectedTypeAndDetermineIfNoBadProblems(context.getExpectedType())) return; + String expectedGenericKeySubType = genericKeySubType; + String expectedGenericValueSubType = genericValueSubType; + + boolean isPureJson = YormlUtils.JsonMarker.isPureJson(getJavaObject()); + + // if expecting json then + if (isJsonMarkerTypeExpected()) { + if (!isPureJson) { + warn("Cannot write "+getJavaObject()+" as pure JSON"); + return; + } + @SuppressWarnings("unchecked") + Map m = Reflections.invokeConstructorFromArgsIncludingPrivate(typesAliased.keySet().iterator().next()).get(); + m.putAll((Map)getJavaObject()); + storeWriteObjectAndAdvance(m); + return; + } + + // if expected type in the allows list or compatible with "map" and j.u.Map, look at that value + // else try read/write as { type: mapxxx, value: valuexxx } + // else end, it isn't for us + String alias = getAlias(getJavaObject().getClass()); + if (alias==null && !typesAllowed.contains(getJavaObject().getClass())) { + // actual type should not be written this way + return; + } + String aliasOfExpected = getAlias(expectedJavaType); + boolean writeWithoutTypeInformation; + if (alias!=null) writeWithoutTypeInformation = alias.equals(aliasOfExpected); + else writeWithoutTypeInformation = getJavaObject().getClass().equals(expectedJavaType); + + String declaredType = alias; + if (declaredType==null) declaredType = config.getTypeRegistry().getTypeName(getJavaObject()); + + // then for value + // if all keys are primitives then write as map { keyxxx: valuexxx } + // else write as list of singleton maps as above or kv pairs - [ { key: keyxxx , value: valuexxx } ] + // (in both cases using generics if available) + boolean allKeysString = true; + for (Object k: jo.keySet()) { + if (!(k instanceof String)) { allKeysString = false; break; } + } + + Object result; + boolean isEmpty; + if (allKeysString) { + if (isPureJson && genericValueSubType==null) { + genericValueSubType = YormlUtils.TYPE_JSON; + } + + MutableMap out = MutableMap.of(); + for (Map.Entry me: jo.entrySet()) { + Object v = converter.write(new YormlContextForWrite(me.getValue(), context.getJsonPath()+"/"+me.getKey(), genericValueSubType)); + out.put(me.getKey(), v); + } + isEmpty = out.isEmpty(); + result = out; + + } else { + int i=0; + MutableList out = MutableList.of(); + for (Map.Entry me: jo.entrySet()) { + Object v = converter.write(new YormlContextForWrite(me.getValue(), context.getJsonPath()+"["+i+"]/value", genericValueSubType)); + + if (me.getKey() instanceof String) { + out.add(MutableMap.of(me.getKey(), v)); + } else { + Object k = converter.write(new YormlContextForWrite(me.getKey(), context.getJsonPath()+"["+i+"]/key", genericValueSubType)); + out.add(MutableMap.of("key", k, "value", v)); + } + i++; + + } + isEmpty = out.isEmpty(); + result = out; + } + + if (!isEmpty && ((!allKeysString && genericKeySubType!=null) || genericValueSubType!=null)) { + // if relying on generics we must include the types + if (writeWithoutTypeInformation) { + boolean mustWrap = false; + mustWrap |= (!allKeysString && genericKeySubType!=null && !Objects.equal(expectedGenericKeySubType, genericKeySubType)); + mustWrap |= (genericValueSubType!=null && !Objects.equal(expectedGenericValueSubType, genericValueSubType)); + if (mustWrap) { + writeWithoutTypeInformation = false; + } + } + declaredType = declaredType + "<" + (allKeysString ? "string" : genericKeySubType!=null ? genericKeySubType : "object") + "," + + (genericValueSubType!=null ? genericValueSubType : "object") + ">"; + } + + if (!writeWithoutTypeInformation) { + result = MutableMap.of("type", declaredType, "value", result); + } + + + storeWriteObjectAndAdvance(result); + } + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java index 4f331da726..c9f9210023 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.brooklyn.util.yorml.serializers; import java.util.List; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ReadingTypeOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ReadingTypeOnBlackboard.java index 3bce75e93c..d683c0f0ba 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ReadingTypeOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ReadingTypeOnBlackboard.java @@ -18,18 +18,20 @@ */ package org.apache.brooklyn.util.yorml.serializers; -import java.util.List; import java.util.Map; +import java.util.Set; -import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.YormlContextForRead; +import org.apache.brooklyn.util.yorml.YormlContextForWrite; import org.apache.brooklyn.util.yorml.YormlException; import org.apache.brooklyn.util.yorml.YormlRequirement; public class ReadingTypeOnBlackboard implements YormlRequirement { - List errorNotes = MutableList.of(); + Set errorNotes = MutableSet.of(); public static final String KEY = ReadingTypeOnBlackboard.class.getCanonicalName(); @@ -44,7 +46,8 @@ public static ReadingTypeOnBlackboard get(Map blackboard) { @Override public void checkCompletion(YormlContext context) { - if (context.getJavaObject()!=null) return; + if (context instanceof YormlContextForRead && context.getJavaObject()!=null) return; + if (context instanceof YormlContextForWrite && context.getYamlObject()!=null) return; if (errorNotes.isEmpty()) throw new YormlException("No means to identify type to instantiate", context); throw new YormlException(Strings.join(errorNotes, "; "), context); } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/javalang/ReflectionsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/javalang/ReflectionsTest.java index f5ec96e4a1..7104c32f0b 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/javalang/ReflectionsTest.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/javalang/ReflectionsTest.java @@ -23,9 +23,13 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableSet; import org.testng.Assert; import org.testng.annotations.Test; @@ -259,4 +263,41 @@ public void testGetFieldValue() { Assert.assertEquals(Reflections.getFieldValueMaybe(f2, FF1.class.getCanonicalName()+"."+"x").get(), 1); } + @SuppressWarnings("rawtypes") + static class MM1 { + public void foo(List l) {} + @SuppressWarnings("unused") + private void bar(List l) {} + } + + @SuppressWarnings("rawtypes") + static class MM2 extends MM1 { + public void foo(ArrayList l) {} + } + + @Test + public void testFindMethods() { + Asserts.assertSize(Reflections.findMethodsCompatible(MM2.class, "foo", ArrayList.class), 2); + Asserts.assertSize(Reflections.findMethodsCompatible(MM2.class, "foo", List.class), 1); + Asserts.assertSize(Reflections.findMethodsCompatible(MM2.class, "foo", Object.class), 0); + Asserts.assertSize(Reflections.findMethodsCompatible(MM2.class, "foo", Map.class), 0); + Asserts.assertSize(Reflections.findMethodsCompatible(MM2.class, "bar", List.class), 1); + Asserts.assertSize(Reflections.findMethodsCompatible(MM1.class, "bar", ArrayList.class), 1); + } + + @Test + public void testFindMethod() { + Asserts.assertTrue(Reflections.findMethodMaybe(MM2.class, "foo", ArrayList.class).isPresent()); + Asserts.assertTrue(Reflections.findMethodMaybe(MM2.class, "foo", List.class).isPresent()); + Asserts.assertTrue(Reflections.findMethodMaybe(MM2.class, "foo", Object.class).isAbsent()); + Asserts.assertTrue(Reflections.findMethodMaybe(MM2.class, "bar", List.class).isPresent()); + Asserts.assertTrue(Reflections.findMethodMaybe(MM2.class, "bar", ArrayList.class).isAbsent()); + } + + @Test + public void testHasSerializableMethods() { + Asserts.assertFalse(Reflections.hasSpecialSerializationMethods(MM2.class)); + Asserts.assertTrue(Reflections.hasSpecialSerializationMethods(LinkedHashMap.class)); + } + } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java index 6371b5b9f4..91442ff40d 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java @@ -19,11 +19,15 @@ package org.apache.brooklyn.util.yorml.tests; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.yorml.tests.YormlBasicTests.Shape; +import org.testng.Assert; import org.testng.annotations.Test; /** Tests that explicit fields can be set at the outer level in yaml. */ @@ -31,27 +35,23 @@ public class MapListTests { YormlTestFixture y = YormlTestFixture.newInstance(); -// String MAP1_JSON = "{ a: 1, b: bbb }"; -// Map MAP1_OBJ = MutableMap.of("a", 1, "b", "bbb"); -// -// @Test public void testReadMap() { y.read(MAP1_JSON, "json").assertResult(MAP1_OBJ); } -// @Test public void testWriteMap() { y.write(MAP1_OBJ, "json").assertResult(MAP1_JSON); } -// -// String MAP1_JSON_EXPLICIT_TYPE = "{ type: json, value: "+MAP1_JSON+" }"; -// @Test public void testReadMapNoTypeExpected() { y.read(MAP1_JSON_EXPLICIT_TYPE, null).assertResult(MAP1_OBJ); } -// @Test public void testWriteMapNoTypeExpected() { y.write(MAP1_OBJ, null).assertResult(MAP1_JSON_EXPLICIT_TYPE); } -// -// String MAP1_JSON_OBJECT_OBJECT = "[ { key: { type: string, value: a }, value: { type: int, value: 1 }, "+ -// "{ key: { type: string, value: b }, value: { type: string, value: bbb } ]"; -// @Test public void testReadMapVerbose() { y.read(MAP1_JSON_OBJECT_OBJECT, "map").assertResult(MAP1_OBJ); } -// @Test public void testWriteMapVerbose() { y.write(MAP1_OBJ, "map").assertResult(MAP1_JSON_OBJECT_OBJECT); } -// -// String MAP1_JSON_STRING_OBJECT = "{ a: { type: int, value: 1 }, b: { type: string, value: bbb } ]"; -// @Test public void testReadMapVerboseStringKey() { y.read(MAP1_JSON_STRING_OBJECT, "map").assertResult(MAP1_OBJ); } -// @Test public void testWriteMapVerboseStringKey() { y.write(MAP1_OBJ, "map").assertResult(MAP1_JSON_STRING_OBJECT); } -// @Test public void testReadMapVerboseJsonKey() { y.read(MAP1_JSON_STRING_OBJECT, "map").assertResult(MAP1_OBJ); } -// @Test public void testWriteMapVerboseJsonKey() { y.write(MAP1_OBJ, "map").assertResult(MAP1_JSON_STRING_OBJECT); } + String MAP1_JSON = "{ a: 1, b: bbb }"; + Map MAP1_OBJ = MutableMap.of("a", 1, "b", "bbb"); + + @Test public void testReadMap() { y.read(MAP1_JSON, "json").assertResult(MAP1_OBJ); } + @Test public void testWriteMap() { y.write(MAP1_OBJ, "json").assertResult(MAP1_JSON); } + + String MAP1_JSON_EXPLICIT_TYPE = "{ type: \"map\", value: "+MAP1_JSON+" }"; + @Test public void testReadMapNoTypeExpected() { y.read(MAP1_JSON_EXPLICIT_TYPE, null).assertResult(MAP1_OBJ); } + @Test public void testWriteMapNoTypeExpected() { y.write(MAP1_OBJ, null).assertResult(MAP1_JSON_EXPLICIT_TYPE); } + String MAP1_JSON_OBJECT_OBJECT = "[ { key: { type: string, value: a }, value: { type: int, value: 1 } }, "+ + "{ key: { type: string, value: b }, value: { type: string, value: bbb } } ]"; + @Test public void testReadMapVerbose() { y.read(MAP1_JSON_OBJECT_OBJECT, "map").assertResult(MAP1_OBJ); } + + String MAP1_JSON_STRING_OBJECT = "{ a: { type: int, value: 1 }, b: { type: string, value: bbb } }"; + @Test public void testReadMapVerboseStringKey() { y.read(MAP1_JSON_STRING_OBJECT, "map").assertResult(MAP1_OBJ); } + @Test public void testReadMapVerboseJsonKey() { y.read(MAP1_JSON_STRING_OBJECT, "map").assertResult(MAP1_OBJ); } String LIST1_JSON = "[ a, 1, b ]"; List LIST1_OBJ = MutableList.of("a", 1, "b"); @@ -91,9 +91,28 @@ public class MapListTests { @Test public void testReadSet() { y.read(SET1_JSON, "set").assertResult(SET1_OBJ); } @Test public void testWriteSetJson() { y.write(SET1_OBJ, "json").assertResult(LIST1_JSON); } + @Test public void testReadWithShape() { + y.tr.put("shape", Shape.class); + Shape shape = new Shape().name("my-shape"); + + Map m1 = MutableMap.of("k1", shape); + String MAP_W_SHAPE = "{ k1: { type: shape, fields: { name: my-shape } } }"; + y.read(MAP_W_SHAPE, "map").assertResult(m1); + y.write(m1, "map").assertResult(MAP_W_SHAPE); + y.write(m1, null).assertResult("{ type: map, value: " + MAP_W_SHAPE + " }"); + + Map m2 = MutableMap.of(shape, "v1", "k2", 2); + Map m3 = MutableMap.of(shape, "v1", "k2", 2); + Assert.assertEquals(m2, m3); + String MAP_W_SHAPE_KEY_JSON = "[ { key: { type: shape, fields: { name: my-shape } }, value: v1 }, { k2: 2 } ]"; + String MAP_W_SHAPE_KEY_NON_GENERIC = "[ { key: { type: shape, fields: { name: my-shape } }, value: { type: string, value: v1 } }, { k2: { type: int, value: 2 } } ]"; + y.read(MAP_W_SHAPE_KEY_JSON, "map").assertResult(m2); + y.write(m2, "map").assertResult(MAP_W_SHAPE_KEY_JSON); + y.write(m2, "map").assertResult(MAP_W_SHAPE_KEY_NON_GENERIC); + } // TODO - // map tests + // enums // passing generics from fields // poor man: if field is compatible to mutable list or mutable set then make list<..> or set<..> // rich man: registry can handle generics diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java index c5d492deda..ddd4bc02a6 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java @@ -51,6 +51,8 @@ public boolean equals(Object xo) { public String toString() { return Objects.toStringHelper(this).add("name", name).add("color", color).omitNullValues().toString(); } + @Override + public int hashCode() { return Objects.hashCode(name, color); } public Shape name(String name) { this.name = name; return this; } public Shape color(String color) { this.color = color; return this; } From 85476ab50d6b169b12e99876133864895581d1c6 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 29 Jun 2016 11:53:38 +0100 Subject: [PATCH 17/77] support enums --- .../org/apache/brooklyn/util/yorml/Yorml.java | 2 + .../serializers/InstantiateTypeEnum.java | 101 ++++++++++++++++++ .../InstantiateTypeFromRegistry.java | 13 ++- .../serializers/InstantiateTypePrimitive.java | 12 +-- .../InstantiateTypeWorkerAbstract.java | 10 ++ .../util/yorml/tests/MapListTests.java | 17 ++- .../util/yorml/tests/YormlBasicTests.java | 19 ++++ 7 files changed, 157 insertions(+), 17 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeEnum.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java index 02419257f2..b8c42867c2 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java @@ -24,6 +24,7 @@ import org.apache.brooklyn.util.yorml.internal.YormlConfig; import org.apache.brooklyn.util.yorml.internal.YormlConverter; import org.apache.brooklyn.util.yorml.serializers.FieldsInMapUnderFields; +import org.apache.brooklyn.util.yorml.serializers.InstantiateTypeEnum; import org.apache.brooklyn.util.yorml.serializers.InstantiateTypeFromRegistry; import org.apache.brooklyn.util.yorml.serializers.InstantiateTypeList; import org.apache.brooklyn.util.yorml.serializers.InstantiateTypeMap; @@ -44,6 +45,7 @@ public static Yorml newInstance(YormlTypeRegistry typeRegistry) { return newInstance(typeRegistry, MutableList.of( new FieldsInMapUnderFields(), new InstantiateTypePrimitive(), + new InstantiateTypeEnum(), new InstantiateTypeList(), new InstantiateTypeMap(), new InstantiateTypeFromRegistry() )); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeEnum.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeEnum.java new file mode 100644 index 0000000000..8e39e2f4fd --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeEnum.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.serializers; + +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.yorml.YormlContext; + +public class InstantiateTypeEnum extends YormlSerializerComposition { + + protected YormlSerializerWorker newWorker() { + return new Worker(); + } + + public class Worker extends InstantiateTypeWorkerAbstract { + + public void read() { + if (!canDoRead()) return; + + Class type = null; + boolean fromMap = false; + Maybe value = Maybe.absent(); + + if (getExpectedTypeJava()!=null) { + if (!getExpectedTypeJava().isEnum()) return; + + value = Maybe.of(getYamlObject()); + if (!isJsonPrimitiveObject(value.get())) { + // warn, but try { type: .., value: ... } syntax + warn("Enum "+getExpectedTypeJava()+" is not a string"); + + } else { + type = getExpectedTypeJava(); + } + } + + if (type==null) { + String typeName = readingTypeFromFieldOrExpected(); + if (typeName==null) return; + type = config.getTypeRegistry().getJavaType(typeName); + if (type==null || !type.isEnum()) return; + value = readingValueFromTypeValueMap(); + if (value.isAbsent()) { + warn("No value declared for enum "+type); + return; + } + if (!isJsonPrimitiveObject(value.get())) { + warn("Enum "+getExpectedTypeJava()+" is not a string"); + return; + } + + fromMap = true; + } + + Maybe enumValue = tryCoerceAndNoteError(value.get(), type); + if (enumValue.isAbsent()) return; +// Maybe v = Enums.valueOfIgnoreCase((Class)type, Strings.toString(getYamlObject())); + + storeReadObjectAndAdvance(enumValue.get(), false); + if (fromMap) removeTypeAndValueKeys(); + } + + public void write() { + if (!canDoWrite()) return; + if (!getJavaObject().getClass().isEnum()) return; + + boolean wrap = true; + if (getExpectedTypeJava()!=null) { + if (!getExpectedTypeJava().isEnum()) return; + wrap = false; + } + + Object result = ((Enum)getJavaObject()).name(); + + if (wrap) { + result = writingMapWithTypeAndLiteralValue( + config.getTypeRegistry().getTypeName(getJavaObject()), + result); + } + + context.phaseInsert(YormlContext.StandardPhases.MANIPULATING); + storeWriteObjectAndAdvance(result); + } + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java index 667f282243..d04e45af86 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java @@ -22,6 +22,7 @@ import java.util.List; import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.FieldOrderings; import org.apache.brooklyn.util.javalang.ReflectionPredicates; @@ -53,14 +54,18 @@ public void read() { if (type==null) return; - Object result = config.getTypeRegistry().newInstanceMaybe((String)type, Yorml.newInstance(config)).orNull(); - if (result==null) { - warn("Unknown type '"+type+"'"); + Maybe resultM = config.getTypeRegistry().newInstanceMaybe((String)type, Yorml.newInstance(config)); + if (resultM.isAbsent()) { + Class jt = config.getTypeRegistry().getJavaType((String)type); + String message = jt==null ? "Unknown type '"+type+"'" : "Unable to instantiate type '"+type+"' ("+jt+")"; + RuntimeException exc = ((Maybe.Absent)resultM).getException(); + if (exc!=null) message+=": "+Exceptions.collapseText(exc); + warn(message); return; } addSerializers(type); - storeReadObjectAndAdvance(result, true); + storeReadObjectAndAdvance(resultM.get(), true); if (isYamlMap()) { removeFromYamlKeysOnBlackboard("type"); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypePrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypePrimitive.java index d815916afa..f2e29fc515 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypePrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypePrimitive.java @@ -63,7 +63,7 @@ public void read() { if (expectedJavaType==null) expectedJavaType = getSpecialKnownTypeName(typeName); if (!isJsonPrimitiveType(expectedJavaType) && !isJsonMarkerType(typeName)) return; - value = readingValueFromTypeValueMap(expectedJavaType); + value = readingValueFromTypeValueMap(); if (value.isAbsent()) return; if (tryCoerceAndNoteError(value.get(), expectedJavaType).isAbsent()) return; @@ -73,16 +73,6 @@ public void read() { storeReadObjectAndAdvance(value.get(), false); } - protected Maybe tryCoerceAndNoteError(Object value, Class expectedJavaType) { - if (expectedJavaType==null) return Maybe.of(value); - Maybe coerced = config.getCoercer().tryCoerce(value, expectedJavaType); - if (coerced.isAbsent()) { - // type present but not coercible - error - ReadingTypeOnBlackboard.get(blackboard).addNote("Cannot interpret '"+value+"' as primitive "+expectedJavaType); - } - return coerced; - } - public void write() { if (!canDoWrite()) return; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java index c9f9210023..a5146ffec2 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java @@ -95,6 +95,16 @@ protected void storeReadObjectAndAdvance(Object result, boolean addPhases) { context.phaseAdvance(); } + protected Maybe tryCoerceAndNoteError(Object value, Class expectedJavaType) { + if (expectedJavaType==null) return Maybe.of(value); + Maybe coerced = config.getCoercer().tryCoerce(value, expectedJavaType); + if (coerced.isAbsent()) { + // type present but not coercible - error + ReadingTypeOnBlackboard.get(blackboard).addNote("Cannot interpret '"+value+"' as primitive "+expectedJavaType); + } + return coerced; + } + protected void storeWriteObjectAndAdvance(Object jo) { context.setYamlObject(jo); context.phaseAdvance(); diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java index 91442ff40d..2ba132f043 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java @@ -18,6 +18,7 @@ */ package org.apache.brooklyn.util.yorml.tests; +import java.math.RoundingMode; import java.util.List; import java.util.Map; import java.util.Set; @@ -111,11 +112,23 @@ public class MapListTests { y.write(m2, "map").assertResult(MAP_W_SHAPE_KEY_NON_GENERIC); } + @Test public void testReadWithEnum() { + y.tr.put("rounding-mode", RoundingMode.class); + + Map m1 = MutableMap.of("k1", RoundingMode.UP); + String MAP_W_RM = "{ k1: { type: rounding-mode, value: UP } }"; + y.read(MAP_W_RM, "map").assertResult(m1); + y.write(m1, "map").assertResult(MAP_W_RM); + y.write(m1, null).assertResult("{ type: map, value: " + MAP_W_RM + " }"); + + String MAP_W_RM_TYPE_KNOWN = "{ k1: UP }"; + y.read(MAP_W_RM_TYPE_KNOWN, "map").assertResult(m1); + y.write(m1, "map").assertResult(MAP_W_RM_TYPE_KNOWN); + } + // TODO - // enums // passing generics from fields // poor man: if field is compatible to mutable list or mutable set then make list<..> or set<..> // rich man: registry can handle generics - // maps w generics } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java index ddd4bc02a6..ff7d9c240a 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.util.yorml.tests; +import java.math.RoundingMode; + import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.Jsonya; import org.apache.brooklyn.util.exceptions.Exceptions; @@ -134,6 +136,23 @@ public void testIntPrimitiveWhereTypeUnknown() { write(42).assertResult("{ type: int, value: 42 }"). read("{ type: int, value: 42 }", null).assertResult(42); } + @Test + public void testEnumWhereTypeKnown() { + YormlTestFixture.newInstance(). + addType("rounding-mode", RoundingMode.class). + write(RoundingMode.HALF_EVEN, "rounding-mode").assertResult(RoundingMode.HALF_EVEN.name()). + read(RoundingMode.HALF_EVEN.name(), "rounding-mode").assertResult(RoundingMode.HALF_EVEN). + read("half-even", "rounding-mode").assertResult(RoundingMode.HALF_EVEN); + } + + @Test + public void testEnumWhereTypeUnknown() { + String json = "{ type: rounding-mode, value: "+RoundingMode.HALF_EVEN.name()+" }"; + YormlTestFixture.newInstance(). + addType("rounding-mode", RoundingMode.class). + write(RoundingMode.HALF_EVEN).assertResult(json). + read(json, null).assertResult(RoundingMode.HALF_EVEN); + } @Test public void testRegisteredType() { From cd4f9a4b7db800a10c65f28a893f7dc47ff7122e Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 29 Jun 2016 13:34:13 +0100 Subject: [PATCH 18/77] easy way to make all fields explicit, and related fixes also failing test for map/list generics which i'm about to fix --- .../internal/SerializersOnBlackboard.java | 2 +- .../util/yorml/internal/YormlConverter.java | 4 ++ .../util/yorml/internal/YormlUtils.java | 32 ++++++++++ .../yorml/serializers/AllFieldsExplicit.java | 62 +++++++++++++++++++ .../util/yorml/serializers/ExplicitField.java | 21 ++++--- .../InstantiateTypeFromRegistry.java | 23 +------ .../YormlSerializerComposition.java | 5 ++ .../util/yorml/tests/ExplicitFieldTests.java | 25 ++++++++ .../util/yorml/tests/MapListTests.java | 40 ++++++++++++ .../yorml/tests/MockYormlTypeRegistry.java | 6 +- .../util/yorml/tests/YormlTestFixture.java | 4 +- 11 files changed, 190 insertions(+), 34 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/AllFieldsExplicit.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java index 6848e29feb..10f7fcce42 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java @@ -52,7 +52,7 @@ public static SerializersOnBlackboard create(Map blackboard) { List expectedTypeSerializers = MutableList.of(); List postSerializers = MutableList.of(); - public void addInstantiatedTypeSerializers(Iterable instantiatedTypeSerializers) { + public void addInstantiatedTypeSerializers(Iterable instantiatedTypeSerializers) { Iterables.addAll(this.instantiatedTypeSerializers, instantiatedTypeSerializers); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java index 309acd9df6..48cdd71be9 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java @@ -80,13 +80,17 @@ protected void loopOverSerializers(YormlContext context) { while (context.phaseStepAdvance() List getAllNonTransientNonStaticFieldNames(Class type, T optionalInstanceToRequireNonNullFieldValue) { + List result = MutableList.of(); + List fields = Reflections.findFields(type, + null, + FieldOrderings.ALPHABETICAL_FIELD_THEN_SUB_BEST_FIRST); + Field lastF = null; + for (Field f: fields) { + if (ReflectionPredicates.IS_FIELD_NON_TRANSIENT.apply(f) && ReflectionPredicates.IS_FIELD_NON_STATIC.apply(f)) { + if (optionalInstanceToRequireNonNullFieldValue==null || + Reflections.getFieldValueMaybe(optionalInstanceToRequireNonNullFieldValue, f).isPresentAndNonNull()) { + String name = f.getName(); + if (lastF!=null && lastF.getName().equals(f.getName())) { + // if field is shadowed use FQN + name = f.getDeclaringClass().getCanonicalName()+"."+name; + } + result.add(name); + } + } + lastF = f; + } + return result; + } + + @SuppressWarnings("unchecked") + public static List getAllNonTransientNonStaticFieldNamesUntyped(Class type, Object optionalInstanceToRequireNonNullFieldValue) { + return getAllNonTransientNonStaticFieldNames((Class)type, optionalInstanceToRequireNonNullFieldValue); + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/AllFieldsExplicit.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/AllFieldsExplicit.java new file mode 100644 index 0000000000..b946eca702 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/AllFieldsExplicit.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.serializers; + +import java.util.List; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.yorml.internal.SerializersOnBlackboard; +import org.apache.brooklyn.util.yorml.internal.YormlUtils; + +/* Adds ExplicitField instances for all fields declared on the type */ +public class AllFieldsExplicit extends YormlSerializerComposition { + + protected YormlSerializerWorker newWorker() { + return new Worker(); + } + + /** marker on blackboard indicating that we have run */ + static class DoneAllFieldsExplicit {} + + public class Worker extends YormlSerializerWorker { + + public void read() { run(); } + public void write() { run(); } + + protected void run() { + if (!hasJavaObject()) return; + if (blackboard.containsKey(DoneAllFieldsExplicit.class.getName())) return; + + // mark done + blackboard.put(DoneAllFieldsExplicit.class.getName(), new DoneAllFieldsExplicit()); + + SerializersOnBlackboard serializers = SerializersOnBlackboard.get(blackboard); + + List fields = YormlUtils.getAllNonTransientNonStaticFieldNames(getJavaObject().getClass(), null); + for (String f: fields) { + ExplicitField ef = new ExplicitField(); + ef.fieldName = f; + serializers.addInstantiatedTypeSerializers(MutableList.of(ef)); + } + + context.phaseRestart(); + } + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java index c9a50cb25d..b0e226827d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Set; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.yorml.YormlContext; @@ -128,15 +129,16 @@ public void read() { if (!readyForMainEvent()) return; if (!hasJavaObject()) return; if (!isYamlMap()) return; + if (!hasYamlKeysOnBlackboard()) return; + @SuppressWarnings("unchecked") Map fields = peekFromYamlKeysOnBlackboard("fields", Map.class).orNull(); + if (fields==null) { + // create the fields if needed; FieldsInFieldsMap will remove (even if empty) + fields = MutableMap.of(); + YamlKeysOnBlackboard.getOrCreate(blackboard, null).yamlKeysToReadToJava.put("fields", fields); + } - /* - * if fields is null either we are too early (not yet set by instantiate-type) - * or too late (already read in to java), so we bail -- this yaml key cannot be handled at this time - */ - if (fields==null) return; - int keysMatched = 0; for (String aliasO: getKeyNameAndAliases()) { Set aliasMangles = ExplicitFieldsBlackboard.get(blackboard).isAliasesStrict(fieldName) ? @@ -179,6 +181,10 @@ public void write() { @SuppressWarnings("unchecked") Map fields = getFromYamlMap("fields", Map.class).orNull(); + /* + * if fields is null either we are too early (not yet set by instantiate-type) + * or too late (already read in to java), so we bail -- this yaml key cannot be handled at this time + */ if (fields==null) return; Maybe dv = ExplicitFieldsBlackboard.get(blackboard).getDefault(fieldName); @@ -212,7 +218,8 @@ public void write() { } // and move the `fields` object to the end getYamlMap().remove("fields"); - getYamlMap().put("fields", fields); + if (!fields.isEmpty()) + getYamlMap().put("fields", fields); // rerun this phase again, as we've changed it context.phaseInsert(StandardPhases.MANIPULATING); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java index d04e45af86..de9d144d28 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java @@ -18,18 +18,14 @@ */ package org.apache.brooklyn.util.yorml.serializers; -import java.lang.reflect.Field; -import java.util.List; - import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.javalang.FieldOrderings; -import org.apache.brooklyn.util.javalang.ReflectionPredicates; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yorml.Yorml; import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.internal.YormlUtils; public class InstantiateTypeFromRegistry extends YormlSerializerComposition { @@ -92,22 +88,7 @@ public void write() { // collect fields JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); - List fields = Reflections.findFields(getJavaObject().getClass(), - null, - FieldOrderings.ALPHABETICAL_FIELD_THEN_SUB_BEST_FIRST); - Field lastF = null; - for (Field f: fields) { - Maybe v = Reflections.getFieldValueMaybe(getJavaObject(), f); - if (ReflectionPredicates.IS_FIELD_NON_TRANSIENT.apply(f) && ReflectionPredicates.IS_FIELD_NON_STATIC.apply(f) && v.isPresentAndNonNull()) { - String name = f.getName(); - if (lastF!=null && lastF.getName().equals(f.getName())) { - // if field is shadowed use FQN - name = f.getDeclaringClass().getCanonicalName()+"."+name; - } - fib.fieldsToWriteFromJava.add(name); - } - lastF = f; - } + fib.fieldsToWriteFromJava.addAll(YormlUtils.getAllNonTransientNonStaticFieldNamesUntyped(getJavaObject().getClass(), getJavaObject())); context.phaseInsert(YormlContext.StandardPhases.HANDLING_FIELDS, YormlContext.StandardPhases.MANIPULATING); storeWriteObjectAndAdvance(map); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java index 317f61be4b..8c1c7d6af8 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java @@ -105,6 +105,11 @@ protected Maybe peekFromYamlKeysOnBlackboard(String key, Class expecte if (expectedType!=null && !expectedType.isInstance(v)) return Maybe.absent(); return Maybe.of((T)v); } + protected boolean hasYamlKeysOnBlackboard() { + YamlKeysOnBlackboard ykb = YamlKeysOnBlackboard.peek(blackboard); + if (ykb==null || ykb.yamlKeysToReadToJava==null || ykb.yamlKeysToReadToJava.isEmpty()) return false; + return true; + } protected void removeFromYamlKeysOnBlackboard(String ...keys) { YamlKeysOnBlackboard ykb = YamlKeysOnBlackboard.peek(blackboard); for (String key: keys) { diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java index b9f032c3b7..a3a1b9b093 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ExplicitFieldTests.java @@ -25,6 +25,7 @@ import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.yorml.YormlSerializer; +import org.apache.brooklyn.util.yorml.serializers.AllFieldsExplicit; import org.apache.brooklyn.util.yorml.serializers.ExplicitField; import org.apache.brooklyn.util.yorml.tests.YormlBasicTests.Shape; import org.apache.brooklyn.util.yorml.tests.YormlBasicTests.ShapeWithSize; @@ -46,6 +47,8 @@ protected static YormlTestFixture simpleExplicitFieldFixture() { static String SIMPLE_IN_WITHOUT_TYPE = "{ name: diamond, fields: { color: black } }"; static Shape SIMPLE_OUT = new Shape().name("diamond").color("black"); + static String SIMPLE_IN_NAME_ONLY_WITHOUT_TYPE = "{ name: diamond }"; + static Shape SIMPLE_OUT_NAME_ONLY = new Shape().name("diamond"); @Test public void testReadExplicitField() { @@ -59,6 +62,19 @@ public void testWriteExplicitField() { write( SIMPLE_OUT, "shape" ). assertResult( SIMPLE_IN_WITHOUT_TYPE ); } + + @Test + public void testReadExplicitFieldNameOnly() { + simpleExplicitFieldFixture(). + read( SIMPLE_IN_NAME_ONLY_WITHOUT_TYPE, "shape" ). + assertResult( SIMPLE_OUT_NAME_ONLY ); + } + @Test + public void testWriteExplicitFieldNameOnly() { + simpleExplicitFieldFixture(). + write( SIMPLE_OUT_NAME_ONLY, "shape" ). + assertResult( SIMPLE_IN_NAME_ONLY_WITHOUT_TYPE ); + } static String SIMPLE_IN_WITH_TYPE = "{ type: shape, name: diamond, fields: { color: black } }"; @@ -284,4 +300,13 @@ public void testFieldNameManglesExcludedWhenStrict() { } } + static String SIMPLE_IN_ALL_FIELDS_EXPLICIT = "{ color: black, name: diamond }"; + @Test public void testAllFieldsExplicit() { + YormlTestFixture y = YormlTestFixture.newInstance(). + addType("shape", Shape.class, MutableList.of(new AllFieldsExplicit())); + + y.read( SIMPLE_IN_ALL_FIELDS_EXPLICIT, "shape" ).assertResult( SIMPLE_OUT ). + write( SIMPLE_OUT, "shape" ).assertResult( SIMPLE_IN_ALL_FIELDS_EXPLICIT ); + } + } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java index 2ba132f043..88885c2377 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java @@ -21,12 +21,14 @@ import java.math.RoundingMode; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.yorml.serializers.AllFieldsExplicit; import org.apache.brooklyn.util.yorml.tests.YormlBasicTests.Shape; import org.testng.Assert; import org.testng.annotations.Test; @@ -125,6 +127,44 @@ public class MapListTests { y.read(MAP_W_RM_TYPE_KNOWN, "map").assertResult(m1); y.write(m1, "map").assertResult(MAP_W_RM_TYPE_KNOWN); } + + static class TestingGenericsOnFields { + List list; + Map map; + Set set; + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TestingGenericsOnFields)) return false; + TestingGenericsOnFields gf = (TestingGenericsOnFields)obj; + return Objects.equals(list, gf.list) && Objects.equals(map, gf.map) && Objects.equals(set, gf.set); + } + @Override + public int hashCode() { + return Objects.hash(list, map, set); + } + } + + @Test public void testGenericListMapSet() { + y.tr.put("gf", TestingGenericsOnFields.class, MutableList.of(new AllFieldsExplicit())); + + TestingGenericsOnFields gf; + + gf = new TestingGenericsOnFields(); + gf.list = MutableList.of(RoundingMode.UP); + y.read("{ list: [ UP ] }", "gf").assertResult(gf); + y.write(gf, "gf").assertResult(gf); + + gf = new TestingGenericsOnFields(); + gf.map = MutableMap.of(RoundingMode.UP, RoundingMode.DOWN); + y.read("{ map: { UP: DOWN } }", "gf").assertResult(gf); + y.write(gf, "gf").assertResult(gf); + + gf = new TestingGenericsOnFields(); + gf.set = MutableSet.of(RoundingMode.UP); + y.read("{ set: [ UP ] }", "gf").assertResult(gf); + y.write(gf, "gf").assertResult(gf); + } // TODO // passing generics from fields diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java index e1ab45efff..6216e35862 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java @@ -49,7 +49,7 @@ static class MockRegisteredType { final List serializers; final Object yamlDefinition; - public MockRegisteredType(String id, String parentType, Class javaType, Collection interfaceTypes, List serializers, Object yamlDefinition) { + public MockRegisteredType(String id, String parentType, Class javaType, Collection interfaceTypes, List serializers, Object yamlDefinition) { super(); this.id = id; this.parentType = parentType; @@ -114,7 +114,7 @@ protected Class getJavaType(MockRegisteredType registeredType, String typeNam public void put(String typeName, Class javaType) { put(typeName, javaType, null); } - public void put(String typeName, Class javaType, List serializers) { + public void put(String typeName, Class javaType, List serializers) { types.put(typeName, new MockRegisteredType(typeName, "java:"+javaType.getName(), javaType, MutableSet.of(), serializers, null)); } @@ -123,7 +123,7 @@ public void put(String typeName, String yamlDefinition) { put(typeName, yamlDefinition, null); } @SuppressWarnings("unchecked") - public void put(String typeName, String yamlDefinition, List serializers) { + public void put(String typeName, String yamlDefinition, List serializers) { Object yamlObject = Iterables.getOnlyElement( Yamls.parseAll(yamlDefinition) ); if (!(yamlObject instanceof Map)) throw new IllegalArgumentException("Mock only supports map definitions"); diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java index d138d1e838..924abcde78 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java @@ -103,7 +103,7 @@ static void assertEqualsIgnoringQuotes(Object s1, Object s2, String message) { } public YormlTestFixture addType(String name, Class type) { tr.put(name, type); return this; } - public YormlTestFixture addType(String name, Class type, List serializers) { tr.put(name, type, serializers); return this; } + public YormlTestFixture addType(String name, Class type, List serializers) { tr.put(name, type, serializers); return this; } public YormlTestFixture addType(String name, String yamlDefinition) { tr.put(name, yamlDefinition); return this; } - public YormlTestFixture addType(String name, String yamlDefinition, List serializers) { tr.put(name, yamlDefinition, serializers); return this; } + public YormlTestFixture addType(String name, String yamlDefinition, List serializers) { tr.put(name, yamlDefinition, serializers); return this; } } \ No newline at end of file From fa9535a2762ad56054b75aad5883a7b06cc9ecaf Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 29 Jun 2016 14:37:28 +0100 Subject: [PATCH 19/77] support generics from fields for map/list/set --- .../util/yorml/internal/YormlUtils.java | 38 +++++++++++++++++ .../serializers/FieldsInMapUnderFields.java | 5 ++- .../serializers/InstantiateTypeList.java | 20 +++++---- .../yorml/serializers/InstantiateTypeMap.java | 10 +++-- .../org/apache/brooklyn/util/yorml/sketch.md | 3 +- .../util/yorml/tests/MapListTests.java | 42 ++++++++++++------- .../yorml/tests/MockYormlTypeRegistry.java | 7 +++- 7 files changed, 93 insertions(+), 32 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java index f6ca3a01e9..93c8dab7f1 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java @@ -19,6 +19,8 @@ package org.apache.brooklyn.util.yorml.internal; import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.List; import java.util.Map; @@ -162,4 +164,40 @@ public static List getAllNonTransientNonStaticFieldNames(Class ty public static List getAllNonTransientNonStaticFieldNamesUntyped(Class type, Object optionalInstanceToRequireNonNullFieldValue) { return getAllNonTransientNonStaticFieldNames((Class)type, optionalInstanceToRequireNonNullFieldValue); } + + /** + * Provides poor man's generics -- we decorate when looking at a field, + * and strip when looking up in the registry. + *

+ * It's not that bad as fields are the *only* place in java where generic information is available. + *

+ * However we don't do them recursively at all (so eg a List> becomes a List). + * TODO That wouldn't be hard to fix. + */ + public static String getFieldTypeName(Field ff, YormlConfig config) { + String baseTypeName = config.getTypeRegistry().getTypeNameOfClass(ff.getType()); + String typeName = baseTypeName; + Type type = ff.getGenericType(); + if (type instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType)type; + if (pt.getActualTypeArguments().length>0) { + typeName += "<"; + for (int i=0; i0) typeName += ","; + Type ft = pt.getActualTypeArguments()[i]; + Class fc = null; + if (fc==null && ft instanceof ParameterizedType) ft = ((ParameterizedType)ft).getRawType(); + if (fc==null && ft instanceof Class) fc = (Class)ft; + String rfc = config.getTypeRegistry().getTypeNameOfClass(fc); + if (rfc==null) { + // cannot resolve generics + return baseTypeName; + } + typeName += rfc; + } + typeName += ">"; + } + } + return typeName; + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java index 3f15c3d8e4..244e6e1267 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java @@ -31,6 +31,7 @@ import org.apache.brooklyn.util.yorml.YormlContext; import org.apache.brooklyn.util.yorml.YormlContextForRead; import org.apache.brooklyn.util.yorml.YormlContextForWrite; +import org.apache.brooklyn.util.yorml.internal.YormlUtils; public class FieldsInMapUnderFields extends YormlSerializerComposition { @@ -59,7 +60,7 @@ public void read() { if (Modifier.isStatic(ff.getModifiers())) { // as above } else { - String fieldType = config.getTypeRegistry().getTypeNameOfClass(ff.getType()); + String fieldType = YormlUtils.getFieldTypeName(ff, config); Object v2 = converter.read( new YormlContextForRead(v, context.getJsonPath()+"/"+f, fieldType) ); ff.setAccessible(true); @@ -97,7 +98,7 @@ public void write() { // silently drop null fields } else { Field ff = Reflections.findFieldMaybe(getJavaObject().getClass(), f).get(); - String fieldType = config.getTypeRegistry().getTypeNameOfClass(ff.getType()); + String fieldType = YormlUtils.getFieldTypeName(ff, config); Object v2 = converter.write(new YormlContextForWrite(v.get(), context.getJsonPath()+"/"+f, fieldType) ); fields.put(f, v2); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java index 3ee209af4c..fd845e4a95 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java @@ -225,13 +225,13 @@ private Object newInstance(Class javaType, String explicitTypeName) { } // and set the subtype - if (gp.subTypeCount()!=1) return null; - - String subType = Iterables.getOnlyElement(gp.subTypes); - if (genericSubType!=null && !genericSubType.equals(subType)) { - log.debug("Got different generic subtype, expected "+context.getExpectedType()+" but declared "+explicitTypeName+"; preferring declared"); + if (gp.subTypeCount()==1) { + String subType = Iterables.getOnlyElement(gp.subTypes); + if (genericSubType!=null && !genericSubType.equals(subType)) { + log.debug("Got different generic subtype, expected "+context.getExpectedType()+" but declared "+explicitTypeName+"; preferring declared"); + } + genericSubType = subType; } - genericSubType = subType; } Class concreteJavaType = null; @@ -309,9 +309,13 @@ public void write() { } genericSubType = Iterables.getOnlyElement(gp.subTypes); } - if (expectedJavaType==null) { - expectedJavaType = typeAliases.get(gp.baseType); + Class newExpectedType = typeAliases.get(gp.baseType); + if (newExpectedType!=null && (expectedJavaType==null || expectedJavaType.isAssignableFrom(newExpectedType))) { + expectedJavaType = newExpectedType; } + String expectedJavaTypeName = typesAliased.get(expectedJavaType); + if (expectedJavaTypeName!=null) expectedJavaType = typeAliases.get(expectedJavaTypeName); + else expectedJavaTypeName = config.getTypeRegistry().getTypeNameOfClass(expectedJavaType); String actualTypeName = typesAliased.get(getJavaObject().getClass()); boolean isBasicCollectionType = (actualTypeName!=null); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeMap.java index 28ac0109d5..773bb1428e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeMap.java @@ -130,10 +130,14 @@ public void read() { // create actualType, then populate from value Map jo = null; - if (typeAliases.get(actualBaseTypeName)!=null) { - jo = (Map) Reflections.invokeConstructorFromArgsIncludingPrivate(typeAliases.get(actualBaseTypeName)).get(); + if (typeAliases.get(alias)!=null) { + jo = (Map) Reflections.invokeConstructorFromArgsIncludingPrivate(typeAliases.get(alias)).get(); } else { - jo = (Map) config.getTypeRegistry().newInstance(actualBaseTypeName, Yorml.newInstance(config)); + try { + jo = (Map) config.getTypeRegistry().newInstance(actualBaseTypeName, Yorml.newInstance(config)); + } catch (Exception e) { + throw new IllegalStateException("Cannot instantiate "+actualBaseTypeName, e); + } } @SuppressWarnings("unchecked") Map jom = (Map)jo; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md index 41051bcf26..65d0b036ba 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md @@ -518,9 +518,8 @@ to be shown. ### TODO -* maps and lists, with generics -* Config/data keys * complex syntax, type as key, etc +* config/data keys * defining serializers and linking to brooklyn diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java index 88885c2377..658d98e938 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java @@ -143,32 +143,44 @@ public boolean equals(Object obj) { public int hashCode() { return Objects.hash(list, map, set); } + @Override + public String toString() { + return com.google.common.base.Objects.toStringHelper(this).add("list", list).add("map", map).add("set", set).omitNullValues().toString(); + } } - @Test public void testGenericListMapSet() { + @Test public void testGenericList() { y.tr.put("gf", TestingGenericsOnFields.class, MutableList.of(new AllFieldsExplicit())); - TestingGenericsOnFields gf; gf = new TestingGenericsOnFields(); gf.list = MutableList.of(RoundingMode.UP); - y.read("{ list: [ UP ] }", "gf").assertResult(gf); - y.write(gf, "gf").assertResult(gf); - + y.reading("{ list: [ UP ] }", "gf").writing(gf, "gf").doReadWriteAssertingJsonMatch(); + } + @Test public void testGenericMap() { + y.tr.put("gf", TestingGenericsOnFields.class, MutableList.of(new AllFieldsExplicit())); + TestingGenericsOnFields gf; + String json; gf = new TestingGenericsOnFields(); gf.map = MutableMap.of(RoundingMode.UP, RoundingMode.DOWN); - y.read("{ map: { UP: DOWN } }", "gf").assertResult(gf); - y.write(gf, "gf").assertResult(gf); - + json = "{ map: [ { key: UP, value: DOWN } ] }"; + y.reading(json, "gf").writing(gf, "gf").doReadWriteAssertingJsonMatch(); + // TODO make it smart enough to realize all keys are strings and adjust, so (a) this works, and (b) we can swap json2 with json above + // (but enum keys are not a high priority) +// String json2 = "{ map: { UP: DOWN } }"; +// y.read(json2, "gf").assertResult(gf); + } + + @Test public void testGenericListSet() { + y.tr.put("gf", TestingGenericsOnFields.class, MutableList.of(new AllFieldsExplicit())); + TestingGenericsOnFields gf; + String json; gf = new TestingGenericsOnFields(); gf.set = MutableSet.of(RoundingMode.UP); - y.read("{ set: [ UP ] }", "gf").assertResult(gf); - y.write(gf, "gf").assertResult(gf); + json = "{ set: [ UP ] }"; + String json2 = "{ set: { type: set, value: [ UP ] } }"; + y.read(json2, "gf").assertResult(gf); + y.reading(json, "gf").writing(gf, "gf").doReadWriteAssertingJsonMatch(); } - - // TODO - // passing generics from fields - // poor man: if field is compatible to mutable list or mutable set then make list<..> or set<..> - // rich man: registry can handle generics } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java index 6216e35862..8411f7daf1 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java @@ -84,6 +84,9 @@ public Maybe newInstanceMaybe(String typeName, Yorml yorml) { @Override public Class getJavaType(String typeName) { + if (typeName==null) return null; + // string generics here + if (typeName.indexOf('<')>0) typeName = typeName.substring(0, typeName.indexOf('<')); return getJavaType(types.get(typeName), typeName); } @@ -146,6 +149,7 @@ public String getTypeName(Object obj) { @Override public String getTypeNameOfClass(Class type) { + if (type==null) return null; for (Map.Entry t: types.entrySet()) { if (type.equals(t.getValue().javaType) && t.getValue().yamlDefinition==null) return t.getKey(); } @@ -156,8 +160,7 @@ protected String getDefaultTypeNameOfClass(Class type) { Maybe primitive = Boxing.getPrimitiveName(type); if (primitive.isPresent()) return primitive.get(); if (String.class.equals(type)) return "string"; - // TODO map and list? - + // map and list handled by those serializers return "java:"+type.getName(); } From e4235f388caab21a3bb8e50f923f50faa6a5a9d6 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 30 Jun 2016 13:36:17 +0100 Subject: [PATCH 20/77] support clever parsing of singleton maps --- .../internal/SerializersOnBlackboard.java | 6 + .../util/yorml/internal/YormlUtils.java | 32 +++ .../serializers/ConvertSingletonMap.java | 191 ++++++++++++++++++ .../serializers/InstantiateTypeList.java | 20 +- .../InstantiateTypeWorkerAbstract.java | 6 - .../YormlSerializerComposition.java | 8 + .../org/apache/brooklyn/util/yorml/sketch.md | 59 +++++- .../yorml/tests/ConvertSingletonMapTests.java | 102 ++++++++++ .../util/yorml/tests/MapListTests.java | 6 + .../util/yorml/tests/YormlTestFixture.java | 2 +- 10 files changed, 418 insertions(+), 14 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ConvertSingletonMap.java create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ConvertSingletonMapTests.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java index 10f7fcce42..86ef26b41d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java @@ -60,4 +60,10 @@ public Iterable getSerializers() { return Iterables.concat(preSerializers, instantiatedTypeSerializers, expectedTypeSerializers, postSerializers); } + public static boolean isAddedByTypeInstantiation(Map blackboard, YormlSerializer serializer) { + SerializersOnBlackboard sb = get(blackboard); + if (sb!=null && sb.instantiatedTypeSerializers.contains(serializer)) return true; + return false; + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java index 93c8dab7f1..90d779ce5d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java @@ -32,6 +32,7 @@ import org.apache.brooklyn.util.text.Strings; import com.google.common.annotations.Beta; +import com.google.common.base.Objects; public class YormlUtils { @@ -200,4 +201,35 @@ public static String getFieldTypeName(Field ff, YormlConfig config) { } return typeName; } + + /** add the given defaults to the target, ignoring any where the key is already present; returns number added */ + public static int addDefaults(Map defaults, Map target) { + int i=0; + if (defaults!=null) for (String key: defaults.keySet()) { + if (!target.containsKey(key)) { + target.put(key, defaults.get(key)); + i++; + } + } + return i; + } + + + /** removes the given defaults from the target, where the key and value match, + * ignoring any where the key is already present; returns number removed */ + public static int removeDefaults(Map defaults, Map target) { + int i=0; + if (defaults!=null && target!=null) for (String key: defaults.keySet()) { + if (target.containsKey(key)) { + Object v = target.get(key); + Object dv = defaults.get(key); + if (Objects.equal(v, dv)) { + target.remove(key); + i++; + } + } + } + return i; + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ConvertSingletonMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ConvertSingletonMap.java new file mode 100644 index 0000000000..0a34e6695b --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ConvertSingletonMap.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.serializers; + +import java.util.Map; + +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yorml.internal.SerializersOnBlackboard; +import org.apache.brooklyn.util.yorml.internal.YormlUtils; + +/* + * key-for-key: type + * key-for-primitive-value: type || key-for-any-value: ... || key-for-list-value: || key-for-map-value + * || merge-with-map-value + * defaults: { type: explicit-field } + */ +public class ConvertSingletonMap extends YormlSerializerComposition { + + public ConvertSingletonMap() { } + + public ConvertSingletonMap(String keyForKey, String keyForAnyValue, String keyForPrimitiveValue, String keyForListValue, + String keyForMapValue, Boolean mergeWithMapValue, Map defaults) { + super(); + this.keyForKey = keyForKey; + this.keyForAnyValue = keyForAnyValue; + this.keyForPrimitiveValue = keyForPrimitiveValue; + this.keyForListValue = keyForListValue; + this.keyForMapValue = keyForMapValue; + this.mergeWithMapValue = mergeWithMapValue; + this.defaults = defaults; + } + + protected YormlSerializerWorker newWorker() { + return new Worker(); + } + + public final static String DEFAULT_KEY_FOR_KEY = ".key"; + public final static String DEFAULT_KEY_FOR_VALUE = ".value"; + + String keyForKey = DEFAULT_KEY_FOR_KEY; + String keyForAnyValue = DEFAULT_KEY_FOR_VALUE; + String keyForPrimitiveValue; + String keyForListValue; + String keyForMapValue; + Boolean mergeWithMapValue; + Map defaults; + + public static class ConvertSingletonApplied {} + + public class Worker extends YormlSerializerWorker { + public void read() { + if (!context.isPhase(YormlContext.StandardPhases.MANIPULATING)) return; + // runs before type instantiated + if (hasJavaObject()) return; + + if (!isYamlMap()) return; + if (getYamlMap().size()!=1) return; + // don't run multiple times + if (blackboard.put(ConvertSingletonMap.class.getName(), new ConvertSingletonApplied())!=null) return; + + // it *is* a singleton map + Object key = getYamlMap().keySet().iterator().next(); + Object value = getYamlMap().values().iterator().next(); + + // key should always be primitive + if (!isJsonPrimitiveObject(key)) return; + + Map newYamlMap = MutableMap.of(); + + newYamlMap.put(keyForKey, key); + + if (isJsonPrimitiveObject(value) && keyForPrimitiveValue!=null) { + newYamlMap.put(keyForPrimitiveValue, value); + } else if (value instanceof Map) { + boolean merge; + if (mergeWithMapValue==null) merge = ((Map)value).containsKey(keyForKey) || keyForMapValue==null; + else merge = mergeWithMapValue; + if (merge) { + newYamlMap.putAll((Map)value); + } else { + newYamlMap.put(keyForMapValue != null ? keyForMapValue : keyForAnyValue, value); + } + } else if (value instanceof Iterable && keyForListValue!=null) { + newYamlMap.put(keyForListValue, value); + } else { + newYamlMap.put(keyForAnyValue, value); + } + + YormlUtils.addDefaults(defaults, newYamlMap); + + context.setYamlObject(newYamlMap); + context.phaseRestart(); + } + + String OUR_PHASE = "manipulate-convert-singleton"; + + public void write() { + if (!isYamlMap()) return; + // don't run if we're only added after instantiating the type (because then we couldn't read back!) + if (SerializersOnBlackboard.isAddedByTypeInstantiation(blackboard, ConvertSingletonMap.this)) return; + + if (context.isPhase(YormlContext.StandardPhases.MANIPULATING) && !context.seenPhase(OUR_PHASE)) { + // finish manipulations before seeking to apply this + context.phaseInsert(OUR_PHASE); + return; + } + if (!context.isPhase(OUR_PHASE)) return; + + // don't run multiple times + if (blackboard.put(ConvertSingletonMap.class.getName(), new ConvertSingletonApplied())!=null) return; + + if (!getYamlMap().containsKey(keyForKey)) return; + Object newKey = getYamlMap().get(keyForKey); + if (!isJsonPrimitiveObject(newKey)) { + // NB this is potentially irreversible - + // e.g. if given say we want for { color: red, xxx: yyy } to write { red: { xxx: yyy } } + // but if we have { color: { type: string, value: red } } and don't rewrite + // user will end up with { color: color, type: string, value: red } + // ... so keyForKey should probably be reserved for things which are definitely primitives + return; + } + + Map yamlMap = MutableMap.copyOf(getYamlMap()); + yamlMap.remove(keyForKey); + + YormlUtils.removeDefaults(defaults, yamlMap); + + Object newValue = null; + + if (yamlMap.size()==1) { + Object remainingKey = yamlMap.keySet().iterator().next(); + if (remainingKey!=null) { + Object remainingObject = yamlMap.values().iterator().next(); + + if (remainingObject instanceof Map) { + // NB can only merge to map if merge false or merge null and map key specified + if (!Boolean.TRUE.equals(mergeWithMapValue)) { + if (remainingKey.equals(keyForMapValue)) { + newValue = remainingObject; + } else if (keyForMapValue==null && remainingKey.equals(keyForAnyValue) && Boolean.FALSE.equals(mergeWithMapValue)) { + newValue = remainingObject; + } + } + } else if (remainingObject instanceof Iterable) { + if (remainingKey.equals(keyForListValue)) { + newValue = remainingObject; + } else if (keyForListValue==null && remainingKey.equals(keyForAnyValue) && Boolean.FALSE.equals(mergeWithMapValue)) { + newValue = remainingObject; + } + } else if (isJsonPrimitiveObject(remainingObject)) { + if (remainingKey.equals(keyForPrimitiveValue)) { + newValue = remainingObject; + } else if (keyForPrimitiveValue==null && remainingKey.equals(keyForAnyValue) && Boolean.FALSE.equals(mergeWithMapValue)) { + newValue = remainingObject; + } + } + } + } + + if (newValue==null && !Boolean.FALSE.equals(mergeWithMapValue)) { + if (keyForMapValue==null && keyForAnyValue==null) { + // if keyFor{Map,Any}Value was supplied it will steal what we want to merge, + // so only apply if those are both null + newValue = yamlMap; + } + } + if (newValue==null) return; // doesn't apply + + context.setYamlObject(MutableMap.of(newKey, newValue)); + context.phaseRestart(); + } + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java index fd845e4a95..2e53c767fc 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java @@ -20,6 +20,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; @@ -81,10 +82,15 @@ public class InstantiateTypeList extends YormlSerializerComposition { LinkedHashSet.class, SET, Set.class, SET ); - + Map typesAliasedByName = MutableMap.of( + Arrays.class.getCanonicalName()+"$ArrayList", LIST //Arrays.ArrayList is changed to default + ); + + // any other types we allow? (expect this to be populated by trial and error) @SuppressWarnings("rawtypes") Set> typesAllowed = MutableSet.>of( - // TODO does anything fit this category? we serialize as a json list, including the type, and use xxx.add(...) to read in + ); + Set typesAllowedByName = MutableSet.of( ); protected YormlSerializerWorker newWorker() { @@ -290,7 +296,7 @@ public void write() { return; } @SuppressWarnings("unchecked") - Collection l = Reflections.invokeConstructorFromArgsIncludingPrivate(typesAliased.keySet().iterator().next()).get(); + Collection l = Reflections.invokeConstructorFromArgsIncludingPrivate(typeAliases.values().iterator().next()).get(); Iterables.addAll(l, (Iterable)getJavaObject()); storeWriteObjectAndAdvance(l); return; @@ -314,14 +320,20 @@ public void write() { expectedJavaType = newExpectedType; } String expectedJavaTypeName = typesAliased.get(expectedJavaType); + if (expectedJavaTypeName==null && expectedJavaType!=null) expectedJavaTypeName = typesAliasedByName.get(expectedJavaType.getName()); + if (expectedJavaTypeName!=null) expectedJavaType = typeAliases.get(expectedJavaTypeName); else expectedJavaTypeName = config.getTypeRegistry().getTypeNameOfClass(expectedJavaType); String actualTypeName = typesAliased.get(getJavaObject().getClass()); + if (actualTypeName==null) actualTypeName = typesAliasedByName.get(getJavaObject().getClass().getName()); + boolean isBasicCollectionType = (actualTypeName!=null); if (actualTypeName==null) actualTypeName = config.getTypeRegistry().getTypeName(getJavaObject()); if (actualTypeName==null) return; - boolean isAllowedCollectionType = isBasicCollectionType || typesAllowed.contains(getJavaObject().getClass()); + boolean isAllowedCollectionType = isBasicCollectionType || + typesAllowed.contains(getJavaObject().getClass()) || + typesAllowedByName.contains(getJavaObject().getClass().getName()); if (!isAllowedCollectionType) return; Class reconstructedJavaType = typeAliases.get(actualTypeName); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java index a5146ffec2..96574e6998 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java @@ -35,12 +35,6 @@ public abstract class InstantiateTypeWorkerAbstract extends YormlSerializerWorker { - protected boolean isJsonPrimitiveObject(Object o) { - if (o==null) return true; - if (o instanceof String) return true; - if (Boxing.isPrimitiveOrBoxedObject(o)) return true; - return false; - } protected boolean isJsonPrimitiveType(Class type) { if (type==null) return false; if (String.class.isAssignableFrom(type)) return true; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java index 8c1c7d6af8..30657f5390 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java @@ -24,6 +24,7 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.javalang.Boxing; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yorml.YormlContext; import org.apache.brooklyn.util.yorml.YormlContextForRead; @@ -128,6 +129,13 @@ protected Set findAllKeyManglesYamlKeys(String targetKey) { } return result; } + + protected boolean isJsonPrimitiveObject(Object o) { + if (o==null) return true; + if (o instanceof String) return true; + if (Boxing.isPrimitiveOrBoxedObject(o)) return true; + return false; + } public abstract void read(); public abstract void write(); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md index 65d0b036ba..d73833d59d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md @@ -265,6 +265,10 @@ and the serializations defined for that type specify: * If the item is a primitive V, it is converted to `{ .value: V }` * If it is a map with no `type` defined, `type: explicit-field` is added + + + + This allows the serialization rules defined on the specific type to kick in to handle `.key` or `.value` entries introduced but not removed. In the case of `explicit-field` (the default type, as shown in the rules above), this will rename either such key `.value` to `field-name` (and give an error if `field-name` is already present). @@ -502,9 +506,11 @@ whilst allowing them to be extended. The general phases are: -* `manipulating` (any custom serializers operate in this phase) -* `handling-type` (default to instantaiate the java type, on read, or set the `type` field, on write), - which if successful inserts: +* `manipulating` (custom serializers, operating directly on the input YAML map) +* `handling-type` (default to instantaiate the java type, on read, or set the `type` field, on write), + on read, sets the Java object and sets YamlKeysOnBlackboard which are subsequently used for manipulation; + on write, sets the YAML object and sets JavaFieldsOnBlackboard (and sets ReadingTypeOnBlackboard with errors); + inserting new phases: * when reading: * `manipulating` (custom serializers again, now with the object created, fields known, and other serializers loaded) * `handling-fields` (write the fields to the java object) @@ -531,6 +537,7 @@ to be shown. * yaml segment information and code-point completion + ## Real World Use Cases ### An Init.d-style entity/effector language @@ -562,3 +569,49 @@ to be shown. - type: effector ``` + + + +First, if the `serialization` field (which expects a list) is given a map, +the `convert-map-to-list` serializer converts each pair in that map to a list entry as follows: + +* if V is a non-empty map, then the corresponding list entry is the map V with `{ field-name: K }` added +* otherwise, the corresponding list entry is `{ field-name: K, type: V }` + convert-map-to-list: { key-for-key: field-name + key-for-primitive-value: type, || key-for-any-value: ... || key-for-list-value: || key-for-map-value + || merge-with-map-value + apply-to-singleton-maps: true + defaults: { type: explicit-field } + # explicit-field sets key-for-list-value as aliases +# on serializer + convert-singleton-map: { key-for-key: type + key-for-primitive-value: type, || key-for-any-value: ... || key-for-list-value: || key-for-map-value + || merge-with-map-value + defaults: { type: explicit-field } + +Next, each entry in the list is interpreted as a `serialization` instance, +and the serializations defined for that type specify: + +* If the key `.key` is present and `type` is not defined, that key is renamed to `type` (ignored if `type` is already present) + rename-key: { from: .key, to: type, fail-if-present: true } + rename-default-key: type (as above but .value) + rename-default-value: to + # above two serializers have special rules to need their own + +* If the item is a primitive V, it is converted to `{ .value: V }` + primitive-to-kv-pair + primitive-to-kv-pair: { key: .value || value: foo } + +* If it is a map with no `type` defined, `type: explicit-field` is added + defaults: { type: explicit-field } + # serializer declares convert-singleton-map ( merge-with-map-value ) + +NOTES + +convert-map-to-list (default-key, default-value) + +* if V is a non-empty map, then the corresponding list entry is the map V with `{ : K }` added +* otherwise, the corresponding list entry is `{ : K, : V }` + + +OLD \ No newline at end of file diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ConvertSingletonMapTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ConvertSingletonMapTests.java new file mode 100644 index 0000000000..39fc59a3d6 --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/ConvertSingletonMapTests.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yorml.tests; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.yorml.serializers.AllFieldsExplicit; +import org.apache.brooklyn.util.yorml.serializers.ConvertSingletonMap; +import org.apache.brooklyn.util.yorml.tests.YormlBasicTests.ShapeWithSize; +import org.testng.annotations.Test; + +import com.google.common.base.Objects; + +/** Tests that explicit fields can be set at the outer level in yaml. */ +public class ConvertSingletonMapTests { + + static class ShapeWithTags extends ShapeWithSize { + List tags; + Map metadata; + + @Override + public boolean equals(Object xo) { + return super.equals(xo) && Objects.equal(tags, ((ShapeWithTags)xo).tags) && Objects.equal(metadata, ((ShapeWithTags)xo).metadata); + } + @Override + public int hashCode() { return Objects.hashCode(super.hashCode(), tags, metadata); } + + public ShapeWithTags tags(String ...tags) { this.tags = Arrays.asList(tags); return this; } + public ShapeWithTags metadata(Map metadata) { this.metadata = metadata; return this; } + } + + /* y does: + * * key defaults to name + * * primitive value defaults to color + * * list value defaults to tags + * and y2 adds: + * * map defaults to metadata + * * default for value is size (but won't be used unless one of the above is changed to null) + */ + YormlTestFixture y = YormlTestFixture.newInstance(). + addType("shape", ShapeWithTags.class, MutableList.of( + new AllFieldsExplicit(), + new ConvertSingletonMap("name", null, "color", "tags", null, null, MutableMap.of("size", 0)))); + + YormlTestFixture y2 = YormlTestFixture.newInstance(). + addType("shape", ShapeWithTags.class, MutableList.of( + new AllFieldsExplicit(), + new ConvertSingletonMap("name", "size", "color", "tags", "metadata", null, MutableMap.of("size", 42)))); + + + @Test public void testPrimitiveValue() { + y.reading("{ red-square: red }", "shape").writing(new ShapeWithTags().name("red-square").color("red"), "shape") + .doReadWriteAssertingJsonMatch(); + } + + @Test public void testListValue() { + y.reading("{ good-square: [ good ] }", "shape").writing(new ShapeWithTags().tags("good").name("good-square"), "shape") + .doReadWriteAssertingJsonMatch(); + } + + @Test public void testMapValueMerge() { + y.reading("{ merge-square: { size: 12 } }", "shape").writing(new ShapeWithTags().size(12).name("merge-square"), "shape") + .doReadWriteAssertingJsonMatch(); + } + + @Test public void testMapValueSetAndDefaults() { + y2.reading("{ happy-square: { mood: happy } }", "shape").writing(new ShapeWithTags().metadata(MutableMap.of("mood", "happy")).size(42).name("happy-square"), "shape") + .doReadWriteAssertingJsonMatch(); + } + + @Test public void testMapValueWontMergeIfWouldTreatAsMetadataAndDoesntApplyDefaults() { + y2.reading("{ name: bad-square, size: 12 }", "shape").writing(new ShapeWithTags().size(12).name("bad-square"), "shape") + .doReadWriteAssertingJsonMatch(); + } + + @Test public void testWontApplyIfTypeUnknown() { + // size is needed without an extra defaults bit + y.write(new ShapeWithTags().name("red-square").color("red"), null) + .assertResult("{ type: shape, color: red, name: red-square, size: 0 }"); + } + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java index 658d98e938..b4f474a2a8 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.util.yorml.tests; import java.math.RoundingMode; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; @@ -87,6 +88,11 @@ public class MapListTests { @Test public void testReadListJsonTypeInBody() { y.read(LIST1_JSON_LIST_JSON_TYPE, null).assertResult(LIST1_OBJ); } @Test public void testReadListJsonTypeDeclaredAndInBody() { y.read(LIST1_JSON_LIST_JSON_TYPE, "list").assertResult(LIST1_OBJ); } + @Test public void testArraysAsList() { + y.reading("[ a, b ]", "list").writing(Arrays.asList("a", "b"), "list") + .doReadWriteAssertingJsonMatch(); + } + Set SET1_OBJ = MutableSet.of("a", 1, "b"); String SET1_JSON = "{ type: set, value: [ a, 1, b ] }"; @Test public void testWriteSet() { y.write(SET1_OBJ, "set").assertResult(LIST1_JSON); } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java index 924abcde78..56992e0055 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java @@ -91,7 +91,7 @@ public YormlTestFixture assertResult(Object expectation) { public YormlTestFixture doReadWriteAssertingJsonMatch() { read(readObject, readObjectExpectedType); write(writeObject, writeObjectExpectedType); - assertEqualsIgnoringQuotes(Jsonya.newInstance().add(lastWriteResult).toString(), readObject, "Write output should read input"); + assertEqualsIgnoringQuotes(Jsonya.newInstance().add(lastWriteResult).toString(), readObject, "Write output should match read input"); assertEqualsIgnoringQuotes(lastReadResult, writeObject, "Read output should match write input"); return this; } From 8d5409e8a5e984bd1ae3786186f58c93bd933dbf Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 12 Aug 2016 16:34:06 +0100 Subject: [PATCH 21/77] yorml notes --- .../org/apache/brooklyn/util/yorml/sketch.md | 13 ++++++++----- .../util/yorml/tests/YormlBasicTests.java | 16 ++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md index d73833d59d..c7c35f9ef9 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yorml/sketch.md @@ -57,12 +57,12 @@ matter some syntaxes may be more natural than others. Consider allowing writing say_hi: # map converts to list treating key as name of effector, expecting type 'Effector' as value type: ssh # type alias 'ssh' when type 'Effector` is needed matches SshEffector type parameters: # field in SshEffector, of type Parameter - - name # string given when Parameter expected means it's the parameter name + - person # string given when Parameter expected means it's the parameter name - name: hello_word # map of type, becomes a Parameter populating fields description: how to say hello default: hello command: | # and now the command, which SshEffector expects - echo ${hello_word} ${name:-world} + echo ${hello_word} ${person:-world} ``` The important thing here is not using all of them at the same time (as we did for shape), @@ -548,12 +548,15 @@ to be shown. steps: 00-provision: provision 10-install: - type: bash - contents: | + bash: | curl blah tar blah 20-run: - type: invoke-effector + effector: + launch: + parameters... + 21-run-other: + type; invoke-effector effector: launch parameters: ... diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java index ff7d9c240a..a2a28bbb15 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java @@ -174,9 +174,6 @@ public void testExpectedType() { @Test public void testExtraFieldError() { - // TODO see failures - // TODO extra blackboard item of fields to handle - // TODO instantiate expected type if none explicit try { YormlTestFixture.newInstance(). addType("shape", Shape.class). @@ -297,10 +294,10 @@ public void testStaticNotWrittenButExtendedItemsAre() { YormlTestFixture.newInstance(). addType("shape-weird", ShapeWithWeirdFields.class). - writing(shape).reading("{ \"type\": \"shape-weird\", " - + "\"fields\": { \"name\": \"normal-trust-me\", " - + "\""+Shape.class.getCanonicalName()+"."+"name\": \"weird-shape\", " - + "\"size\": 4 " + writing(shape).reading("{ type: shape-weird, " + + "fields: { name: normal-trust-me, " + + Shape.class.getCanonicalName()+"."+"name: weird-shape, " + + "size: 4 " + "} }"). doReadWriteAssertingJsonMatch(); } @@ -311,9 +308,8 @@ public void testStaticNotRead() { try { YormlTestFixture.newInstance(). addType("shape-weird", ShapeWithWeirdFields.class). - read("{ \"type\": \"shape-weird\", " - + "\"fields\": { \"aStatic\": 4 " - + "} }", null); + read("{ type: shape-weird, " + + "fields: { aStatic: 4 " + "} }", null); Assert.assertEquals(3, ShapeWithWeirdFields.aStatic); Asserts.shouldHaveFailedPreviously(); } catch (Exception e) { From 41a1ca5cec93b52f1d2f1912dac6d879b745ffb6 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 31 Aug 2016 15:28:06 +0100 Subject: [PATCH 22/77] rename it YOML as it's not relational --- .../util/{yorml/Yorml.java => yoml/Yoml.java} | 44 ++++++++-------- .../YomlContext.java} | 6 +-- .../YomlContextForRead.java} | 6 +-- .../YomlContextForWrite.java} | 6 +-- .../YomlException.java} | 16 +++--- .../YomlRequirement.java} | 6 +-- .../YomlSerializer.java} | 18 +++---- .../YomlTypeRegistry.java} | 10 ++-- .../internal/SerializersOnBlackboard.java | 18 +++---- .../internal/YomlConfig.java} | 14 +++--- .../internal/YomlConverter.java} | 46 ++++++++--------- .../internal/YomlUtils.java} | 6 +-- .../serializers/AllFieldsExplicit.java | 14 +++--- .../serializers/ConvertSingletonMap.java | 22 ++++---- .../serializers/ExplicitField.java | 18 +++---- .../serializers/ExplicitFieldsBlackboard.java | 24 ++++----- .../serializers/FieldsInMapUnderFields.java | 28 +++++------ .../serializers/InstantiateTypeEnum.java | 10 ++-- .../InstantiateTypeFromRegistry.java | 20 ++++---- .../serializers/InstantiateTypeList.java | 48 +++++++++--------- .../serializers/InstantiateTypeMap.java | 40 +++++++-------- .../serializers/InstantiateTypePrimitive.java | 14 +++--- .../InstantiateTypeWorkerAbstract.java | 34 ++++++------- .../serializers/JavaFieldsOnBlackboard.java | 16 +++--- .../serializers/ReadingTypeOnBlackboard.java | 24 ++++----- .../serializers/YamlKeysOnBlackboard.java | 16 +++--- .../YomlSerializerComposition.java} | 46 ++++++++--------- .../brooklyn/util/{yorml => yoml}/sketch.md | 4 +- .../tests/ConvertSingletonMapTests.java | 12 ++--- .../tests/ExplicitFieldTests.java | 48 +++++++++--------- .../{yorml => yoml}/tests/MapListTests.java | 8 +-- .../tests/MockYomlTypeRegistry.java} | 32 ++++++------ .../tests/YomlBasicTests.java} | 50 +++++++++---------- .../tests/YomlTestFixture.java} | 42 ++++++++-------- 34 files changed, 383 insertions(+), 383 deletions(-) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml/Yorml.java => yoml/Yoml.java} (58%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml/YormlContext.java => yoml/YomlContext.java} (96%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml/YormlContextForRead.java => yoml/YomlContextForRead.java} (86%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml/YormlContextForWrite.java => yoml/YomlContextForWrite.java} (82%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml/YormlException.java => yoml/YomlException.java} (66%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml/YormlRequirement.java => yoml/YomlRequirement.java} (86%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml/YormlSerializer.java => yoml/YomlSerializer.java} (74%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml/YormlTypeRegistry.java => yoml/YomlTypeRegistry.java} (79%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/internal/SerializersOnBlackboard.java (82%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml/internal/YormlConfig.java => yoml/internal/YomlConfig.java} (77%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml/internal/YormlConverter.java => yoml/internal/YomlConverter.java} (74%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml/internal/YormlUtils.java => yoml/internal/YomlUtils.java} (98%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/serializers/AllFieldsExplicit.java (80%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/serializers/ConvertSingletonMap.java (91%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/serializers/ExplicitField.java (94%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/serializers/ExplicitFieldsBlackboard.java (85%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/serializers/FieldsInMapUnderFields.java (79%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/serializers/InstantiateTypeEnum.java (92%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/serializers/InstantiateTypeFromRegistry.java (82%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/serializers/InstantiateTypeList.java (89%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/serializers/InstantiateTypeMap.java (89%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/serializers/InstantiateTypePrimitive.java (89%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/serializers/InstantiateTypeWorkerAbstract.java (81%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/serializers/JavaFieldsOnBlackboard.java (81%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/serializers/ReadingTypeOnBlackboard.java (64%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/serializers/YamlKeysOnBlackboard.java (82%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml/serializers/YormlSerializerComposition.java => yoml/serializers/YomlSerializerComposition.java} (80%) rename utils/common/src/main/java/org/apache/brooklyn/util/{yorml => yoml}/sketch.md (99%) rename utils/common/src/test/java/org/apache/brooklyn/util/{yorml => yoml}/tests/ConvertSingletonMapTests.java (91%) rename utils/common/src/test/java/org/apache/brooklyn/util/{yorml => yoml}/tests/ExplicitFieldTests.java (86%) rename utils/common/src/test/java/org/apache/brooklyn/util/{yorml => yoml}/tests/MapListTests.java (97%) rename utils/common/src/test/java/org/apache/brooklyn/util/{yorml/tests/MockYormlTypeRegistry.java => yoml/tests/MockYomlTypeRegistry.java} (88%) rename utils/common/src/test/java/org/apache/brooklyn/util/{yorml/tests/YormlBasicTests.java => yoml/tests/YomlBasicTests.java} (89%) rename utils/common/src/test/java/org/apache/brooklyn/util/{yorml/tests/YormlTestFixture.java => yoml/tests/YomlTestFixture.java} (68%) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java similarity index 58% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java index b8c42867c2..1cda234788 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/Yorml.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java @@ -16,33 +16,33 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml; +package org.apache.brooklyn.util.yoml; import java.util.List; import org.apache.brooklyn.util.collections.MutableList; -import org.apache.brooklyn.util.yorml.internal.YormlConfig; -import org.apache.brooklyn.util.yorml.internal.YormlConverter; -import org.apache.brooklyn.util.yorml.serializers.FieldsInMapUnderFields; -import org.apache.brooklyn.util.yorml.serializers.InstantiateTypeEnum; -import org.apache.brooklyn.util.yorml.serializers.InstantiateTypeFromRegistry; -import org.apache.brooklyn.util.yorml.serializers.InstantiateTypeList; -import org.apache.brooklyn.util.yorml.serializers.InstantiateTypeMap; -import org.apache.brooklyn.util.yorml.serializers.InstantiateTypePrimitive; +import org.apache.brooklyn.util.yoml.internal.YomlConfig; +import org.apache.brooklyn.util.yoml.internal.YomlConverter; +import org.apache.brooklyn.util.yoml.serializers.FieldsInMapUnderFields; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeEnum; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistry; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeList; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeMap; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypePrimitive; -public class Yorml { +public class Yoml { - final YormlConfig config; + final YomlConfig config; - private Yorml(YormlConfig config) { this.config = config; } + private Yoml(YomlConfig config) { this.config = config; } - public static Yorml newInstance(YormlConfig config) { - return new Yorml(config); + public static Yoml newInstance(YomlConfig config) { + return new Yoml(config); } - public static Yorml newInstance(YormlTypeRegistry typeRegistry) { - return newInstance(typeRegistry, MutableList.of( + public static Yoml newInstance(YomlTypeRegistry typeRegistry) { + return newInstance(typeRegistry, MutableList.of( new FieldsInMapUnderFields(), new InstantiateTypePrimitive(), new InstantiateTypeEnum(), @@ -51,15 +51,15 @@ public static Yorml newInstance(YormlTypeRegistry typeRegistry) { new InstantiateTypeFromRegistry() )); } - private static Yorml newInstance(YormlTypeRegistry typeRegistry, List serializers) { - YormlConfig config = new YormlConfig(); + private static Yoml newInstance(YomlTypeRegistry typeRegistry, List serializers) { + YomlConfig config = new YomlConfig(); config.typeRegistry = typeRegistry; config.serializersPost.addAll(serializers); - return new Yorml(config); + return new Yoml(config); } - public YormlConfig getConfig() { + public YomlConfig getConfig() { return config; } @@ -70,14 +70,14 @@ public Object read(String yaml, String expectedType) { return readFromYamlObject(new org.yaml.snakeyaml.Yaml().load(yaml), expectedType); } public Object readFromYamlObject(Object yamlObject, String type) { - return new YormlConverter(config).read( new YormlContextForRead(yamlObject, "", type) ); + return new YomlConverter(config).read( new YomlContextForRead(yamlObject, "", type) ); } public Object write(Object java) { return write(java, null); } public Object write(Object java, String expectedType) { - return new YormlConverter(config).write( new YormlContextForWrite(java, "", expectedType) ); + return new YomlConverter(config).write( new YomlContextForWrite(java, "", expectedType) ); } // public T read(String yaml, Class type) { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContext.java similarity index 96% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContext.java index ab757ba779..393ab81a51 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContext.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContext.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml; +package org.apache.brooklyn.util.yoml; import java.util.Arrays; import java.util.Iterator; @@ -28,7 +28,7 @@ import com.google.common.base.Objects; -public abstract class YormlContext { +public abstract class YomlContext { final String jsonPath; final String expectedType; @@ -49,7 +49,7 @@ public static interface StandardPhases { String MANIPULATING_TO_LIST = "manipulating-to-list"; } - public YormlContext(String jsonPath, String expectedType) { + public YomlContext(String jsonPath, String expectedType) { this.jsonPath = jsonPath; this.expectedType = expectedType; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForRead.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContextForRead.java similarity index 86% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForRead.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContextForRead.java index 74ccf599f7..b43dbfc8c4 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForRead.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContextForRead.java @@ -16,13 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml; +package org.apache.brooklyn.util.yoml; import org.apache.brooklyn.util.text.Strings; -public class YormlContextForRead extends YormlContext { +public class YomlContextForRead extends YomlContext { - public YormlContextForRead(Object yamlObject, String jsonPath, String expectedType) { + public YomlContextForRead(Object yamlObject, String jsonPath, String expectedType) { super(jsonPath, expectedType); setYamlObject(yamlObject); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForWrite.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContextForWrite.java similarity index 82% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForWrite.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContextForWrite.java index b165e0e3de..04f623652a 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlContextForWrite.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContextForWrite.java @@ -16,11 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml; +package org.apache.brooklyn.util.yoml; -public class YormlContextForWrite extends YormlContext { +public class YomlContextForWrite extends YomlContext { - public YormlContextForWrite(Object javaObject, String jsonPath, String expectedType) { + public YomlContextForWrite(Object javaObject, String jsonPath, String expectedType) { super(jsonPath, expectedType); setJavaObject(javaObject); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlException.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlException.java similarity index 66% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlException.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlException.java index 6780aa7759..989d9c4d9a 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlException.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlException.java @@ -16,20 +16,20 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml; +package org.apache.brooklyn.util.yoml; -public class YormlException extends RuntimeException { +public class YomlException extends RuntimeException { private static final long serialVersionUID = 7825908737102292499L; - YormlContext context; + YomlContext context; - public YormlException(String message) { super(message); } - public YormlException(String message, Throwable cause) { super(message, cause); } - public YormlException(String message, YormlContext context) { this(message); this.context = context; } - public YormlException(String message, YormlContext context, Throwable cause) { this(message, cause); this.context = context; } + public YomlException(String message) { super(message); } + public YomlException(String message, Throwable cause) { super(message, cause); } + public YomlException(String message, YomlContext context) { this(message); this.context = context; } + public YomlException(String message, YomlContext context, Throwable cause) { this(message, cause); this.context = context; } - public YormlContext getContext() { + public YomlContext getContext() { return context; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlRequirement.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlRequirement.java similarity index 86% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlRequirement.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlRequirement.java index ae17a83106..5808846901 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlRequirement.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlRequirement.java @@ -16,10 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml; +package org.apache.brooklyn.util.yoml; -public interface YormlRequirement { +public interface YomlRequirement { - void checkCompletion(YormlContext context); + void checkCompletion(YomlContext context); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlSerializer.java similarity index 74% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlSerializer.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlSerializer.java index acf777488b..2428378269 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlSerializer.java @@ -16,20 +16,20 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml; +package org.apache.brooklyn.util.yoml; import java.util.Map; -import org.apache.brooklyn.util.yorml.internal.YormlConverter; -import org.apache.brooklyn.util.yorml.serializers.YormlSerializerComposition; +import org.apache.brooklyn.util.yoml.internal.YomlConverter; +import org.apache.brooklyn.util.yoml.serializers.YomlSerializerComposition; -/** Describes a serializer which can be used by {@link YormlConverter}. +/** Describes a serializer which can be used by {@link YomlConverter}. *

* Instances of this class should be thread-safe for use with simultaneous conversions. - * Often implementations will extend {@link YormlSerializerComposition} and which stores + * Often implementations will extend {@link YomlSerializerComposition} and which stores * per-conversion data in a per-method-invocation object. */ -public interface YormlSerializer { +public interface YomlSerializer { /** * modifies yaml object and/or java object and/or blackboard as appropriate, @@ -37,7 +37,7 @@ public interface YormlSerializer { * returning true if it did anything (and so should restart the cycle). * implementations must NOT return true indefinitely if passed the same instances! */ - public void read(YormlContextForRead context, YormlConverter converter, Map blackboard); + public void read(YomlContextForRead context, YomlConverter converter, Map blackboard); /** * modifies java object and/or yaml object and/or blackboard as appropriate, @@ -45,11 +45,11 @@ public interface YormlSerializer { * returning true if it did anything (and so should restart the cycle). * implementations must NOT return true indefinitely if passed the same instances! */ - public void write(YormlContextForWrite context, YormlConverter converter, Map blackboard); + public void write(YomlContextForWrite context, YomlConverter converter, Map blackboard); /** * generates human-readable schema for a type using this schema. */ - public String document(String type, YormlConverter converter); + public String document(String type, YomlConverter converter); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java similarity index 79% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java index 049541db1b..2f8361293f 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/YormlTypeRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java @@ -16,25 +16,25 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml; +package org.apache.brooklyn.util.yoml; import java.util.Collection; import java.util.Set; import org.apache.brooklyn.util.guava.Maybe; -public interface YormlTypeRegistry { +public interface YomlTypeRegistry { /** Absent if unknown type; throws if type is ill-defined or incomplete. */ - Maybe newInstanceMaybe(String type, Yorml yorml); + Maybe newInstanceMaybe(String type, Yoml yoml); - Object newInstance(String type, Yorml yorml); + Object newInstance(String type, Yoml yoml); Class getJavaType(String typeName); String getTypeName(Object obj); String getTypeNameOfClass(Class type); - void collectSerializers(String typeName, Collection serializers, Set typesVisited); + void collectSerializers(String typeName, Collection serializers, Set typesVisited); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java similarity index 82% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java index 86ef26b41d..af231d7746 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/SerializersOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java @@ -16,13 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.internal; +package org.apache.brooklyn.util.yoml.internal; import java.util.List; import java.util.Map; import org.apache.brooklyn.util.collections.MutableList; -import org.apache.brooklyn.util.yorml.YormlSerializer; +import org.apache.brooklyn.util.yoml.YomlSerializer; import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; @@ -47,20 +47,20 @@ public static SerializersOnBlackboard create(Map blackboard) { return peek(blackboard); } - List preSerializers = MutableList.of(); - List instantiatedTypeSerializers = MutableList.of(); - List expectedTypeSerializers = MutableList.of(); - List postSerializers = MutableList.of(); + List preSerializers = MutableList.of(); + List instantiatedTypeSerializers = MutableList.of(); + List expectedTypeSerializers = MutableList.of(); + List postSerializers = MutableList.of(); - public void addInstantiatedTypeSerializers(Iterable instantiatedTypeSerializers) { + public void addInstantiatedTypeSerializers(Iterable instantiatedTypeSerializers) { Iterables.addAll(this.instantiatedTypeSerializers, instantiatedTypeSerializers); } - public Iterable getSerializers() { + public Iterable getSerializers() { return Iterables.concat(preSerializers, instantiatedTypeSerializers, expectedTypeSerializers, postSerializers); } - public static boolean isAddedByTypeInstantiation(Map blackboard, YormlSerializer serializer) { + public static boolean isAddedByTypeInstantiation(Map blackboard, YomlSerializer serializer) { SerializersOnBlackboard sb = get(blackboard); if (sb!=null && sb.instantiatedTypeSerializers.contains(serializer)) return true; return false; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConfig.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfig.java similarity index 77% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConfig.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfig.java index 0a06e67272..9d228b08ef 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConfig.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfig.java @@ -16,24 +16,24 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.internal; +package org.apache.brooklyn.util.yoml.internal; import java.util.List; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.javalang.coerce.TypeCoercer; import org.apache.brooklyn.util.javalang.coerce.TypeCoercerExtensible; -import org.apache.brooklyn.util.yorml.YormlSerializer; -import org.apache.brooklyn.util.yorml.YormlTypeRegistry; +import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.YomlTypeRegistry; -public class YormlConfig { +public class YomlConfig { - public YormlTypeRegistry typeRegistry; + public YomlTypeRegistry typeRegistry; public TypeCoercer coercer = TypeCoercerExtensible.newDefault(); - public List serializersPost = MutableList.of(); + public List serializersPost = MutableList.of(); - public YormlTypeRegistry getTypeRegistry() { + public YomlTypeRegistry getTypeRegistry() { return typeRegistry; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java similarity index 74% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java index 48cdd71be9..4b061bcdf5 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java @@ -16,30 +16,30 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.internal; +package org.apache.brooklyn.util.yoml.internal; import java.util.Map; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.YormlContextForRead; -import org.apache.brooklyn.util.yorml.YormlContextForWrite; -import org.apache.brooklyn.util.yorml.YormlRequirement; -import org.apache.brooklyn.util.yorml.YormlSerializer; -import org.apache.brooklyn.util.yorml.serializers.ReadingTypeOnBlackboard; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.YomlContextForRead; +import org.apache.brooklyn.util.yoml.YomlContextForWrite; +import org.apache.brooklyn.util.yoml.YomlRequirement; +import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.serializers.ReadingTypeOnBlackboard; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Iterables; -public class YormlConverter { +public class YomlConverter { - private static final Logger log = LoggerFactory.getLogger(YormlConverter.class); + private static final Logger log = LoggerFactory.getLogger(YomlConverter.class); - private final YormlConfig config; + private final YomlConfig config; - public YormlConverter(YormlConfig config) { + public YomlConverter(YomlConfig config) { this.config = config; } @@ -48,7 +48,7 @@ public YormlConverter(YormlConfig config) { * makes shallow copy of the object, then goes through serializers modifying it or creating/setting result, * until result is done */ - public Object read(YormlContextForRead context) { + public Object read(YomlContextForRead context) { loopOverSerializers(context); return context.getJavaObject(); } @@ -56,12 +56,12 @@ public Object read(YormlContextForRead context) { /** * returns jsonable object (map, list, primitive) */ - public Object write(final YormlContextForWrite context) { + public Object write(final YomlContextForWrite context) { loopOverSerializers(context); return context.getYamlObject(); } - protected void loopOverSerializers(YormlContext context) { + protected void loopOverSerializers(YomlContext context) { Map blackboard = MutableMap.of(); // find the serializers known so far; store on blackboard so they could be edited @@ -71,23 +71,23 @@ protected void loopOverSerializers(YormlContext context) { } serializers.postSerializers.addAll(config.serializersPost); - if (context instanceof YormlContextForRead) { + if (context instanceof YomlContextForRead) { // read needs instantiated so that these errors display before manipulating errors and others ReadingTypeOnBlackboard.get(blackboard); } while (context.phaseAdvance()) { while (context.phaseStepAdvance() blackboard) { + protected void checkCompletion(YomlContext context, Map blackboard) { for (Object bo: blackboard.values()) { - if (bo instanceof YormlRequirement) { - ((YormlRequirement)bo).checkCompletion(context); + if (bo instanceof YomlRequirement) { + ((YomlRequirement)bo).checkCompletion(context); } } } @@ -115,7 +115,7 @@ public String document(String type) { return null; } - public YormlConfig getConfig() { + public YomlConfig getConfig() { return config; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java similarity index 98% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java index 90d779ce5d..b1542ed03c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/internal/YormlUtils.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.internal; +package org.apache.brooklyn.util.yoml.internal; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; @@ -34,7 +34,7 @@ import com.google.common.annotations.Beta; import com.google.common.base.Objects; -public class YormlUtils { +public class YomlUtils { /** true iff k1 and k2 are case-insensitively equal after removing all - and _. * Note that the definition of mangling may change. @@ -175,7 +175,7 @@ public static List getAllNonTransientNonStaticFieldNamesUntyped(Class * However we don't do them recursively at all (so eg a List> becomes a List). * TODO That wouldn't be hard to fix. */ - public static String getFieldTypeName(Field ff, YormlConfig config) { + public static String getFieldTypeName(Field ff, YomlConfig config) { String baseTypeName = config.getTypeRegistry().getTypeNameOfClass(ff.getType()); String typeName = baseTypeName; Type type = ff.getGenericType(); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/AllFieldsExplicit.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java similarity index 80% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/AllFieldsExplicit.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java index b946eca702..fd44f33400 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/AllFieldsExplicit.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java @@ -16,25 +16,25 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import java.util.List; import org.apache.brooklyn.util.collections.MutableList; -import org.apache.brooklyn.util.yorml.internal.SerializersOnBlackboard; -import org.apache.brooklyn.util.yorml.internal.YormlUtils; +import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; /* Adds ExplicitField instances for all fields declared on the type */ -public class AllFieldsExplicit extends YormlSerializerComposition { +public class AllFieldsExplicit extends YomlSerializerComposition { - protected YormlSerializerWorker newWorker() { + protected YomlSerializerWorker newWorker() { return new Worker(); } /** marker on blackboard indicating that we have run */ static class DoneAllFieldsExplicit {} - public class Worker extends YormlSerializerWorker { + public class Worker extends YomlSerializerWorker { public void read() { run(); } public void write() { run(); } @@ -48,7 +48,7 @@ protected void run() { SerializersOnBlackboard serializers = SerializersOnBlackboard.get(blackboard); - List fields = YormlUtils.getAllNonTransientNonStaticFieldNames(getJavaObject().getClass(), null); + List fields = YomlUtils.getAllNonTransientNonStaticFieldNames(getJavaObject().getClass(), null); for (String f: fields) { ExplicitField ef = new ExplicitField(); ef.fieldName = f; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ConvertSingletonMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java similarity index 91% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ConvertSingletonMap.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java index 0a34e6695b..24e311d8f8 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ConvertSingletonMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java @@ -16,14 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import java.util.Map; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.internal.SerializersOnBlackboard; -import org.apache.brooklyn.util.yorml.internal.YormlUtils; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; /* * key-for-key: type @@ -31,7 +31,7 @@ * || merge-with-map-value * defaults: { type: explicit-field } */ -public class ConvertSingletonMap extends YormlSerializerComposition { +public class ConvertSingletonMap extends YomlSerializerComposition { public ConvertSingletonMap() { } @@ -47,7 +47,7 @@ public ConvertSingletonMap(String keyForKey, String keyForAnyValue, String keyFo this.defaults = defaults; } - protected YormlSerializerWorker newWorker() { + protected YomlSerializerWorker newWorker() { return new Worker(); } @@ -64,9 +64,9 @@ protected YormlSerializerWorker newWorker() { public static class ConvertSingletonApplied {} - public class Worker extends YormlSerializerWorker { + public class Worker extends YomlSerializerWorker { public void read() { - if (!context.isPhase(YormlContext.StandardPhases.MANIPULATING)) return; + if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return; // runs before type instantiated if (hasJavaObject()) return; @@ -103,7 +103,7 @@ public void read() { newYamlMap.put(keyForAnyValue, value); } - YormlUtils.addDefaults(defaults, newYamlMap); + YomlUtils.addDefaults(defaults, newYamlMap); context.setYamlObject(newYamlMap); context.phaseRestart(); @@ -116,7 +116,7 @@ public void write() { // don't run if we're only added after instantiating the type (because then we couldn't read back!) if (SerializersOnBlackboard.isAddedByTypeInstantiation(blackboard, ConvertSingletonMap.this)) return; - if (context.isPhase(YormlContext.StandardPhases.MANIPULATING) && !context.seenPhase(OUR_PHASE)) { + if (context.isPhase(YomlContext.StandardPhases.MANIPULATING) && !context.seenPhase(OUR_PHASE)) { // finish manipulations before seeking to apply this context.phaseInsert(OUR_PHASE); return; @@ -140,7 +140,7 @@ public void write() { Map yamlMap = MutableMap.copyOf(getYamlMap()); yamlMap.remove(keyForKey); - YormlUtils.removeDefaults(defaults, yamlMap); + YomlUtils.removeDefaults(defaults, yamlMap); Object newValue = null; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java similarity index 94% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java index b0e226827d..5534befcda 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitField.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import java.util.Collections; import java.util.List; @@ -26,8 +26,8 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.YormlContext.StandardPhases; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.YomlContext.StandardPhases; import com.google.common.base.Objects; @@ -37,9 +37,9 @@ *

* On write, after FieldsInMapUnderFields sets the `fields` map, * look for the field name, and rewrite under the preferred alias at the root. */ -public class ExplicitField extends YormlSerializerComposition { +public class ExplicitField extends YomlSerializerComposition { - protected YormlSerializerWorker newWorker() { + protected YomlSerializerWorker newWorker() { return new Worker(); } @@ -76,7 +76,7 @@ public static enum FieldConstraint { REQUIRED } // also keyword `default` as alias Object defaultValue; - public class Worker extends YormlSerializerWorker { + public class Worker extends YomlSerializerWorker { String PREPARING_EXPLICIT_FIELDS = "preparing-explicit-fields"; @@ -97,9 +97,9 @@ protected Iterable getKeyNameAndAliases() { } protected boolean readyForMainEvent() { - if (!context.seenPhase(YormlContext.StandardPhases.HANDLING_TYPE)) return false; + if (!context.seenPhase(YomlContext.StandardPhases.HANDLING_TYPE)) return false; if (!context.seenPhase(PREPARING_EXPLICIT_FIELDS)) { - if (context.isPhase(YormlContext.StandardPhases.MANIPULATING)) { + if (context.isPhase(YomlContext.StandardPhases.MANIPULATING)) { // interrupt the manipulating phase to do a preparing phase context.phaseInsert(PREPARING_EXPLICIT_FIELDS, StandardPhases.MANIPULATING); context.phaseAdvance(); @@ -121,7 +121,7 @@ protected boolean readyForMainEvent() { return false; } if (ExplicitFieldsBlackboard.get(blackboard).isFieldDone(fieldName)) return false; - if (!context.isPhase(YormlContext.StandardPhases.MANIPULATING)) return false; + if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return false; return true; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldsBlackboard.java similarity index 85% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldsBlackboard.java index 91ff410857..33b05da663 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ExplicitFieldsBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldsBlackboard.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import java.util.Collection; import java.util.List; @@ -28,13 +28,13 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.YormlException; -import org.apache.brooklyn.util.yorml.YormlRequirement; -import org.apache.brooklyn.util.yorml.YormlSerializer; -import org.apache.brooklyn.util.yorml.serializers.ExplicitField.FieldConstraint; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.YomlException; +import org.apache.brooklyn.util.yoml.YomlRequirement; +import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.serializers.ExplicitField.FieldConstraint; -public class ExplicitFieldsBlackboard implements YormlRequirement { +public class ExplicitFieldsBlackboard implements YomlRequirement { public static final String KEY = ExplicitFieldsBlackboard.class.getCanonicalName(); @@ -53,7 +53,7 @@ public static ExplicitFieldsBlackboard get(Map blackboard) { private final Map> aliases = MutableMap.of(); private final Set fieldsDone = MutableSet.of(); private final Map fieldsConstraints = MutableMap.of(); - private final Map defaultValueForFieldComesFromSerializer = MutableMap.of(); + private final Map defaultValueForFieldComesFromSerializer = MutableMap.of(); private final Map defaultValueOfField = MutableMap.of(); public String getKeyName(String fieldName) { @@ -108,7 +108,7 @@ public void setConstraintIfUnset(String fieldName, FieldConstraint constraint) { fieldsConstraints.put(fieldName, constraint); } @Override - public void checkCompletion(YormlContext context) { + public void checkCompletion(YomlContext context) { List incompleteRequiredFields = MutableList.of(); for (Map.Entry fieldConstraint: fieldsConstraints.entrySet()) { FieldConstraint v = fieldConstraint.getValue(); @@ -117,7 +117,7 @@ public void checkCompletion(YormlContext context) { } } if (!incompleteRequiredFields.isEmpty()) { - throw new YormlException("Missing one or more explicitly required fields: "+Strings.join(incompleteRequiredFields, ", "), context); + throw new YomlException("Missing one or more explicitly required fields: "+Strings.join(incompleteRequiredFields, ", "), context); } } @@ -128,11 +128,11 @@ public void setFieldDone(String fieldName) { fieldsDone.add(fieldName); } - public void setUseDefaultFrom(String fieldName, YormlSerializer explicitField, Object defaultValue) { + public void setUseDefaultFrom(String fieldName, YomlSerializer explicitField, Object defaultValue) { defaultValueForFieldComesFromSerializer.put(fieldName, explicitField); defaultValueOfField.put(fieldName, defaultValue); } - public boolean shouldUseDefaultFrom(String fieldName, YormlSerializer explicitField) { + public boolean shouldUseDefaultFrom(String fieldName, YomlSerializer explicitField) { return explicitField.equals(defaultValueForFieldComesFromSerializer.get(fieldName)); } public Maybe getDefault(String fieldName) { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java similarity index 79% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java index 244e6e1267..2613fc51bc 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -28,20 +28,20 @@ import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.YormlContextForRead; -import org.apache.brooklyn.util.yorml.YormlContextForWrite; -import org.apache.brooklyn.util.yorml.internal.YormlUtils; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.YomlContextForRead; +import org.apache.brooklyn.util.yoml.YomlContextForWrite; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; -public class FieldsInMapUnderFields extends YormlSerializerComposition { +public class FieldsInMapUnderFields extends YomlSerializerComposition { - protected YormlSerializerWorker newWorker() { + protected YomlSerializerWorker newWorker() { return new Worker(); } - public static class Worker extends YormlSerializerWorker { + public static class Worker extends YomlSerializerWorker { public void read() { - if (!context.isPhase(YormlContext.StandardPhases.HANDLING_FIELDS)) return; + if (!context.isPhase(YomlContext.StandardPhases.HANDLING_FIELDS)) return; if (!hasJavaObject()) return; @SuppressWarnings("unchecked") @@ -60,8 +60,8 @@ public void read() { if (Modifier.isStatic(ff.getModifiers())) { // as above } else { - String fieldType = YormlUtils.getFieldTypeName(ff, config); - Object v2 = converter.read( new YormlContextForRead(v, context.getJsonPath()+"/"+f, fieldType) ); + String fieldType = YomlUtils.getFieldTypeName(ff, config); + Object v2 = converter.read( new YomlContextForRead(v, context.getJsonPath()+"/"+f, fieldType) ); ff.setAccessible(true); ff.set(getJavaObject(), v2); @@ -82,7 +82,7 @@ public void read() { } public void write() { - if (!context.isPhase(YormlContext.StandardPhases.HANDLING_FIELDS)) return; + if (!context.isPhase(YomlContext.StandardPhases.HANDLING_FIELDS)) return; if (!isYamlMap()) return; if (getFromYamlMap("fields", Map.class).isPresent()) return; JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); @@ -98,8 +98,8 @@ public void write() { // silently drop null fields } else { Field ff = Reflections.findFieldMaybe(getJavaObject().getClass(), f).get(); - String fieldType = YormlUtils.getFieldTypeName(ff, config); - Object v2 = converter.write(new YormlContextForWrite(v.get(), context.getJsonPath()+"/"+f, fieldType) ); + String fieldType = YomlUtils.getFieldTypeName(ff, config); + Object v2 = converter.write(new YomlContextForWrite(v.get(), context.getJsonPath()+"/"+f, fieldType) ); fields.put(f, v2); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeEnum.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java similarity index 92% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeEnum.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java index 8e39e2f4fd..e19e41bb4e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeEnum.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java @@ -16,14 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.yorml.YormlContext; +import org.apache.brooklyn.util.yoml.YomlContext; -public class InstantiateTypeEnum extends YormlSerializerComposition { +public class InstantiateTypeEnum extends YomlSerializerComposition { - protected YormlSerializerWorker newWorker() { + protected YomlSerializerWorker newWorker() { return new Worker(); } @@ -93,7 +93,7 @@ public void write() { result); } - context.phaseInsert(YormlContext.StandardPhases.MANIPULATING); + context.phaseInsert(YomlContext.StandardPhases.MANIPULATING); storeWriteObjectAndAdvance(result); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java similarity index 82% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java index de9d144d28..b347ea44e4 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeFromRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java @@ -16,20 +16,20 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yorml.Yorml; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.internal.YormlUtils; +import org.apache.brooklyn.util.yoml.Yoml; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; -public class InstantiateTypeFromRegistry extends YormlSerializerComposition { +public class InstantiateTypeFromRegistry extends YomlSerializerComposition { - protected YormlSerializerWorker newWorker() { + protected YomlSerializerWorker newWorker() { return new Worker(); } @@ -50,7 +50,7 @@ public void read() { if (type==null) return; - Maybe resultM = config.getTypeRegistry().newInstanceMaybe((String)type, Yorml.newInstance(config)); + Maybe resultM = config.getTypeRegistry().newInstanceMaybe((String)type, Yoml.newInstance(config)); if (resultM.isAbsent()) { Class jt = config.getTypeRegistry().getJavaType((String)type); String message = jt==null ? "Unknown type '"+type+"'" : "Unable to instantiate type '"+type+"' ("+jt+")"; @@ -69,7 +69,7 @@ public void read() { } public void write() { - if (!context.isPhase(YormlContext.StandardPhases.HANDLING_TYPE)) return; + if (!context.isPhase(YomlContext.StandardPhases.HANDLING_TYPE)) return; if (hasYamlObject()) return; if (!hasJavaObject()) return; if (JavaFieldsOnBlackboard.isPresent(blackboard)) return; @@ -88,9 +88,9 @@ public void write() { // collect fields JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); - fib.fieldsToWriteFromJava.addAll(YormlUtils.getAllNonTransientNonStaticFieldNamesUntyped(getJavaObject().getClass(), getJavaObject())); + fib.fieldsToWriteFromJava.addAll(YomlUtils.getAllNonTransientNonStaticFieldNamesUntyped(getJavaObject().getClass(), getJavaObject())); - context.phaseInsert(YormlContext.StandardPhases.HANDLING_FIELDS, YormlContext.StandardPhases.MANIPULATING); + context.phaseInsert(YomlContext.StandardPhases.HANDLING_FIELDS, YomlContext.StandardPhases.MANIPULATING); storeWriteObjectAndAdvance(map); return; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java similarity index 89% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java index 2e53c767fc..b285cb8166 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeList.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -32,13 +32,13 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.javalang.Reflections; -import org.apache.brooklyn.util.yorml.Yorml; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.YormlContextForRead; -import org.apache.brooklyn.util.yorml.YormlContextForWrite; -import org.apache.brooklyn.util.yorml.internal.YormlUtils; -import org.apache.brooklyn.util.yorml.internal.YormlUtils.GenericsParse; -import org.apache.brooklyn.util.yorml.internal.YormlUtils.JsonMarker; +import org.apache.brooklyn.util.yoml.Yoml; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.YomlContextForRead; +import org.apache.brooklyn.util.yoml.YomlContextForWrite; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; +import org.apache.brooklyn.util.yoml.internal.YomlUtils.GenericsParse; +import org.apache.brooklyn.util.yoml.internal.YomlUtils.JsonMarker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,12 +60,12 @@ * * repeat with value */ -public class InstantiateTypeList extends YormlSerializerComposition { +public class InstantiateTypeList extends YomlSerializerComposition { private static final Logger log = LoggerFactory.getLogger(InstantiateTypeList.class); - private static final String LIST = YormlUtils.TYPE_LIST; - private static final String SET = YormlUtils.TYPE_SET; + private static final String LIST = YomlUtils.TYPE_LIST; + private static final String SET = YomlUtils.TYPE_SET; @SuppressWarnings("rawtypes") Map> typeAliases = MutableMap.>of( @@ -93,7 +93,7 @@ public class InstantiateTypeList extends YormlSerializerComposition { Set typesAllowedByName = MutableSet.of( ); - protected YormlSerializerWorker newWorker() { + protected YomlSerializerWorker newWorker() { return new Worker(); } @@ -117,8 +117,8 @@ public void read() { } else { // but we have a collection // spawn manipulate-convert-from-list phase - if (!context.seenPhase(YormlContext.StandardPhases.MANIPULATING_FROM_LIST)) { - context.phaseInsert(YormlContext.StandardPhases.MANIPULATING_FROM_LIST, YormlContext.StandardPhases.HANDLING_TYPE); + if (!context.seenPhase(YomlContext.StandardPhases.MANIPULATING_FROM_LIST)) { + context.phaseInsert(YomlContext.StandardPhases.MANIPULATING_FROM_LIST, YomlContext.StandardPhases.HANDLING_TYPE); context.phaseAdvance(); } return; @@ -153,8 +153,8 @@ public void read() { } if (expectedJavaType!=null) { // collection definitely expected but not received - if (!context.seenPhase(YormlContext.StandardPhases.MANIPULATING_TO_LIST)) { - context.phaseInsert(YormlContext.StandardPhases.MANIPULATING_TO_LIST, YormlContext.StandardPhases.HANDLING_TYPE); + if (!context.seenPhase(YomlContext.StandardPhases.MANIPULATING_TO_LIST)) { + context.phaseInsert(YomlContext.StandardPhases.MANIPULATING_TO_LIST, YomlContext.StandardPhases.HANDLING_TYPE); context.phaseAdvance(); } return; @@ -182,7 +182,7 @@ public void read() { protected boolean parseExpectedTypeAndDetermineIfNoBadProblems(String type) { if (isJsonMarkerType(type)) { - genericSubType = YormlUtils.TYPE_JSON; + genericSubType = YomlUtils.TYPE_JSON; } else { GenericsParse gp = new GenericsParse(type); if (gp.warning!=null) { @@ -215,7 +215,7 @@ private Object newInstance(Class javaType, String explicitTypeName) { if (locallyWantedType==null) { // rely on type registry - return config.getTypeRegistry().newInstance(explicitTypeName, Yorml.newInstance(config)); + return config.getTypeRegistry().newInstance(explicitTypeName, Yoml.newInstance(config)); } // create it ourselves, but first assert it matches expected @@ -278,7 +278,7 @@ protected void readIterableInto(Collection joq, Iterable yo) { int index = 0; for (Object yi: yo) { - jo.add(converter.read( new YormlContextForRead(yi, context.getJsonPath()+"["+index+"]", genericSubType) )); + jo.add(converter.read( new YomlContextForRead(yi, context.getJsonPath()+"["+index+"]", genericSubType) )); index++; } @@ -287,7 +287,7 @@ protected void readIterableInto(Collection joq, Iterable yo) { public void write() { if (!canDoWrite()) return; - boolean isPureJson = YormlUtils.JsonMarker.isPureJson(getJavaObject()); + boolean isPureJson = YomlUtils.JsonMarker.isPureJson(getJavaObject()); // if expecting json then: if (isJsonMarkerTypeExpected()) { @@ -358,11 +358,11 @@ public void write() { } } } - if ((YormlUtils.TYPE_LIST.equals(actualTypeName) || (YormlUtils.TYPE_SET.equals(actualTypeName))) && genericSubType==null) { + if ((YomlUtils.TYPE_LIST.equals(actualTypeName) || (YomlUtils.TYPE_SET.equals(actualTypeName))) && genericSubType==null) { if (JsonMarker.isPureJson(getJavaObject()) && !Iterables.isEmpty((Iterable)getJavaObject())) { writeWithoutTypeInformation = false; - actualTypeName = actualTypeName+"<"+YormlUtils.TYPE_JSON+">"; - genericSubType = YormlUtils.TYPE_JSON; + actualTypeName = actualTypeName+"<"+YomlUtils.TYPE_JSON+">"; + genericSubType = YomlUtils.TYPE_JSON; } } @@ -380,7 +380,7 @@ public void write() { int index = 0; for (Object ji: (Iterable)getJavaObject()) { - list.add(converter.write( new YormlContextForWrite(ji, context.getJsonPath()+"["+index+"]", genericSubType) )); + list.add(converter.write( new YomlContextForWrite(ji, context.getJsonPath()+"["+index+"]", genericSubType) )); index++; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java similarity index 89% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeMap.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java index 773bb1428e..46e0157c1c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import java.util.LinkedHashMap; import java.util.Map; @@ -27,18 +27,18 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; -import org.apache.brooklyn.util.yorml.Yorml; -import org.apache.brooklyn.util.yorml.YormlContextForRead; -import org.apache.brooklyn.util.yorml.YormlContextForWrite; -import org.apache.brooklyn.util.yorml.internal.YormlUtils; -import org.apache.brooklyn.util.yorml.internal.YormlUtils.GenericsParse; +import org.apache.brooklyn.util.yoml.Yoml; +import org.apache.brooklyn.util.yoml.YomlContextForRead; +import org.apache.brooklyn.util.yoml.YomlContextForWrite; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; +import org.apache.brooklyn.util.yoml.internal.YomlUtils.GenericsParse; import com.google.common.base.Objects; -public class InstantiateTypeMap extends YormlSerializerComposition { +public class InstantiateTypeMap extends YomlSerializerComposition { - private static final String MAP = YormlUtils.TYPE_MAP; + private static final String MAP = YomlUtils.TYPE_MAP; // these mapping class settings are slightly OTT but keeping for consistency with list and in case we have 'alias' @@ -58,7 +58,7 @@ public class InstantiateTypeMap extends YormlSerializerComposition { // TODO does anything fit this category? we serialize as a json map, including the type, and use xxx.put(...) to read in ); - protected YormlSerializerWorker newWorker() { + protected YomlSerializerWorker newWorker() { return new Worker(); } @@ -134,7 +134,7 @@ public void read() { jo = (Map) Reflections.invokeConstructorFromArgsIncludingPrivate(typeAliases.get(alias)).get(); } else { try { - jo = (Map) config.getTypeRegistry().newInstance(actualBaseTypeName, Yorml.newInstance(config)); + jo = (Map) config.getTypeRegistry().newInstance(actualBaseTypeName, Yoml.newInstance(config)); } catch (Exception e) { throw new IllegalStateException("Cannot instantiate "+actualBaseTypeName, e); } @@ -165,9 +165,9 @@ public void read() { ev1 = m.get("value"); } if (ek2==null) { - ek2 = converter.read(new YormlContextForRead(ek1, newPath+"/key", genericKeySubType)); + ek2 = converter.read(new YomlContextForRead(ek1, newPath+"/key", genericKeySubType)); } - ev2 = converter.read(new YormlContextForRead(ev1, newPath+"/value", genericValueSubType)); + ev2 = converter.read(new YomlContextForRead(ev1, newPath+"/value", genericValueSubType)); jom.put(ek2, ev2); } else { // must be an entry set, so invalid @@ -180,7 +180,7 @@ public void read() { } else if (value instanceof Map) { for (Map.Entry me: ((Map)value).entrySet()) { - Object v = converter.read(new YormlContextForRead(me.getValue(), context.getJsonPath()+"/"+me.getKey(), genericValueSubType)); + Object v = converter.read(new YomlContextForRead(me.getValue(), context.getJsonPath()+"/"+me.getKey(), genericValueSubType)); jom.put(me.getKey(), v); } @@ -206,8 +206,8 @@ private String getAlias(Class type) { protected boolean parseExpectedTypeAndDetermineIfNoBadProblems(String type) { if (isJsonMarkerType(type)) { - genericKeySubType = YormlUtils.TYPE_JSON; - genericValueSubType = YormlUtils.TYPE_JSON; + genericKeySubType = YomlUtils.TYPE_JSON; + genericValueSubType = YomlUtils.TYPE_JSON; } else { GenericsParse gp = new GenericsParse(type); if (gp.warning!=null) { @@ -245,7 +245,7 @@ public void write() { String expectedGenericKeySubType = genericKeySubType; String expectedGenericValueSubType = genericValueSubType; - boolean isPureJson = YormlUtils.JsonMarker.isPureJson(getJavaObject()); + boolean isPureJson = YomlUtils.JsonMarker.isPureJson(getJavaObject()); // if expecting json then if (isJsonMarkerTypeExpected()) { @@ -289,12 +289,12 @@ public void write() { boolean isEmpty; if (allKeysString) { if (isPureJson && genericValueSubType==null) { - genericValueSubType = YormlUtils.TYPE_JSON; + genericValueSubType = YomlUtils.TYPE_JSON; } MutableMap out = MutableMap.of(); for (Map.Entry me: jo.entrySet()) { - Object v = converter.write(new YormlContextForWrite(me.getValue(), context.getJsonPath()+"/"+me.getKey(), genericValueSubType)); + Object v = converter.write(new YomlContextForWrite(me.getValue(), context.getJsonPath()+"/"+me.getKey(), genericValueSubType)); out.put(me.getKey(), v); } isEmpty = out.isEmpty(); @@ -304,12 +304,12 @@ public void write() { int i=0; MutableList out = MutableList.of(); for (Map.Entry me: jo.entrySet()) { - Object v = converter.write(new YormlContextForWrite(me.getValue(), context.getJsonPath()+"["+i+"]/value", genericValueSubType)); + Object v = converter.write(new YomlContextForWrite(me.getValue(), context.getJsonPath()+"["+i+"]/value", genericValueSubType)); if (me.getKey() instanceof String) { out.add(MutableMap.of(me.getKey(), v)); } else { - Object k = converter.write(new YormlContextForWrite(me.getKey(), context.getJsonPath()+"["+i+"]/key", genericValueSubType)); + Object k = converter.write(new YomlContextForWrite(me.getKey(), context.getJsonPath()+"["+i+"]/key", genericValueSubType)); out.add(MutableMap.of("key", k, "value", v)); } i++; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypePrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java similarity index 89% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypePrimitive.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java index f2e29fc515..d1772217cc 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypePrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java @@ -16,16 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.internal.YormlUtils; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; -public class InstantiateTypePrimitive extends YormlSerializerComposition { +public class InstantiateTypePrimitive extends YomlSerializerComposition { - protected YormlSerializerWorker newWorker() { + protected YomlSerializerWorker newWorker() { return new Worker(); } @@ -76,7 +76,7 @@ public void read() { public void write() { if (!canDoWrite()) return; - if (!YormlUtils.JsonMarker.isPureJson(getJavaObject())) return; + if (!YomlUtils.JsonMarker.isPureJson(getJavaObject())) return; if (isJsonPrimitiveType(getExpectedTypeJava()) || isJsonMarkerTypeExpected()) { storeWriteObjectAndAdvance(getJavaObject()); @@ -90,7 +90,7 @@ public void write() { config.getTypeRegistry().getTypeName(getJavaObject()), getJavaObject()); - context.phaseInsert(YormlContext.StandardPhases.MANIPULATING); + context.phaseInsert(YomlContext.StandardPhases.MANIPULATING); storeWriteObjectAndAdvance(map); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java similarity index 81% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java index 96574e6998..d191168564 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/InstantiateTypeWorkerAbstract.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import java.util.List; import java.util.Map; @@ -27,13 +27,13 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Boxing; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.YormlSerializer; -import org.apache.brooklyn.util.yorml.internal.SerializersOnBlackboard; -import org.apache.brooklyn.util.yorml.internal.YormlUtils; -import org.apache.brooklyn.util.yorml.serializers.YormlSerializerComposition.YormlSerializerWorker; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; +import org.apache.brooklyn.util.yoml.serializers.YomlSerializerComposition.YomlSerializerWorker; -public abstract class InstantiateTypeWorkerAbstract extends YormlSerializerWorker { +public abstract class InstantiateTypeWorkerAbstract extends YomlSerializerWorker { protected boolean isJsonPrimitiveType(Class type) { if (type==null) return false; @@ -49,24 +49,24 @@ protected boolean isJsonMarkerTypeExpected() { return isJsonMarkerType(context.getExpectedType()); } protected boolean isJsonMarkerType(String typeName) { - return YormlUtils.TYPE_JSON.equals(typeName); + return YomlUtils.TYPE_JSON.equals(typeName); } protected Class getSpecialKnownTypeName(String typename) { - if (YormlUtils.TYPE_STRING.equals(typename)) return String.class; - if (YormlUtils.TYPE_LIST.equals(typename)) return List.class; - if (YormlUtils.TYPE_SET.equals(typename)) return Set.class; - if (YormlUtils.TYPE_MAP.equals(typename)) return Map.class; + if (YomlUtils.TYPE_STRING.equals(typename)) return String.class; + if (YomlUtils.TYPE_LIST.equals(typename)) return List.class; + if (YomlUtils.TYPE_SET.equals(typename)) return Set.class; + if (YomlUtils.TYPE_MAP.equals(typename)) return Map.class; return Boxing.boxedType( Boxing.getPrimitiveType(typename).orNull() ); } protected boolean canDoRead() { - if (!context.isPhase(YormlContext.StandardPhases.HANDLING_TYPE)) return false; + if (!context.isPhase(YomlContext.StandardPhases.HANDLING_TYPE)) return false; if (hasJavaObject()) return false; return true; } protected boolean canDoWrite() { - if (!context.isPhase(YormlContext.StandardPhases.HANDLING_TYPE)) return false; + if (!context.isPhase(YomlContext.StandardPhases.HANDLING_TYPE)) return false; if (hasYamlObject()) return false; if (!hasJavaObject()) return false; if (JavaFieldsOnBlackboard.isPresent(blackboard)) return false; @@ -75,7 +75,7 @@ protected boolean canDoWrite() { protected void addSerializers(String type) { if (!type.equals(context.getExpectedType())) { - Set serializers = MutableSet.of(); + Set serializers = MutableSet.of(); config.typeRegistry.collectSerializers(type, serializers, MutableSet.of()); SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(serializers); } @@ -83,7 +83,7 @@ protected void addSerializers(String type) { protected void storeReadObjectAndAdvance(Object result, boolean addPhases) { if (addPhases) { - context.phaseInsert(YormlContext.StandardPhases.MANIPULATING, YormlContext.StandardPhases.HANDLING_FIELDS); + context.phaseInsert(YomlContext.StandardPhases.MANIPULATING, YomlContext.StandardPhases.HANDLING_FIELDS); } context.setJavaObject(result); context.phaseAdvance(); @@ -149,4 +149,4 @@ protected MutableMap writingMapWithTypeAndLiteralValue(String ty protected void warn(String message) { ReadingTypeOnBlackboard.get(blackboard).addNote(message); } -} \ No newline at end of file +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/JavaFieldsOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/JavaFieldsOnBlackboard.java similarity index 81% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/JavaFieldsOnBlackboard.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/JavaFieldsOnBlackboard.java index 89275c2932..d974780408 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/JavaFieldsOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/JavaFieldsOnBlackboard.java @@ -16,19 +16,19 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import java.util.List; import java.util.Map; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.YormlException; -import org.apache.brooklyn.util.yorml.YormlRequirement; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.YomlException; +import org.apache.brooklyn.util.yoml.YomlRequirement; /** Indicates that something has handled the type * (on read, creating the java object, and on write, setting the `type` field in the yaml object) * and made a determination of what fields need to be handled */ -public class JavaFieldsOnBlackboard implements YormlRequirement { +public class JavaFieldsOnBlackboard implements YomlRequirement { private static String KEY = JavaFieldsOnBlackboard.class.getName(); @@ -51,9 +51,9 @@ public static JavaFieldsOnBlackboard create(Map blackboard) { List fieldsToWriteFromJava; @Override - public void checkCompletion(YormlContext context) { + public void checkCompletion(YomlContext context) { if (!fieldsToWriteFromJava.isEmpty()) { - throw new YormlException("Incomplete write of Java object data: "+fieldsToWriteFromJava, context); + throw new YomlException("Incomplete write of Java object data: "+fieldsToWriteFromJava, context); } } -} \ No newline at end of file +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ReadingTypeOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java similarity index 64% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ReadingTypeOnBlackboard.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java index d683c0f0ba..8b8b50c19e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/ReadingTypeOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java @@ -16,20 +16,20 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import java.util.Map; import java.util.Set; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.YormlContextForRead; -import org.apache.brooklyn.util.yorml.YormlContextForWrite; -import org.apache.brooklyn.util.yorml.YormlException; -import org.apache.brooklyn.util.yorml.YormlRequirement; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.YomlContextForRead; +import org.apache.brooklyn.util.yoml.YomlContextForWrite; +import org.apache.brooklyn.util.yoml.YomlException; +import org.apache.brooklyn.util.yoml.YomlRequirement; -public class ReadingTypeOnBlackboard implements YormlRequirement { +public class ReadingTypeOnBlackboard implements YomlRequirement { Set errorNotes = MutableSet.of(); @@ -45,11 +45,11 @@ public static ReadingTypeOnBlackboard get(Map blackboard) { } @Override - public void checkCompletion(YormlContext context) { - if (context instanceof YormlContextForRead && context.getJavaObject()!=null) return; - if (context instanceof YormlContextForWrite && context.getYamlObject()!=null) return; - if (errorNotes.isEmpty()) throw new YormlException("No means to identify type to instantiate", context); - throw new YormlException(Strings.join(errorNotes, "; "), context); + public void checkCompletion(YomlContext context) { + if (context instanceof YomlContextForRead && context.getJavaObject()!=null) return; + if (context instanceof YomlContextForWrite && context.getYamlObject()!=null) return; + if (errorNotes.isEmpty()) throw new YomlException("No means to identify type to instantiate", context); + throw new YomlException(Strings.join(errorNotes, "; "), context); } public void addNote(String message) { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java similarity index 82% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java index 601c962423..8e3bab4848 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YamlKeysOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java @@ -16,17 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import java.util.Map; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.YormlException; -import org.apache.brooklyn.util.yorml.YormlRequirement; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.YomlException; +import org.apache.brooklyn.util.yoml.YomlRequirement; /** Keys from a YAML map that still need to be handled */ -public class YamlKeysOnBlackboard implements YormlRequirement { +public class YamlKeysOnBlackboard implements YomlRequirement { private static String KEY = YamlKeysOnBlackboard.class.getName(); @@ -53,11 +53,11 @@ public static YamlKeysOnBlackboard create(Map blackboard) { Map yamlKeysToReadToJava; @Override - public void checkCompletion(YormlContext context) { + public void checkCompletion(YomlContext context) { if (!yamlKeysToReadToJava.isEmpty()) { // TODO limit toString to depth 2 ? - throw new YormlException("Incomplete read of YAML keys: "+yamlKeysToReadToJava, context); + throw new YomlException("Incomplete read of YAML keys: "+yamlKeysToReadToJava, context); } } -} \ No newline at end of file +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java similarity index 80% rename from utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java index 30657f5390..c61e579d75 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yorml/serializers/YormlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.serializers; +package org.apache.brooklyn.util.yoml.serializers; import java.util.Map; import java.util.Set; @@ -26,27 +26,27 @@ import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Boxing; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yorml.YormlContext; -import org.apache.brooklyn.util.yorml.YormlContextForRead; -import org.apache.brooklyn.util.yorml.YormlContextForWrite; -import org.apache.brooklyn.util.yorml.YormlSerializer; -import org.apache.brooklyn.util.yorml.internal.YormlConfig; -import org.apache.brooklyn.util.yorml.internal.YormlConverter; -import org.apache.brooklyn.util.yorml.internal.YormlUtils; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.YomlContextForRead; +import org.apache.brooklyn.util.yoml.YomlContextForWrite; +import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.internal.YomlConfig; +import org.apache.brooklyn.util.yoml.internal.YomlConverter; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; -public abstract class YormlSerializerComposition implements YormlSerializer { +public abstract class YomlSerializerComposition implements YomlSerializer { - protected abstract YormlSerializerWorker newWorker(); + protected abstract YomlSerializerWorker newWorker(); - public abstract static class YormlSerializerWorker { + public abstract static class YomlSerializerWorker { - protected YormlConverter converter; - protected YormlContext context; - protected YormlContextForRead readContext; - protected YormlConfig config; + protected YomlConverter converter; + protected YomlContext context; + protected YomlContextForRead readContext; + protected YomlConfig config; protected Map blackboard; - private void initRead(YormlContextForRead context, YormlConverter converter, Map blackboard) { + private void initRead(YomlContextForRead context, YomlConverter converter, Map blackboard) { if (this.context!=null) throw new IllegalStateException("Already initialized, for "+context); this.context = context; this.readContext = context; @@ -55,7 +55,7 @@ private void initRead(YormlContextForRead context, YormlConverter converter, Map this.blackboard = blackboard; } - private void initWrite(YormlContextForWrite context, YormlConverter converter, Map blackboard) { + private void initWrite(YomlContextForWrite context, YomlConverter converter, Map blackboard) { if (this.context!=null) throw new IllegalStateException("Already initialized, for "+context); this.context = context; this.converter = converter; @@ -123,7 +123,7 @@ protected Set findAllKeyManglesYamlKeys(String targetKey) { Set result = MutableSet.of(); YamlKeysOnBlackboard ykb = YamlKeysOnBlackboard.peek(blackboard); for (Object k: ykb.yamlKeysToReadToJava.keySet()) { - if (k instanceof String && YormlUtils.mangleable(targetKey, (String)k)) { + if (k instanceof String && YomlUtils.mangleable(targetKey, (String)k)) { result.add((String)k); } } @@ -142,8 +142,8 @@ protected boolean isJsonPrimitiveObject(Object o) { } @Override - public void read(YormlContextForRead context, YormlConverter converter, Map blackboard) { - YormlSerializerWorker worker; + public void read(YomlContextForRead context, YomlConverter converter, Map blackboard) { + YomlSerializerWorker worker; try { worker = newWorker(); } catch (Exception e) { throw Exceptions.propagate(e); } @@ -152,8 +152,8 @@ public void read(YormlContextForRead context, YormlConverter converter, Map blackboard) { - YormlSerializerWorker worker; + public void write(YomlContextForWrite context, YomlConverter converter, Map blackboard) { + YomlSerializerWorker worker; try { worker = newWorker(); } catch (Exception e) { throw Exceptions.propagate(e); } @@ -162,7 +162,7 @@ public void write(YormlContextForWrite context, YormlConverter converter, Map extras) { + protected static YomlTestFixture extended0ExplicitFieldFixture(List extras) { return commonExplicitFieldFixtureKeyNameAlias(", defaultValue: { type: string, value: bob }"). addType("shape-with-size", "{ type: \"java:"+ShapeWithSize.class.getName()+"\", interfaceTypes: [ shape ] }", MutableList.copyOf(extras).append(explicitFieldSerializer("{ fieldName: size, alias: shape-size }")) ); } - protected static YormlTestFixture extended1ExplicitFieldFixture() { + protected static YomlTestFixture extended1ExplicitFieldFixture() { return extended0ExplicitFieldFixture( MutableList.of( explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name }")) ); } @Test public void testExplicitFieldSerializersAreCollected() { - YormlTestFixture ytc = extended1ExplicitFieldFixture(); - Set serializers = MutableSet.of(); + YomlTestFixture ytc = extended1ExplicitFieldFixture(); + Set serializers = MutableSet.of(); ytc.tr.collectSerializers("shape-with-size", serializers, MutableSet.of()); Assert.assertEquals(serializers.size(), 3, "Wrong serializers: "+serializers); } @@ -204,7 +204,7 @@ public void testInheritedAliasIsUsed() { @Test public void testOverriddenKeyNameNotUsed() { try { - YormlTestFixture x = extended1ExplicitFieldFixture().read(EXTENDED_IN_ORIGINAL_KEYNAME, null); + YomlTestFixture x = extended1ExplicitFieldFixture().read(EXTENDED_IN_ORIGINAL_KEYNAME, null); Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); } catch (Exception e) { Asserts.expectedFailureContains(e, "shape-name", "diamond"); @@ -247,7 +247,7 @@ public void testInheritedAliasIsNotUsedIfRestricted() { // same as testInheritedAliasIsUsed -- except fails because we say aliases-inherited: false String json = "{ type: shape-with-size, my-name: diamond, size: 2, fields: { color: black } }"; try { - YormlTestFixture x = extended0ExplicitFieldFixture( MutableList.of( + YomlTestFixture x = extended0ExplicitFieldFixture( MutableList.of( explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name, aliasesInherited: false }")) ) .read( json, null ); Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); @@ -269,7 +269,7 @@ public void testFieldNameAsAlias() { public void testFieldNameAsAliasExcludedWhenStrict() { String json = "{ type: shape-with-size, name: diamond, size: 2, fields: { color: black } }"; try { - YormlTestFixture x = extended0ExplicitFieldFixture( MutableList.of( + YomlTestFixture x = extended0ExplicitFieldFixture( MutableList.of( explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name, aliasesStrict: true }")) ) .read( json, null ); Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); @@ -291,7 +291,7 @@ public void testFieldNameMangled() { @Test public void testFieldNameManglesExcludedWhenStrict() { try { - YormlTestFixture x = extended0ExplicitFieldFixture( MutableList.of( + YomlTestFixture x = extended0ExplicitFieldFixture( MutableList.of( explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name, aliasesStrict: true }")) ) .read( EXTENDED_IN_1_MANGLED, null ); Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); @@ -302,7 +302,7 @@ public void testFieldNameManglesExcludedWhenStrict() { static String SIMPLE_IN_ALL_FIELDS_EXPLICIT = "{ color: black, name: diamond }"; @Test public void testAllFieldsExplicit() { - YormlTestFixture y = YormlTestFixture.newInstance(). + YomlTestFixture y = YomlTestFixture.newInstance(). addType("shape", Shape.class, MutableList.of(new AllFieldsExplicit())); y.read( SIMPLE_IN_ALL_FIELDS_EXPLICIT, "shape" ).assertResult( SIMPLE_OUT ). diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MapListTests.java similarity index 97% rename from utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java rename to utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MapListTests.java index b4f474a2a8..92a9774da6 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MapListTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MapListTests.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.tests; +package org.apache.brooklyn.util.yoml.tests; import java.math.RoundingMode; import java.util.Arrays; @@ -29,15 +29,15 @@ import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; -import org.apache.brooklyn.util.yorml.serializers.AllFieldsExplicit; -import org.apache.brooklyn.util.yorml.tests.YormlBasicTests.Shape; +import org.apache.brooklyn.util.yoml.serializers.AllFieldsExplicit; +import org.apache.brooklyn.util.yoml.tests.YomlBasicTests.Shape; import org.testng.Assert; import org.testng.annotations.Test; /** Tests that explicit fields can be set at the outer level in yaml. */ public class MapListTests { - YormlTestFixture y = YormlTestFixture.newInstance(); + YomlTestFixture y = YomlTestFixture.newInstance(); String MAP1_JSON = "{ a: 1, b: bbb }"; Map MAP1_OBJ = MutableMap.of("a", 1, "b", "bbb"); diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java similarity index 88% rename from utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java rename to utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java index 8411f7daf1..a1c4b870d8 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/MockYormlTypeRegistry.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.tests; +package org.apache.brooklyn.util.yoml.tests; import java.util.Collection; import java.util.List; @@ -31,14 +31,14 @@ import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yaml.Yamls; -import org.apache.brooklyn.util.yorml.Yorml; -import org.apache.brooklyn.util.yorml.YormlSerializer; -import org.apache.brooklyn.util.yorml.YormlTypeRegistry; -import org.apache.brooklyn.util.yorml.internal.YormlUtils; +import org.apache.brooklyn.util.yoml.Yoml; +import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.YomlTypeRegistry; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; import com.google.common.collect.Iterables; -public class MockYormlTypeRegistry implements YormlTypeRegistry { +public class MockYomlTypeRegistry implements YomlTypeRegistry { static class MockRegisteredType { final String id; @@ -46,10 +46,10 @@ static class MockRegisteredType { final Set interfaceTypes; final Class javaType; - final List serializers; + final List serializers; final Object yamlDefinition; - public MockRegisteredType(String id, String parentType, Class javaType, Collection interfaceTypes, List serializers, Object yamlDefinition) { + public MockRegisteredType(String id, String parentType, Class javaType, Collection interfaceTypes, List serializers, Object yamlDefinition) { super(); this.id = id; this.parentType = parentType; @@ -63,16 +63,16 @@ public MockRegisteredType(String id, String parentType, Class javaType, Colle Map types = MutableMap.of(); @Override - public Object newInstance(String typeName, Yorml yorml) { - return newInstanceMaybe(typeName, yorml).get(); + public Object newInstance(String typeName, Yoml yoml) { + return newInstanceMaybe(typeName, yoml).get(); } @Override - public Maybe newInstanceMaybe(String typeName, Yorml yorml) { + public Maybe newInstanceMaybe(String typeName, Yoml yoml) { MockRegisteredType type = types.get(typeName); if (type!=null && type.yamlDefinition!=null) { String parentTypeName = type.parentType; if (type.parentType==null && type.javaType!=null) parentTypeName = getDefaultTypeNameOfClass(type.javaType); - return Maybe.of(yorml.readFromYamlObject(type.yamlDefinition, parentTypeName)); + return Maybe.of(yoml.readFromYamlObject(type.yamlDefinition, parentTypeName)); } Class javaType = getJavaType(type, typeName); if (javaType==null) { @@ -99,7 +99,7 @@ protected Class getJavaType(MockRegisteredType registeredType, String typeNam if (result==null && typeName==null) return null; if (result==null) result = Boxing.boxedType(Boxing.getPrimitiveType(typeName).orNull()); - if (result==null && YormlUtils.TYPE_STRING.equals(typeName)) result = String.class; + if (result==null && YomlUtils.TYPE_STRING.equals(typeName)) result = String.class; if (result==null && typeName.startsWith("java:")) { typeName = Strings.removeFromStart(typeName, "java:"); @@ -117,7 +117,7 @@ protected Class getJavaType(MockRegisteredType registeredType, String typeNam public void put(String typeName, Class javaType) { put(typeName, javaType, null); } - public void put(String typeName, Class javaType, List serializers) { + public void put(String typeName, Class javaType, List serializers) { types.put(typeName, new MockRegisteredType(typeName, "java:"+javaType.getName(), javaType, MutableSet.of(), serializers, null)); } @@ -126,7 +126,7 @@ public void put(String typeName, String yamlDefinition) { put(typeName, yamlDefinition, null); } @SuppressWarnings("unchecked") - public void put(String typeName, String yamlDefinition, List serializers) { + public void put(String typeName, String yamlDefinition, List serializers) { Object yamlObject = Iterables.getOnlyElement( Yamls.parseAll(yamlDefinition) ); if (!(yamlObject instanceof Map)) throw new IllegalArgumentException("Mock only supports map definitions"); @@ -165,7 +165,7 @@ protected String getDefaultTypeNameOfClass(Class type) { } @Override - public void collectSerializers(String typeName, Collection serializers, Set typesVisited) { + public void collectSerializers(String typeName, Collection serializers, Set typesVisited) { if (typeName==null || !typesVisited.add(typeName)) return; MockRegisteredType rt = types.get(typeName); if (rt==null || rt.serializers==null) return; diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlBasicTests.java similarity index 89% rename from utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java rename to utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlBasicTests.java index a2a28bbb15..14ae35dec3 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlBasicTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlBasicTests.java @@ -16,14 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.tests; +package org.apache.brooklyn.util.yoml.tests; import java.math.RoundingMode; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.Jsonya; import org.apache.brooklyn.util.exceptions.Exceptions; -import org.apache.brooklyn.util.yorml.Yorml; +import org.apache.brooklyn.util.yoml.Yoml; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; @@ -35,9 +35,9 @@ *

* And shows how to use them at a low level. */ -public class YormlBasicTests { +public class YomlBasicTests { - private static final Logger log = LoggerFactory.getLogger(YormlBasicTests.class); + private static final Logger log = LoggerFactory.getLogger(YomlBasicTests.class); static class Shape { String name; @@ -64,9 +64,9 @@ public String toString() { @Test public void testReadJavaType() { - MockYormlTypeRegistry tr = new MockYormlTypeRegistry(); + MockYomlTypeRegistry tr = new MockYomlTypeRegistry(); tr.put("shape", Shape.class); - Yorml y = Yorml.newInstance(tr); + Yoml y = Yoml.newInstance(tr); Object resultO = y.read("{ type: shape }", "object"); Assert.assertNotNull(resultO); @@ -78,9 +78,9 @@ public void testReadJavaType() { @Test public void testReadFieldInFields() { - MockYormlTypeRegistry tr = new MockYormlTypeRegistry(); + MockYomlTypeRegistry tr = new MockYomlTypeRegistry(); tr.put("shape", Shape.class); - Yorml y = Yorml.newInstance(tr); + Yoml y = Yoml.newInstance(tr); Object resultO = y.read("{ type: shape, fields: { color: red } }", "object"); Assert.assertNotNull(resultO); @@ -92,8 +92,8 @@ public void testReadFieldInFields() { @Test public void testWritePrimitiveFieldInFields() { - MockYormlTypeRegistry tr = new MockYormlTypeRegistry(); - Yorml y = Yorml.newInstance(tr); + MockYomlTypeRegistry tr = new MockYomlTypeRegistry(); + Yoml y = Yoml.newInstance(tr); Shape s = new ShapeWithSize(); s.color = "red"; @@ -110,7 +110,7 @@ public void testWritePrimitiveFieldInFields() { @Test public void testFieldInFieldsUsingTestFixture() { - YormlTestFixture.newInstance().writing( new Shape().color("red") ). + YomlTestFixture.newInstance().writing( new Shape().color("red") ). reading( Jsonya.newInstance().add("type", "java"+":"+Shape.class.getName()) .at("fields").add("color", "red").root().toString() ). @@ -119,26 +119,26 @@ public void testFieldInFieldsUsingTestFixture() { @Test public void testStringPrimitiveWhereTypeKnown() { - YormlTestFixture.newInstance(). + YomlTestFixture.newInstance(). write("hello", "string").assertResult("hello"). read("hello", "string").assertResult("hello"); } @Test public void testStringPrimitiveWhereTypeUnknown() { - YormlTestFixture.newInstance(). + YomlTestFixture.newInstance(). write("hello").assertResult("{ type: string, value: hello }"). read("{ type: string, value: hello }", null).assertResult("hello"); } @Test public void testIntPrimitiveWhereTypeUnknown() { - YormlTestFixture.newInstance(). + YomlTestFixture.newInstance(). write(42).assertResult("{ type: int, value: 42 }"). read("{ type: int, value: 42 }", null).assertResult(42); } @Test public void testEnumWhereTypeKnown() { - YormlTestFixture.newInstance(). + YomlTestFixture.newInstance(). addType("rounding-mode", RoundingMode.class). write(RoundingMode.HALF_EVEN, "rounding-mode").assertResult(RoundingMode.HALF_EVEN.name()). read(RoundingMode.HALF_EVEN.name(), "rounding-mode").assertResult(RoundingMode.HALF_EVEN). @@ -148,7 +148,7 @@ public void testEnumWhereTypeKnown() { @Test public void testEnumWhereTypeUnknown() { String json = "{ type: rounding-mode, value: "+RoundingMode.HALF_EVEN.name()+" }"; - YormlTestFixture.newInstance(). + YomlTestFixture.newInstance(). addType("rounding-mode", RoundingMode.class). write(RoundingMode.HALF_EVEN).assertResult(json). read(json, null).assertResult(RoundingMode.HALF_EVEN); @@ -156,7 +156,7 @@ public void testEnumWhereTypeUnknown() { @Test public void testRegisteredType() { - YormlTestFixture.newInstance(). + YomlTestFixture.newInstance(). addType("shape", Shape.class). writing(new Shape()). reading( "{ type: shape }" ). @@ -165,7 +165,7 @@ public void testRegisteredType() { @Test public void testExpectedType() { - YormlTestFixture.newInstance(). + YomlTestFixture.newInstance(). addType("shape", Shape.class). writing(new Shape().color("red"), "shape"). reading( "{ fields: { color: red } }", "shape" ). @@ -175,7 +175,7 @@ public void testExpectedType() { @Test public void testExtraFieldError() { try { - YormlTestFixture.newInstance(). + YomlTestFixture.newInstance(). addType("shape", Shape.class). read( "{ type: shape, fields: { size: 4 } }", "shape" ); Asserts.shouldHaveFailedPreviously("should complain about fields still existing"); @@ -204,7 +204,7 @@ public String toString() { @Test public void testFieldInExtendedClassInFields() { - YormlTestFixture.newInstance().writing( new ShapeWithSize().size(4).color("red") ). + YomlTestFixture.newInstance().writing( new ShapeWithSize().size(4).color("red") ). reading( Jsonya.newInstance().add("type", "java"+":"+ShapeWithSize.class.getName()) .at("fields").add("color", "red", "size", 4).root().toString() ). @@ -214,7 +214,7 @@ public void testFieldInExtendedClassInFields() { @Test public void testFieldInExtendedClassInFieldsDefault() { // note we get 0 written - YormlTestFixture.newInstance().writing( new ShapeWithSize().color("red") ). + YomlTestFixture.newInstance().writing( new ShapeWithSize().color("red") ). reading( Jsonya.newInstance().add("type", "java"+":"+ShapeWithSize.class.getName()) .at("fields").add("color", "red", "size", 0).root().toString() ). @@ -225,7 +225,7 @@ public void testFieldInExtendedClassInFieldsDefault() { @Test public void testFailOnUnknownType() { try { - YormlTestFixture ytc = YormlTestFixture.newInstance().read("{ type: shape }", null); + YomlTestFixture ytc = YomlTestFixture.newInstance().read("{ type: shape }", null); Asserts.shouldHaveFailedPreviously("Got "+ytc.lastReadResult+" when we should have failed due to unknown type shape"); } catch (Exception e) { try { @@ -256,7 +256,7 @@ public void testWriteComplexFieldInFields() { pair.shape1 = new Shape().color("red"); pair.shape2 = new ShapeWithSize().size(8).color("blue"); - YormlTestFixture.newInstance(). + YomlTestFixture.newInstance(). addType("shape", Shape.class). addType("shape-with-size", ShapeWithSize.class). addType("shape-pair", ShapePair.class). @@ -292,7 +292,7 @@ public void testStaticNotWrittenButExtendedItemsAre() { ShapeWithWeirdFields.aStatic = 2; shape.aTransient = 12; - YormlTestFixture.newInstance(). + YomlTestFixture.newInstance(). addType("shape-weird", ShapeWithWeirdFields.class). writing(shape).reading("{ type: shape-weird, " + "fields: { name: normal-trust-me, " @@ -306,7 +306,7 @@ public void testStaticNotWrittenButExtendedItemsAre() { public void testStaticNotRead() { ShapeWithWeirdFields.aStatic = 3; try { - YormlTestFixture.newInstance(). + YomlTestFixture.newInstance(). addType("shape-weird", ShapeWithWeirdFields.class). read("{ type: shape-weird, " + "fields: { aStatic: 4 " + "} }", null); diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java similarity index 68% rename from utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java rename to utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java index 56992e0055..d6441a5b5b 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yorml/tests/YormlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yorml.tests; +package org.apache.brooklyn.util.yoml.tests; import java.util.Collection; import java.util.List; @@ -24,16 +24,16 @@ import org.apache.brooklyn.util.collections.Jsonya; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yorml.Yorml; -import org.apache.brooklyn.util.yorml.YormlSerializer; +import org.apache.brooklyn.util.yoml.Yoml; +import org.apache.brooklyn.util.yoml.YomlSerializer; import org.testng.Assert; -public class YormlTestFixture { +public class YomlTestFixture { - public static YormlTestFixture newInstance() { return new YormlTestFixture(); } + public static YomlTestFixture newInstance() { return new YomlTestFixture(); } - MockYormlTypeRegistry tr = new MockYormlTypeRegistry(); - Yorml y = Yorml.newInstance(tr); + MockYomlTypeRegistry tr = new MockYomlTypeRegistry(); + Yoml y = Yoml.newInstance(tr); Object writeObject; String writeObjectExpectedType; @@ -43,40 +43,40 @@ public class YormlTestFixture { Object lastReadResult; Object lastResult; - public YormlTestFixture writing(Object objectToWrite) { + public YomlTestFixture writing(Object objectToWrite) { return writing(objectToWrite, null); } - public YormlTestFixture writing(Object objectToWrite, String expectedType) { + public YomlTestFixture writing(Object objectToWrite, String expectedType) { writeObject = objectToWrite; writeObjectExpectedType = expectedType; return this; } - public YormlTestFixture reading(String stringToRead) { + public YomlTestFixture reading(String stringToRead) { return reading(stringToRead, null); } - public YormlTestFixture reading(String stringToRead, String expectedType) { + public YomlTestFixture reading(String stringToRead, String expectedType) { readObject = stringToRead; readObjectExpectedType = expectedType; return this; } - public YormlTestFixture write(Object objectToWrite) { + public YomlTestFixture write(Object objectToWrite) { return write(objectToWrite, null); } - public YormlTestFixture write(Object objectToWrite, String expectedType) { + public YomlTestFixture write(Object objectToWrite, String expectedType) { writing(objectToWrite, expectedType); lastWriteResult = y.write(objectToWrite, expectedType); lastResult = lastWriteResult; return this; } - public YormlTestFixture read(String objectToRead, String expectedType) { + public YomlTestFixture read(String objectToRead, String expectedType) { reading(objectToRead, expectedType); lastReadResult = y.read(objectToRead, expectedType); lastResult = lastReadResult; return this; } - public YormlTestFixture assertResult(Object expectation) { + public YomlTestFixture assertResult(Object expectation) { if (expectation instanceof String) { if (lastResult instanceof Map || lastResult instanceof Collection) { assertEqualsIgnoringQuotes(Jsonya.newInstance().add(lastResult).toString(), expectation, "Result as JSON string does not match expectation"); @@ -88,7 +88,7 @@ public YormlTestFixture assertResult(Object expectation) { } return this; } - public YormlTestFixture doReadWriteAssertingJsonMatch() { + public YomlTestFixture doReadWriteAssertingJsonMatch() { read(readObject, readObjectExpectedType); write(writeObject, writeObjectExpectedType); assertEqualsIgnoringQuotes(Jsonya.newInstance().add(lastWriteResult).toString(), readObject, "Write output should match read input"); @@ -102,8 +102,8 @@ static void assertEqualsIgnoringQuotes(Object s1, Object s2, String message) { Assert.assertEquals(s1, s2, message); } - public YormlTestFixture addType(String name, Class type) { tr.put(name, type); return this; } - public YormlTestFixture addType(String name, Class type, List serializers) { tr.put(name, type, serializers); return this; } - public YormlTestFixture addType(String name, String yamlDefinition) { tr.put(name, yamlDefinition); return this; } - public YormlTestFixture addType(String name, String yamlDefinition, List serializers) { tr.put(name, yamlDefinition, serializers); return this; } -} \ No newline at end of file + public YomlTestFixture addType(String name, Class type) { tr.put(name, type); return this; } + public YomlTestFixture addType(String name, Class type, List serializers) { tr.put(name, type, serializers); return this; } + public YomlTestFixture addType(String name, String yamlDefinition) { tr.put(name, yamlDefinition); return this; } + public YomlTestFixture addType(String name, String yamlDefinition, List serializers) { tr.put(name, yamlDefinition, serializers); return this; } +} From f93027198e41fefd782c3a02039dc53332c938f2 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 31 Aug 2016 16:43:49 +0100 Subject: [PATCH 23/77] docs tweaks --- .../org/apache/brooklyn/util/yoml/sketch.md | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md index 0c8aac9684..f096b56732 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md @@ -266,9 +266,6 @@ and the serializations defined for that type specify: * If it is a map with no `type` defined, `type: explicit-field` is added - - - This allows the serialization rules defined on the specific type to kick in to handle `.key` or `.value` entries introduced but not removed. In the case of `explicit-field` (the default type, as shown in the rules above), this will rename either such key `.value` to `field-name` (and give an error if `field-name` is already present). @@ -352,15 +349,28 @@ These are handled as a default set of aliases. ### Primitive types All Java primitive types are known, with their boxed and unboxed names, -and the key `value` can be used to set a value. This is normally not necessary -as where a primitive is expected routines will attempt coercion, but in some -cases it is desired. So for instance a red square could be defined as: +along with `string`. The key `value` can be used to set a value for these. +It's not normally necessary to do this because the parser can usually detect +these types and coercion will be applied wherever one is expected; +it's only needed if the value needs coercing and the target type isn't implicit. +For instance a red square with size 8 could be defined as: ``` - type: shape - name: + color: type: string value: red + size: + type: int + value: 8 +``` + +Or of course the more concise: + +``` +- type: shape + color: red + size: 8 ``` @@ -462,7 +472,7 @@ If we have information about the generic types -- supplied e.g. with a type of ` then coercion will be applied in either of the above syntaxes. -### Where the accepted type is unknown +### Where the expected type is unknown In some instances an expected type may be explicitly `java.lang.Object`, or it may be unknown (eg due to generics). In these cases if no serialization rules are specified, From df4c709a7cb6e99690ef9a90af01718d8aaa7d67 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 5 Sep 2016 18:49:57 +0100 Subject: [PATCH 24/77] minor yoml improvements --- .../org/apache/brooklyn/util/yoml/YomlTypeRegistry.java | 6 ++++++ .../apache/brooklyn/util/yoml/internal/YomlConverter.java | 2 ++ .../util/yoml/serializers/InstantiateTypeFromRegistry.java | 2 +- .../brooklyn/util/yoml/tests/MockYomlTypeRegistry.java | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java index 2f8361293f..38e1e11f54 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java @@ -30,6 +30,12 @@ public interface YomlTypeRegistry { Object newInstance(String type, Yoml yoml); + /** Returns the most-specific Java type implied by the given type in the registry, + * or null if the type is not available in the registry. + *

+ * This is needed so that the right deserialization strategies can be applied for + * things like collections and enums. + */ Class getJavaType(String typeName); String getTypeName(Object obj); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java index 4b061bcdf5..068b1041b8 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java @@ -76,6 +76,7 @@ protected void loopOverSerializers(YomlContext context) { ReadingTypeOnBlackboard.get(blackboard); } +System.out.println("YOML now looking at "+context.getJsonPath()+"/ = "+context.getJavaObject()+" <-> "+context.getYamlObject()+" ("+context.getExpectedType()+")"); while (context.phaseAdvance()) { while (context.phaseStepAdvance() "+context.getYamlObject()); checkCompletion(context, blackboard); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java index b347ea44e4..3f2b4fcd06 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java @@ -53,7 +53,7 @@ public void read() { Maybe resultM = config.getTypeRegistry().newInstanceMaybe((String)type, Yoml.newInstance(config)); if (resultM.isAbsent()) { Class jt = config.getTypeRegistry().getJavaType((String)type); - String message = jt==null ? "Unknown type '"+type+"'" : "Unable to instantiate type '"+type+"' ("+jt+")"; + String message = jt==null ? "Invalid type '"+type+"'" : "Unable to create type '"+type+"' ("+jt+")"; RuntimeException exc = ((Maybe.Absent)resultM).getException(); if (exc!=null) message+=": "+Exceptions.collapseText(exc); warn(message); diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java index a1c4b870d8..c59550d73c 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java @@ -85,7 +85,7 @@ public Maybe newInstanceMaybe(String typeName, Yoml yoml) { @Override public Class getJavaType(String typeName) { if (typeName==null) return null; - // string generics here + // strip generics here if (typeName.indexOf('<')>0) typeName = typeName.substring(0, typeName.indexOf('<')); return getJavaType(types.get(typeName), typeName); } From b3e5169b3db6fe556d563469b3e0a9b6364f09e8 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 2 Sep 2016 15:49:29 +0100 Subject: [PATCH 25/77] camp yoml transformer and type-registry integration fleshing out tests demonstrate adding types and resolving them through the brooklyn type creation api --- .../camp/yoml/BrooklynYomlTypeRegistry.java | 231 ++++++++++++++++++ .../camp/yoml/YomlTypePlanTransformer.java | 123 ++++++++++ ...n.core.typereg.BrooklynTypePlanTransformer | 1 + .../camp/yoml/YomlTypeRegistryTest.java | 96 ++++++++ 4 files changed, 451 insertions(+) create mode 100644 camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java create mode 100644 camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java new file mode 100644 index 0000000000..19c91ba27e --- /dev/null +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java @@ -0,0 +1,231 @@ +package org.apache.brooklyn.camp.yoml; + +import java.util.Collection; +import java.util.Set; + +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; +import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext; +import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; +import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.javalang.Boxing; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yoml.Yoml; +import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.YomlTypeRegistry; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; + +/** + * Provides a bridge so YOML can find things in the Brooklyn type registry. + */ +public class BrooklynYomlTypeRegistry implements YomlTypeRegistry { + + private ManagementContext mgmt; + + /* + * NB, there are a few subtleties here around library loading. For instance, given: + * + * - id: x + * item: { type: X } + * - id: x2 + * item: { type: x } + * - id: cluster-x + * item: { type: cluster, children: [ { type: x }, { type: X } ] } + * + * Assume these items declare different libraries. + * + * We *need* libraries to be used when resolving the reference to a parent java type (e.g. x's parent type X). + * + * We *don't* want libraries to be used transitively, e.g. when x2 or cluster-x refers to x, + * only x's libraries apply to loading X, not x2's libraries. + * + * We *may* want libraries to be used when resolving references to types in a plan besides the parent type; + * e.g. cluster-x's libraries will be needed when resolving its reference to it's child of declared type X. + * But we might *NOT* want to support that, and could instead require that any type referenced elsewhere + * be defined as an explicit YAML type in the registry. This will simplify our lives as we will force all + * java objects to be explicitly defined as a type in the registry. But it means we won't be able to parse + * current plans so for the moment this is deferred, and we'd want to go through a "warning" cycle when + * applying. + * + * (In that last case we could even be stricter and say that any yaml types + * should have a simple single yaml-java bridge eg using a JavaClassNameTypeImplementationPlan + * to facilitate reverse lookup.) + * + * The defaultLoadingContext is used for the first and third cases above. + * The second case is handled by calling back to the registry with a limited context. + * (Note it will require some work to be able to distinguish between the third case and the first, + * as we don't currently have that contextual information when methods here are called.) + * + *

+ * + * One bit of ugly remains: + * + * If we install v1 then v2, cluster-x:1 will pick up x:2. + * We could change this: + * - by encouraging explicit `type: x:1` in the definition of cluster-x + * - by allowing `type: x:.` version shorthand, where `.` means the same version as the calling type + * - by recording locally-preferred types (aka "friends") on a registered type, + * and looking at those friends first; this would be nice in that we could allow private friends + * + * Yoml.read(...) can take a special "top-level type extensions" in order to find friends, + * and/or a special "top-level libraries" in order to resolve types in the first instance. + * These would *not* be passed when resolving a found type. + */ + private RegisteredTypeLoadingContext defaultLoadingContext; + + public BrooklynYomlTypeRegistry(ManagementContext mgmt, RegisteredTypeLoadingContext defaultLoadingContext) { + this.mgmt = mgmt; + this.defaultLoadingContext = defaultLoadingContext; + + } + + protected BrooklynTypeRegistry registry() { + return mgmt.getTypeRegistry(); + } + + @Override + public Maybe newInstanceMaybe(String typeName, Yoml yoml) { + return newInstanceMaybe(typeName, yoml, defaultLoadingContext); + } + + public Maybe newInstanceMaybe(String typeName, Yoml yoml, RegisteredTypeLoadingContext context) { + RegisteredType typeR = registry().get(typeName, context); + + if (typeR!=null) { + RegisteredTypeLoadingContext nextContext = null; + if (context==null) { + nextContext = RegisteredTypeLoadingContexts.alreadyEncountered(MutableSet.of(typeName)); + } else { + if (!context.getAlreadyEncounteredTypes().contains(typeName)) { + // we lose any other contextual information, but that seems _good_ since it was used to find the type, + // we probably now want to shift to the loading context of that type, e.g. the x2 example above; + // we just need to ensure it doesn't try to load a super which is also a sub! + nextContext = RegisteredTypeLoadingContexts.alreadyEncountered(context.getAlreadyEncounteredTypes(), typeName); + } + } + if (nextContext==null) { + // fall through to path below; we have a circular reference, so need to load java instead + } else { + return Maybe.of(registry().create(typeR, context, null)); + } + } + + Class t = null; + { + Exception e = null; + try { + t = getJavaType(null, typeName, context); + } catch (Exception ee) { + Exceptions.propagateIfFatal(ee); + e = ee; + } + if (t==null) { + return Maybe.absent("Neither the type registry nor the classpath could load type "+typeName+ + (e!=null && !Exceptions.isRootBoringClassNotFound(e, typeName) ? ": "+Exceptions.collapseText(e) : "")); + } + } + try { + return Maybe.of((Object)t.newInstance()); + } catch (Exception e) { + return Maybe.absent("Error instantiating type "+typeName+": "+Exceptions.collapseText(e)); + } + } + + @Override + public Object newInstance(String typeN, Yoml yoml) { + return newInstanceMaybe(typeN, yoml).orNull(); + } + + static class YomlClassNotFoundException extends RuntimeException { + private static final long serialVersionUID = 5946251753146668070L; + public YomlClassNotFoundException(String message, Throwable cause) { + super(message, cause); + } + } + + @Override + public Class getJavaType(String typeName) { + try { + return getJavaType(registry().get(typeName), typeName, defaultLoadingContext); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + return null; + } + } + + // TODO use maybe + protected Class getJavaType(RegisteredType typeR, String typeName, RegisteredTypeLoadingContext context) { + Class result = null; + + if (typeR!=null) { + result = RegisteredTypes.peekActualJavaType(typeR); + if (result!=null) return result; + // instantiate + System.out.println("PRE-LOADING to determine type for "+typeR); + Maybe m = newInstanceMaybe(typeName, null, context); + System.out.println("PRE-LOADING DONE, got "+m+" for "+typeR); + if (m.isPresent()) result = m.get().getClass(); + } + + if (result==null && typeName==null) return null; + + // strip generics for the purposes here + if (typeName.indexOf('<')>0) typeName = typeName.substring(0, typeName.indexOf('<')); + + if (result==null) result = Boxing.boxedType(Boxing.getPrimitiveType(typeName).orNull()); + if (result==null && YomlUtils.TYPE_STRING.equals(typeName)) result = String.class; + + if (result==null) { // && typeName.startsWith("java:")) { // TODO require java: prefix? + typeName = Strings.removeFromStart(typeName, "java:"); + BrooklynClassLoadingContext loader = null; + if (context!=null && context.getLoader()!=null) + loader = context.getLoader(); + else + loader = JavaBrooklynClassLoadingContext.create(mgmt); + + Maybe> resultM = loader.tryLoadClass(typeName); + // TODO give better error if absent? + result = resultM.get(); + } + + if (typeR!=null && result!=null) { + RegisteredTypes.cacheActualJavaType(typeR, result); + } + return result; + } + + @Override + public String getTypeName(Object obj) { + // TODO reverse lookup?? + return null; + } + + @Override + public String getTypeNameOfClass(Class type) { + if (type==null) return null; + // TODO reverse lookup?? + // look in catalog for something where plan matches and consists only of type + return getDefaultTypeNameOfClass(type); + } + + protected String getDefaultTypeNameOfClass(Class type) { + Maybe primitive = Boxing.getPrimitiveName(type); + if (primitive.isPresent()) return primitive.get(); + if (String.class.equals(type)) return "string"; + // map and list handled by those serializers + return "java:"+type.getName(); + } + + + @Override + public void collectSerializers(String typeName, Collection serializers, Set typesVisited) { + // TODO wtf? + } + +} diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java new file mode 100644 index 0000000000..d2803eeaf9 --- /dev/null +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java @@ -0,0 +1,123 @@ +package org.apache.brooklyn.camp.yoml; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan; +import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; +import org.apache.brooklyn.core.typereg.AbstractFormatSpecificTypeImplementationPlan; +import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer; +import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan; +import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.yoml.Yoml; +import org.apache.brooklyn.util.yoml.YomlTypeRegistry; + +import com.google.common.collect.ImmutableList; + +/** + * Makes it possible for Brooklyn to resolve YOML items, + * both types registered with the system using YOML + * and plans in the YOML format (sent here as unregistered types), + * and supporting any objects and special handling for "spec" objects. + * + * DONE + * - test programmatic addition and parse (beans) with manual objects + * + * NEXT + * - attach custom serializers + * - support specs from yoml + * - $brooklyn:object(format): + * and $brooklyn:yoml(expected-type): + * - catalog add arbitrary types via yoml, specifying format + * - catalog impl in yoml as test? + * - REST API for deploy accepts specific format, can call yoml (can we test this earlier?) + * + * THEN + * - generate its own documentation + * - persist to yoml + * - yoml allows `constructor: [list]` and `constructor: { mode: static, type: factory, method: newInstance, args: ... }` + * and maybe even `constructor: { mode: chain, steps: [ { mode: constructor, type: Foo.Builder }, { mode: method, method: bar, args: [ true ] }, { mode: method, method: build } ] }` + * - type access control and java instantiation access control ? + */ +public class YomlTypePlanTransformer extends AbstractTypePlanTransformer { + + private static final List FORMATS = ImmutableList.of("yoml"); + + public static final String FORMAT = FORMATS.get(0); + + public YomlTypePlanTransformer() { + super(FORMAT, "YOML Brooklyn syntax", "Standard YOML adapters for Apache Brooklyn including OASIS CAMP"); + } + + @Override + protected double scoreForNullFormat(Object planData, RegisteredType type, RegisteredTypeLoadingContext context) { + Maybe> plan = RegisteredTypes.getAsYamlMap(planData); + if (plan.isAbsent()) return 0; + int score = 0; + if (plan.get().containsKey("type")) score += 5; + if (plan.get().containsKey("services")) score += 2; + // TODO these should become legacy + if (plan.get().containsKey("brooklyn.locations")) score += 1; + if (plan.get().containsKey("brooklyn.policies")) score += 1; + + if (score==0) return 0.1; + return (1.0 - 1.0/score); + } + + @Override + protected double scoreForNonmatchingNonnullFormat(String planFormat, Object planData, RegisteredType type, RegisteredTypeLoadingContext context) { + if (FORMATS.contains(planFormat.toLowerCase())) return 0.9; + return 0; + } + + @Override + protected AbstractBrooklynObjectSpec createSpec(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception { + // TODO + return null; + } + + @Override + protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception { + YomlTypeRegistry tr = new BrooklynYomlTypeRegistry(mgmt, context); + Yoml y = Yoml.newInstance(tr); + // TODO could cache the parse, could cache the instantiation instructions + Object data = type.getPlan().getPlanData(); + + Class expectedSuperType = context.getExpectedJavaSuperType(); + String expectedSuperTypeName = tr.getTypeNameOfClass(expectedSuperType); + + if (data instanceof String) { + return y.read((String) data, expectedSuperTypeName); + } else { + // could do this +// return y.readFromYamlObject(data, expectedSuperTypeName); + // but it should always be a string... + throw new IllegalArgumentException("The implementation plan for '"+type+"' should be a string in order to process as YOML"); + } + } + + @Override + public double scoreForTypeDefinition(String formatCode, Object catalogData) { + // TODO catalog parsing + return 0; + } + + @Override + public List createFromTypeDefinition(String formatCode, Object catalogData) { + // TODO catalog parsing + return null; + } + + + public static class YomlTypeImplementationPlan extends AbstractFormatSpecificTypeImplementationPlan { + public YomlTypeImplementationPlan(TypeImplementationPlan otherPlan) { + super(FORMATS.get(0), String.class, otherPlan); + } + public YomlTypeImplementationPlan(String planData) { + this(new BasicTypeImplementationPlan(FORMATS.get(0), planData)); + } + } +} diff --git a/camp/camp-brooklyn/src/main/resources/META-INF/services/org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer b/camp/camp-brooklyn/src/main/resources/META-INF/services/org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer index 0c6fab3321..122e117c61 100644 --- a/camp/camp-brooklyn/src/main/resources/META-INF/services/org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer +++ b/camp/camp-brooklyn/src/main/resources/META-INF/services/org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer @@ -16,4 +16,5 @@ # specific language governing permissions and limitations # under the License. # +org.apache.brooklyn.camp.yoml.YomlTypePlanTransformer org.apache.brooklyn.camp.brooklyn.spi.creation.CampTypePlanTransformer diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java new file mode 100644 index 0000000000..0705d8eea9 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.yoml; + +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.camp.yoml.YomlTypePlanTransformer.YomlTypeImplementationPlan; +import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport; +import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; +import org.apache.brooklyn.core.typereg.JavaClassNameTypePlanTransformer.JavaClassNameTypeImplementationPlan; +import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class YomlTypeRegistryTest extends BrooklynMgmtUnitTestSupport { + + private BasicBrooklynTypeRegistry registry() { + return (BasicBrooklynTypeRegistry) mgmt.getTypeRegistry(); + } + + private void add(RegisteredType type) { + add(type, false); + } + private void add(RegisteredType type, boolean canForce) { + registry().addToLocalUnpersistedTypeRegistry(type, canForce); + } + + public static class ItemA { + String name; + } + + private final static RegisteredType SAMPLE_TYPE_JAVA = RegisteredTypes.bean("java.A", "1", + new JavaClassNameTypeImplementationPlan(ItemA.class.getName()), ItemA.class); + + @Test + public void testInstantiateYomlPlan() { + add(SAMPLE_TYPE_JAVA); + Object x = registry().createBeanFromPlan("yoml", "{ type: java.A }", null, null); + Assert.assertTrue(x instanceof ItemA); + } + + @Test + public void testInstantiateYomlPlanExplicitField() { + add(SAMPLE_TYPE_JAVA); + Object x = registry().createBeanFromPlan("yoml", "{ type: java.A, fields: { name: Bob } }", null, null); + Assert.assertTrue(x instanceof ItemA); + Assert.assertEquals( ((ItemA)x).name, "Bob" ); + } + + private final static RegisteredType SAMPLE_TYPE_YOML = RegisteredTypes.bean("yoml.A", "1", + new YomlTypeImplementationPlan("{ type: "+ItemA.class.getName() +" }"), ItemA.class); + + @Test + public void testInstantiateYomlBaseType() { + add(SAMPLE_TYPE_YOML); + Object x = registry().createBeanFromPlan("yoml", "{ type: yoml.A }", null, null); + Assert.assertTrue(x instanceof ItemA); + } + + @Test + public void testInstantiateYomlBaseTypeExplicitField() { + add(SAMPLE_TYPE_YOML); + Object x = registry().createBeanFromPlan("yoml", "{ type: yoml.A, fields: { name: Bob } }", null, null); + Assert.assertTrue(x instanceof ItemA); + Assert.assertEquals( ((ItemA)x).name, "Bob" ); + } + + @Test + public void testYomlTypeMissingGiveGoodError() { + try { + Object x = registry().createBeanFromPlan("yoml", "{ type: yoml.A, fields: { name: Bob } }", null, null); + Asserts.shouldHaveFailedPreviously("Expected type resolution failure; instead it loaded "+x); + } catch (Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, "yoml.A", "neither", "registry", "classpath"); + Asserts.expectedFailureDoesNotContain(e, JavaClassNames.simpleClassName(ClassNotFoundException.class)); + } + } + +} From 0abcd6b8f2fdf3c9be3e26c42f5141d4cd44e54e Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 7 Sep 2016 15:48:12 +0100 Subject: [PATCH 26/77] serializers and supertypes/java-type better handling for serializer collection and supertypes/java-type in underlying yoml and brooklyn registry integration and error reporting --- .../camp/yoml/BrooklynYomlTypeRegistry.java | 255 ++++++++++++++---- .../camp/yoml/YomlTypePlanTransformer.java | 47 +++- .../camp/yoml/YomlTypeRegistryTest.java | 42 ++- .../brooklyn/util/yoml/YomlTypeRegistry.java | 19 +- .../internal/SerializersOnBlackboard.java | 25 +- .../util/yoml/internal/YomlConverter.java | 19 +- .../yoml/serializers/InstantiateTypeEnum.java | 8 +- .../InstantiateTypeFromRegistry.java | 16 +- .../yoml/serializers/InstantiateTypeList.java | 16 +- .../yoml/serializers/InstantiateTypeMap.java | 2 +- .../serializers/InstantiateTypePrimitive.java | 2 +- .../InstantiateTypeWorkerAbstract.java | 8 +- .../serializers/ReadingTypeOnBlackboard.java | 23 +- .../YomlSerializerComposition.java | 2 +- .../util/yoml/tests/MockYomlTypeRegistry.java | 62 +++-- .../util/yoml/tests/YomlBasicTests.java | 2 +- 16 files changed, 415 insertions(+), 133 deletions(-) diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java index 19c91ba27e..11eb502502 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java @@ -1,14 +1,40 @@ -package org.apache.brooklyn.camp.yoml; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */package org.apache.brooklyn.camp.yoml; import java.util.Collection; +import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan; import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; +import org.apache.brooklyn.camp.yoml.YomlTypePlanTransformer.YomlTypeImplementationPlan; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.catalog.internal.CatalogUtils; +import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext; +import org.apache.brooklyn.core.typereg.BasicRegisteredType; import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.util.collections.MutableSet; @@ -20,12 +46,23 @@ import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.YomlTypeRegistry; import org.apache.brooklyn.util.yoml.internal.YomlUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; +import com.google.common.reflect.TypeToken; /** * Provides a bridge so YOML can find things in the Brooklyn type registry. */ public class BrooklynYomlTypeRegistry implements YomlTypeRegistry { + private static final Logger log = LoggerFactory.getLogger(BrooklynYomlTypeRegistry.class); + + @SuppressWarnings("serial") + static ConfigKey> CACHED_SERIALIZERS = ConfigKeys.newConfigKey(new TypeToken>() {}, "yoml.type.serializers", + "Serializers found for a registered type"); + private ManagementContext mgmt; /* @@ -112,26 +149,28 @@ public Maybe newInstanceMaybe(String typeName, Yoml yoml, RegisteredType if (nextContext==null) { // fall through to path below; we have a circular reference, so need to load java instead } else { - return Maybe.of(registry().create(typeR, context, null)); + return Maybe.of(registry().create(typeR, nextContext, null)); } } - Class t = null; + Maybe> t = null; { Exception e = null; try { - t = getJavaType(null, typeName, context); + t = getJavaTypeInternal(typeName, context); } catch (Exception ee) { Exceptions.propagateIfFatal(ee); e = ee; } - if (t==null) { - return Maybe.absent("Neither the type registry nor the classpath could load type "+typeName+ + if (t.isAbsent()) { + // generally doesn't come here; it gets filtered by getJavaTypeMaybe + if (e==null) e = ((Maybe.Absent)t).getException(); + return Maybe.absent("Neither the type registry nor the classpath/libraries could load type "+typeName+ (e!=null && !Exceptions.isRootBoringClassNotFound(e, typeName) ? ": "+Exceptions.collapseText(e) : "")); } } try { - return Maybe.of((Object)t.newInstance()); + return Maybe.of((Object)t.get().newInstance()); } catch (Exception e) { return Maybe.absent("Error instantiating type "+typeName+": "+Exceptions.collapseText(e)); } @@ -149,31 +188,44 @@ public YomlClassNotFoundException(String message, Throwable cause) { } } + /* + * There is also some complexity around the java type and the supertypes. + * For context, the latter is needed so callers can filter appropriately. + * The former is needed so that serializers can filter appropriately + * (the java type is not very extensively used and could be changed to use the supertypes set, + * but we have the same problem for both). + *

+ * The issue is that when adding to the catalog the caller likely supplies + * only the yaml, not a statement of supertypes. So we have to try to figure out + * the supertypes. We can do this (1) on-load, when it is added, or (2) lazy, when it is accessed. + * We're going to do (1), and persisting the results, which means we instantiate + * each item once on addition: this serves as a useful validation, but it does disallow + * forward references (ie referenced types must be declared first; for "templates" we could skip this), + * but we avoid re-instantiation on rebind (and the ordering issues that can arise there). + *

+ * Option (2) while it has some attractions it makes the API more entangled between RegisteredType and the transformer, + * and risks odd errors depending when the lazy evaluation occurs vis-a-vis dependent types. + */ @Override - public Class getJavaType(String typeName) { - try { - return getJavaType(registry().get(typeName), typeName, defaultLoadingContext); - } catch (Exception e) { - Exceptions.propagateIfFatal(e); - return null; - } + public Maybe> getJavaTypeMaybe(String typeName) { + return getJavaTypeInternal(typeName, defaultLoadingContext); } - - // TODO use maybe - protected Class getJavaType(RegisteredType typeR, String typeName, RegisteredTypeLoadingContext context) { - Class result = null; - - if (typeR!=null) { - result = RegisteredTypes.peekActualJavaType(typeR); - if (result!=null) return result; - // instantiate - System.out.println("PRE-LOADING to determine type for "+typeR); - Maybe m = newInstanceMaybe(typeName, null, context); - System.out.println("PRE-LOADING DONE, got "+m+" for "+typeR); - if (m.isPresent()) result = m.get().getClass(); + + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected static Maybe> maybeClass(Class clazz) { + // restrict unchecked wildcard generic warning/suppression to here + return (Maybe) Maybe.of(clazz); + } + + protected Maybe> getJavaTypeInternal(String typeName, RegisteredTypeLoadingContext context) { + RegisteredType type = registry().get(typeName, context); + if (type!=null && !context.getAlreadyEncounteredTypes().contains(type.getId())) { + RegisteredTypeLoadingContext newContext = RegisteredTypeLoadingContexts.loaderAlreadyEncountered(context.getLoader(), context.getAlreadyEncounteredTypes(), typeName); + return getJavaTypeInternal(type, newContext); } - if (result==null && typeName==null) return null; + // try to load it wrt context + Class result = null; // strip generics for the purposes here if (typeName.indexOf('<')>0) typeName = typeName.substring(0, typeName.indexOf('<')); @@ -181,29 +233,83 @@ protected Class getJavaType(RegisteredType typeR, String typeName, Registered if (result==null) result = Boxing.boxedType(Boxing.getPrimitiveType(typeName).orNull()); if (result==null && YomlUtils.TYPE_STRING.equals(typeName)) result = String.class; - if (result==null) { // && typeName.startsWith("java:")) { // TODO require java: prefix? - typeName = Strings.removeFromStart(typeName, "java:"); - BrooklynClassLoadingContext loader = null; - if (context!=null && context.getLoader()!=null) - loader = context.getLoader(); - else - loader = JavaBrooklynClassLoadingContext.create(mgmt); + if (result!=null) return maybeClass(result); - Maybe> resultM = loader.tryLoadClass(typeName); - // TODO give better error if absent? - result = resultM.get(); + // currently we accept but don't require the 'java:' prefix + boolean isJava = typeName.startsWith("java:"); + typeName = Strings.removeFromStart(typeName, "java:"); + BrooklynClassLoadingContext loader = null; + if (context!=null && context.getLoader()!=null) + loader = context.getLoader(); + else + loader = JavaBrooklynClassLoadingContext.create(mgmt); + + Maybe> resultM = loader.tryLoadClass(typeName); + if (resultM.isPresent()) return resultM; + if (isJava) return resultM; // if java was explicit, give the java load error + RuntimeException e = ((Maybe.Absent)resultM).getException(); + return Maybe.absent("Neither the type registry nor the classpath/libraries could find type "+typeName+ + (e!=null && !Exceptions.isRootBoringClassNotFound(e, typeName) ? ": "+Exceptions.collapseText(e) : "")); + } + + protected Maybe> getJavaTypeInternal(RegisteredType type, RegisteredTypeLoadingContext context) { + { + Class result = RegisteredTypes.peekActualJavaType(type); + if (result!=null) return maybeClass(result); } - if (typeR!=null && result!=null) { - RegisteredTypes.cacheActualJavaType(typeR, result); + String declaredPrimarySuperTypeName = null; + + if (type.getPlan() instanceof YomlTypeImplementationPlan) { + declaredPrimarySuperTypeName = ((YomlTypeImplementationPlan)type.getPlan()).javaType; + } + if (declaredPrimarySuperTypeName==null) { + log.warn("Primary java super type not declared for "+type+"; it should have been specified/inferred at definition time; will try to infer it now"); + // first look at plan, for a `type` block + if (type.getPlan() instanceof YomlTypeImplementationPlan) { + Maybe> map = RegisteredTypes.getAsYamlMap(type.getPlan().getPlanData()); + if (map.isPresent()) declaredPrimarySuperTypeName = Strings.toString(map.get().get("type")); + } + } + if (declaredPrimarySuperTypeName==null) { + // could look at declared super types, instantiate, and take the lowest common if found + // (but the above is recommended and the preloading below sufficient) + } + + Maybe> result = Maybe.absent("Unable to find java supertype for "+type); + + if (declaredPrimarySuperTypeName!=null) { + // if a supertype name was found, use it + RegisteredTypeLoadingContext newContext = RegisteredTypeLoadingContexts.loaderAlreadyEncountered( + CatalogUtils.newClassLoadingContext(mgmt, type), context.getAlreadyEncounteredTypes(), type.getId()); + result = getJavaTypeInternal(declaredPrimarySuperTypeName, newContext); + } + + if (result.isAbsent()) { + RegisteredTypeLoadingContext newContext = RegisteredTypeLoadingContexts.alreadyEncountered(context.getAlreadyEncounteredTypes()); + // failing that, instantiate it (caching it as type object to prevent recursive lookups?) + log.warn("Preloading required to determine type for "+type); + Maybe m = newInstanceMaybe(type.getId(), null, newContext); + log.info("Preloading completed, got "+m+" for "+type); + if (m.isPresent()) result = maybeClass(m.get().getClass()); + else { + // if declared java type, prefer that error, otherwise give error from above + if (declaredPrimarySuperTypeName==null) { + result = Maybe.absent(new IllegalStateException("Unable to find java supertype declared on "+type+" and unable to instantiate", + ((Maybe.Absent)result).getException())); + } + } + } + + if (result.isPresent()) { + RegisteredTypes.cacheActualJavaType(type, result.get()); } return result; } @Override public String getTypeName(Object obj) { - // TODO reverse lookup?? - return null; + return getTypeNameOfClass(obj.getClass()); } @Override @@ -222,10 +328,69 @@ protected String getDefaultTypeNameOfClass(Class type) { return "java:"+type.getName(); } - @Override - public void collectSerializers(String typeName, Collection serializers, Set typesVisited) { - // TODO wtf? + public Iterable getSerializersForType(String typeName) { + Set result = MutableSet.of(); + collectSerializers(typeName, result, MutableSet.of()); + return result; + } + + protected void collectSerializers(Object type, Collection result, Set typesVisited) { + if (type instanceof String) type = registry().get((String)type); + if (type==null) return; + boolean canUpdateCache = typesVisited.isEmpty(); + if (!typesVisited.add(type)) return; // already seen + Set supers = MutableSet.of(); + if (type instanceof RegisteredType) { + List serializers = ((BasicRegisteredType)type).getCache().get(CACHED_SERIALIZERS); + if (serializers!=null) { + canUpdateCache = false; + supers.addAll(serializers); + } else { + // TODO don't cache if it's a snapshot version + // (also should invalidate any subtypes, but actually any subtypes of snapshots + // should also be snapshots -- that should be enforced!) +// if (isSnapshot( ((RegisteredType)type).getVersion() )) updateCache = false; + + // apply serializers from this RT + TypeImplementationPlan plan = ((RegisteredType)type).getPlan(); + if (plan instanceof YomlTypeImplementationPlan) { + result.addAll( ((YomlTypeImplementationPlan)plan).serializers ); + } + + // loop over supertypes for serializers declared there (unless we introduce an option to suppress/limit) + supers.addAll(((RegisteredType) type).getSuperTypes()); + } + } else if (type instanceof Class) { + // TODO result.addAll( ... ); based on annotations on the java class + // then do the following if the evaluation above was not recursive +// supers.add(((Class) type).getSuperclass()); +// supers.addAll(Arrays.asList(((Class) type).getInterfaces())); + } else { + throw new IllegalStateException("Illegal supertype entry "+type+", visiting "+typesVisited); + } + for (Object s: supers) { + collectSerializers(s, result, typesVisited); + } + if (canUpdateCache) { + if (type instanceof RegisteredType) { + ((BasicRegisteredType)type).getCache().put(CACHED_SERIALIZERS, ImmutableList.copyOf(result)); + } else { + // could use static weak cache map on classes? if so also update above + } + } + } + + public static RegisteredType newYomlRegisteredType(RegisteredTypeKind kind, + String symbolicName, String version, String planData, + Class javaConcreteType, + Iterable superTypesAsClassOrRegisteredType, + Iterable serializers) { + + YomlTypeImplementationPlan plan = new YomlTypeImplementationPlan(planData, javaConcreteType, serializers); + RegisteredType result = kind==RegisteredTypeKind.SPEC ? RegisteredTypes.spec(symbolicName, version, plan) : RegisteredTypes.bean(symbolicName, version, plan); + RegisteredTypes.addSuperTypes(result, superTypesAsClassOrRegisteredType); + return result; } } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java index d2803eeaf9..db98a003fb 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.brooklyn.camp.yoml; import java.util.List; @@ -5,16 +23,17 @@ import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; import org.apache.brooklyn.api.typereg.RegisteredType; -import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan; import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; import org.apache.brooklyn.core.typereg.AbstractFormatSpecificTypeImplementationPlan; import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer; -import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan; import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.yoml.Yoml; +import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.YomlTypeRegistry; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; /** @@ -25,13 +44,15 @@ * * DONE * - test programmatic addition and parse (beans) with manual objects + * - figure out supertypes and use that to determine java type + * - attach custom serializers (on plan) * * NEXT - * - attach custom serializers + * - test serializers + * - support serializers by annotation * - support specs from yoml * - $brooklyn:object(format): - * and $brooklyn:yoml(expected-type): - * - catalog add arbitrary types via yoml, specifying format + * - type registry api, add arbitrary types via yoml, specifying format * - catalog impl in yoml as test? * - REST API for deploy accepts specific format, can call yoml (can we test this earlier?) * @@ -111,13 +132,15 @@ public List createFromTypeDefinition(String formatCode, Object c return null; } - - public static class YomlTypeImplementationPlan extends AbstractFormatSpecificTypeImplementationPlan { - public YomlTypeImplementationPlan(TypeImplementationPlan otherPlan) { - super(FORMATS.get(0), String.class, otherPlan); - } - public YomlTypeImplementationPlan(String planData) { - this(new BasicTypeImplementationPlan(FORMATS.get(0), planData)); + static class YomlTypeImplementationPlan extends AbstractFormatSpecificTypeImplementationPlan { + final String javaType; + final List serializers; + + public YomlTypeImplementationPlan(String planData, Class javaType, Iterable serializers) { + super(FORMATS.get(0), planData); + this.javaType = Preconditions.checkNotNull(javaType).getName(); + this.serializers = MutableList.copyOf(serializers); +// this.superTypes = (superTypes==null || superTypes.isEmpty() ? MutableList.of(this.javaType) : MutableList.copyOf(superTypes)); } } } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java index 0705d8eea9..17741bccf5 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java @@ -18,8 +18,10 @@ */ package org.apache.brooklyn.camp.yoml; +import java.util.Arrays; + +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; import org.apache.brooklyn.api.typereg.RegisteredType; -import org.apache.brooklyn.camp.yoml.YomlTypePlanTransformer.YomlTypeImplementationPlan; import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport; import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; import org.apache.brooklyn.core.typereg.JavaClassNameTypePlanTransformer.JavaClassNameTypeImplementationPlan; @@ -64,8 +66,16 @@ public void testInstantiateYomlPlanExplicitField() { Assert.assertEquals( ((ItemA)x).name, "Bob" ); } - private final static RegisteredType SAMPLE_TYPE_YOML = RegisteredTypes.bean("yoml.A", "1", - new YomlTypeImplementationPlan("{ type: "+ItemA.class.getName() +" }"), ItemA.class); + private static RegisteredType sampleTypeYoml(String typeName, String typeDefName) { + return BrooklynYomlTypeRegistry.newYomlRegisteredType(RegisteredTypeKind.BEAN, + // symbolicName, version, + typeName==null ? "yoml.A" : typeName, "1", + // planData, + "{ type: "+ (typeDefName==null ? ItemA.class.getName() : typeDefName) +" }", + // javaConcreteType, superTypesAsClassOrRegisteredType, serializers) + ItemA.class, Arrays.asList(ItemA.class), null); + } + private final static RegisteredType SAMPLE_TYPE_YOML = sampleTypeYoml(null, null); @Test public void testInstantiateYomlBaseType() { @@ -74,6 +84,20 @@ public void testInstantiateYomlBaseType() { Assert.assertTrue(x instanceof ItemA); } + @Test + public void testInstantiateYomlBaseTypeJavaPrefix() { + add(sampleTypeYoml(null, "'java:"+ItemA.class.getName()+"'")); + Object x = registry().createBeanFromPlan("yoml", "{ type: yoml.A }", null, null); + Assert.assertTrue(x instanceof ItemA); + } + + @Test + public void testInstantiateYomlBaseTypeSameName() { + add(sampleTypeYoml(ItemA.class.getName(), null)); + Object x = registry().createBeanFromPlan("yoml", "{ type: "+ItemA.class.getName()+" }", null, null); + Assert.assertTrue(x instanceof ItemA); + } + @Test public void testInstantiateYomlBaseTypeExplicitField() { add(SAMPLE_TYPE_YOML); @@ -93,4 +117,16 @@ public void testYomlTypeMissingGiveGoodError() { } } + @Test + public void testYomlTypeMissingGiveGoodErrorNested() { + add(sampleTypeYoml("yoml.B", "yoml.A")); + try { + Object x = registry().createBeanFromPlan("yoml", "{ type: yoml.B, fields: { name: Bob } }", null, null); + Asserts.shouldHaveFailedPreviously("Expected type resolution failure; instead it loaded "+x); + } catch (Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, "yoml.B", "yoml.A", "neither", "registry", "classpath"); + Asserts.expectedFailureDoesNotContain(e, JavaClassNames.simpleClassName(ClassNotFoundException.class)); + } + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java index 38e1e11f54..d53f0423e4 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java @@ -18,29 +18,30 @@ */ package org.apache.brooklyn.util.yoml; -import java.util.Collection; -import java.util.Set; - import org.apache.brooklyn.util.guava.Maybe; public interface YomlTypeRegistry { + Object newInstance(String type, Yoml yoml); + /** Absent if unknown type; throws if type is ill-defined or incomplete. */ Maybe newInstanceMaybe(String type, Yoml yoml); - Object newInstance(String type, Yoml yoml); - /** Returns the most-specific Java type implied by the given type in the registry, - * or null if the type is not available in the registry. + * or a maybe wrapping any explanatory error if the type is not available in the registry. *

* This is needed so that the right deserialization strategies can be applied for * things like collections and enums. */ - Class getJavaType(String typeName); - + Maybe> getJavaTypeMaybe(String typeName); + + /** Return the best known type name to describe the given java instance */ String getTypeName(Object obj); + /** Return the type name to describe the given java class */ String getTypeNameOfClass(Class type); - void collectSerializers(String typeName, Collection serializers, Set typesVisited); + /** Return custom serializers that shoud be used when deserializing something of the given type, + * typically also looking at serializers for its supertypes */ + Iterable getSerializersForType(String typeName); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java index af231d7746..bed13dd46d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java @@ -22,6 +22,7 @@ import java.util.Map; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.yoml.YomlSerializer; import com.google.common.base.Preconditions; @@ -47,13 +48,25 @@ public static SerializersOnBlackboard create(Map blackboard) { return peek(blackboard); } - List preSerializers = MutableList.of(); - List instantiatedTypeSerializers = MutableList.of(); - List expectedTypeSerializers = MutableList.of(); - List postSerializers = MutableList.of(); + private List preSerializers = MutableList.of(); + private List instantiatedTypeSerializers = MutableList.of(); + private List expectedTypeSerializers = MutableList.of(); + private List postSerializers = MutableList.of(); - public void addInstantiatedTypeSerializers(Iterable instantiatedTypeSerializers) { - Iterables.addAll(this.instantiatedTypeSerializers, instantiatedTypeSerializers); + public void addInstantiatedTypeSerializers(Iterable newInstantiatedTypeSerializers) { + addNewSerializers(instantiatedTypeSerializers, newInstantiatedTypeSerializers); + } + public void addExpectedTypeSerializers(Iterable newExpectedTypeSerializers) { + addNewSerializers(expectedTypeSerializers, newExpectedTypeSerializers); + + } + public void addPostSerializers(List newPostSerializers) { + addNewSerializers(postSerializers, newPostSerializers); + } + protected static void addNewSerializers(List addTo, Iterable elementsToAddIfNotPresent) { + MutableSet newOnes = MutableSet.copyOf(elementsToAddIfNotPresent); + newOnes.removeAll(addTo); + addTo.addAll(newOnes); } public Iterable getSerializers() { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java index 068b1041b8..5c097a627d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java @@ -21,7 +21,6 @@ import java.util.Map; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.YomlContextForRead; import org.apache.brooklyn.util.yoml.YomlContextForWrite; @@ -67,36 +66,34 @@ protected void loopOverSerializers(YomlContext context) { // find the serializers known so far; store on blackboard so they could be edited SerializersOnBlackboard serializers = SerializersOnBlackboard.create(blackboard); if (context.getExpectedType()!=null) { - config.typeRegistry.collectSerializers(context.getExpectedType(), serializers.expectedTypeSerializers, MutableSet.of()); + serializers.addExpectedTypeSerializers(config.typeRegistry.getSerializersForType(context.getExpectedType())); } - serializers.postSerializers.addAll(config.serializersPost); + serializers.addPostSerializers(config.serializersPost); if (context instanceof YomlContextForRead) { // read needs instantiated so that these errors display before manipulating errors and others ReadingTypeOnBlackboard.get(blackboard); } -System.out.println("YOML now looking at "+context.getJsonPath()+"/ = "+context.getJavaObject()+" <-> "+context.getYamlObject()+" ("+context.getExpectedType()+")"); + if (log.isTraceEnabled()) log.trace("YOML now looking at "+context.getJsonPath()+"/ = "+context.getJavaObject()+" <-> "+context.getYamlObject()+" ("+context.getExpectedType()+")"); while (context.phaseAdvance()) { while (context.phaseStepAdvance() "+context.getYamlObject()); + if (log.isTraceEnabled()) log.trace("YOML done looking at "+context.getJsonPath()+"/ = "+context.getJavaObject()+" <-> "+context.getYamlObject()); checkCompletion(context, blackboard); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java index e19e41bb4e..6e15a81a88 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java @@ -42,7 +42,7 @@ public void read() { value = Maybe.of(getYamlObject()); if (!isJsonPrimitiveObject(value.get())) { // warn, but try { type: .., value: ... } syntax - warn("Enum "+getExpectedTypeJava()+" is not a string"); + warn("Enum of expected type "+getExpectedTypeJava()+" cannot be created from '"+value.get()+"'"); } else { type = getExpectedTypeJava(); @@ -52,7 +52,8 @@ public void read() { if (type==null) { String typeName = readingTypeFromFieldOrExpected(); if (typeName==null) return; - type = config.getTypeRegistry().getJavaType(typeName); + type = config.getTypeRegistry().getJavaTypeMaybe(typeName).orNull(); + // swallow exception in this context, it isn't meant for enum to resolve if (type==null || !type.isEnum()) return; value = readingValueFromTypeValueMap(); if (value.isAbsent()) { @@ -60,7 +61,7 @@ public void read() { return; } if (!isJsonPrimitiveObject(value.get())) { - warn("Enum "+getExpectedTypeJava()+" is not a string"); + warn("Enum of type "+getExpectedTypeJava()+" cannot be created from '"+value.get()+"'"); return; } @@ -69,7 +70,6 @@ public void read() { Maybe enumValue = tryCoerceAndNoteError(value.get(), type); if (enumValue.isAbsent()) return; -// Maybe v = Enums.valueOfIgnoreCase((Class)type, Strings.toString(getYamlObject())); storeReadObjectAndAdvance(enumValue.get(), false); if (fromMap) removeTypeAndValueKeys(); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java index 3f2b4fcd06..e7b9ba7695 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java @@ -19,7 +19,6 @@ package org.apache.brooklyn.util.yoml.serializers; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; @@ -52,11 +51,16 @@ public void read() { Maybe resultM = config.getTypeRegistry().newInstanceMaybe((String)type, Yoml.newInstance(config)); if (resultM.isAbsent()) { - Class jt = config.getTypeRegistry().getJavaType((String)type); - String message = jt==null ? "Invalid type '"+type+"'" : "Unable to create type '"+type+"' ("+jt+")"; - RuntimeException exc = ((Maybe.Absent)resultM).getException(); - if (exc!=null) message+=": "+Exceptions.collapseText(exc); - warn(message); + String message = "Unable to create type '"+type+"'"; + RuntimeException exc = null; + + Maybe> jt = config.getTypeRegistry().getJavaTypeMaybe((String)type); + if (jt.isAbsent()) { + exc = ((Maybe.Absent)jt).getException(); + } else { + exc = ((Maybe.Absent)resultM).getException(); + } + warn(new IllegalStateException(message, exc)); return; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java index b285cb8166..ce94ae38e0 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java @@ -31,6 +31,7 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.yoml.Yoml; import org.apache.brooklyn.util.yoml.YomlContext; @@ -132,12 +133,19 @@ public void read() { // get any new generic type set - slightly messy if (!parseExpectedTypeAndDetermineIfNoBadProblems(type)) return; - Class javaType = config.getTypeRegistry().getJavaType(type); - if (javaType==null) { javaType = expectedJavaType; } + Maybe> javaTypeM = config.getTypeRegistry().getJavaTypeMaybe(type); + Class javaType; + if (javaTypeM.isPresent()) javaType = javaTypeM.get(); + else { + // the expected java type is now based on inference from `type` + // so if it wasn't recognised we'll bail out below + javaType = expectedJavaType; + } + expectedJavaType = oldExpectedType; if (javaType==null || value==null || !Collection.class.isAssignableFrom(javaType) || !Iterable.class.isInstance(value)) { - // only apply if it's a list type and a list value + // only apply below if it's a list type and a list value return; } // looks like a list in a type-value map @@ -207,7 +215,7 @@ private Object newInstance(Class javaType, String explicitTypeName) { if (explicitTypeName!=null) { GenericsParse gp = new GenericsParse(explicitTypeName); if (gp.warning!=null) { - warn(gp.warning); + warn(gp.warning+" (creating "+javaType+")"); return null; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java index 46e0157c1c..8babcb8b90 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java @@ -121,7 +121,7 @@ public void read() { actualBaseTypeName = expectedBaseTypeName; expectedJavaType = oldExpectedJavaType; expectedBaseTypeName = oldExpectedBaseTypeName; - if (actualType==null) actualType = config.getTypeRegistry().getJavaType(actualBaseTypeName); + if (actualType==null) actualType = config.getTypeRegistry().getJavaTypeMaybe(actualBaseTypeName).orNull(); if (actualType==null) return; //we don't recognise the type if (!Map.class.isAssignableFrom(actualType)) return; //it's not a map diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java index d1772217cc..3781e5e74e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java @@ -59,7 +59,7 @@ public void read() { String typeName = readingTypeFromFieldOrExpected(); if (typeName==null) return; - expectedJavaType = config.getTypeRegistry().getJavaType(typeName); + expectedJavaType = config.getTypeRegistry().getJavaTypeMaybe(typeName).orNull(); if (expectedJavaType==null) expectedJavaType = getSpecialKnownTypeName(typeName); if (!isJsonPrimitiveType(expectedJavaType) && !isJsonMarkerType(typeName)) return; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java index d191168564..99b50a0c4a 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java @@ -28,7 +28,6 @@ import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Boxing; import org.apache.brooklyn.util.yoml.YomlContext; -import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; import org.apache.brooklyn.util.yoml.internal.YomlUtils; import org.apache.brooklyn.util.yoml.serializers.YomlSerializerComposition.YomlSerializerWorker; @@ -75,9 +74,7 @@ protected boolean canDoWrite() { protected void addSerializers(String type) { if (!type.equals(context.getExpectedType())) { - Set serializers = MutableSet.of(); - config.typeRegistry.collectSerializers(type, serializers, MutableSet.of()); - SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(serializers); + SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.typeRegistry.getSerializersForType(type)); } } @@ -149,4 +146,7 @@ protected MutableMap writingMapWithTypeAndLiteralValue(String ty protected void warn(String message) { ReadingTypeOnBlackboard.get(blackboard).addNote(message); } + protected void warn(Throwable message) { + ReadingTypeOnBlackboard.get(blackboard).addNote(message); + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java index 8b8b50c19e..11fa5dadef 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java @@ -18,10 +18,13 @@ */ package org.apache.brooklyn.util.yoml.serializers; +import java.util.List; import java.util.Map; import java.util.Set; +import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.YomlContextForRead; @@ -31,7 +34,7 @@ public class ReadingTypeOnBlackboard implements YomlRequirement { - Set errorNotes = MutableSet.of(); + Set errorNotes = MutableSet.of(); public static final String KEY = ReadingTypeOnBlackboard.class.getCanonicalName(); @@ -49,11 +52,27 @@ public void checkCompletion(YomlContext context) { if (context instanceof YomlContextForRead && context.getJavaObject()!=null) return; if (context instanceof YomlContextForWrite && context.getYamlObject()!=null) return; if (errorNotes.isEmpty()) throw new YomlException("No means to identify type to instantiate", context); - throw new YomlException(Strings.join(errorNotes, "; "), context); + List messages = MutableList.of(); + List throwables = MutableList.of(); + for (Object errorNote: errorNotes) { + if (errorNote instanceof Throwable) { + messages.add(Exceptions.collapseText((Throwable)errorNote)); + throwables.add((Throwable)errorNote); + } else { + messages.add(Strings.toString(errorNote)); + } + } + throw new YomlException(Strings.join(messages, "; "), context, + throwables.isEmpty() ? null : + throwables.size()==1 ? throwables.iterator().next() : + Exceptions.create(throwables)); } public void addNote(String message) { errorNotes.add(message); } + public void addNote(Throwable message) { + errorNotes.add(message); + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java index c61e579d75..2a3a575142 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java @@ -67,7 +67,7 @@ private void initWrite(YomlContextForWrite context, YomlConverter converter, Map public Class getExpectedTypeJava() { String et = context.getExpectedType(); if (Strings.isBlank(et)) return null; - Class ett = config.getTypeRegistry().getJavaType(et); + Class ett = config.getTypeRegistry().getJavaTypeMaybe(et).orNull(); if (Object.class.equals(ett)) return null; return ett; } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java index c59550d73c..7931c528fc 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java @@ -74,38 +74,47 @@ public Maybe newInstanceMaybe(String typeName, Yoml yoml) { if (type.parentType==null && type.javaType!=null) parentTypeName = getDefaultTypeNameOfClass(type.javaType); return Maybe.of(yoml.readFromYamlObject(type.yamlDefinition, parentTypeName)); } - Class javaType = getJavaType(type, typeName); - if (javaType==null) { + Maybe> javaType = getJavaTypeInternal(type, typeName); + if (javaType.isAbsent()) { if (type==null) return Maybe.absent("Unknown type `"+typeName+"`"); - throw new IllegalStateException("Incomplete hierarchy for `"+typeName+"`"); + return Maybe.absent(new IllegalStateException("Incomplete hierarchy for "+type, ((Maybe.Absent)javaType).getException())); } - return Reflections.invokeConstructorFromArgsIncludingPrivate(javaType); + return Reflections.invokeConstructorFromArgsIncludingPrivate(javaType.get()); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected static Maybe> maybeClass(Class clazz) { + // restrict unchecked wildcard generic warning/suppression to here + return (Maybe) Maybe.ofDisallowingNull(clazz); } @Override - public Class getJavaType(String typeName) { - if (typeName==null) return null; + public Maybe> getJavaTypeMaybe(String typeName) { + if (typeName==null) return Maybe.absent(); // strip generics here if (typeName.indexOf('<')>0) typeName = typeName.substring(0, typeName.indexOf('<')); - return getJavaType(types.get(typeName), typeName); + return getJavaTypeInternal(types.get(typeName), typeName); } - protected Class getJavaType(MockRegisteredType registeredType, String typeName) { - Class result = null; + protected Maybe> getJavaTypeInternal(MockRegisteredType registeredType, String typeName) { + Maybe> result = Maybe.absent(); - if (result==null && registeredType!=null) result = registeredType.javaType; - if (result==null && registeredType!=null) result = getJavaType(registeredType.parentType); + if (result.isAbsent() && registeredType!=null) result = maybeClass(registeredType.javaType); + if (result.isAbsent() && registeredType!=null) result = getJavaTypeMaybe(registeredType.parentType); - if (result==null && typeName==null) return null; + if (result.isAbsent()) { + result = Maybe.absent("Unknown type '"+typeName+"' (no match available in mock library)"); + if (typeName==null) return result; + } - if (result==null) result = Boxing.boxedType(Boxing.getPrimitiveType(typeName).orNull()); - if (result==null && YomlUtils.TYPE_STRING.equals(typeName)) result = String.class; + if (result.isAbsent()) result = maybeClass(Boxing.boxedType(Boxing.getPrimitiveType(typeName).orNull())).or(result); + if (result.isAbsent() && YomlUtils.TYPE_STRING.equals(typeName)) result = maybeClass(String.class); - if (result==null && typeName.startsWith("java:")) { + if (result.isAbsent() && typeName.startsWith("java:")) { typeName = Strings.removeFromStart(typeName, "java:"); try { - // TODO use injected loader? - result = Class.forName(typeName); + // IRL use injected loader + result = maybeClass(Class.forName(typeName)); } catch (ClassNotFoundException e) { // ignore, this isn't a java type } @@ -133,13 +142,14 @@ public void put(String typeName, String yamlDefinition, List)yamlObject).remove("type"); if (!(type instanceof String)) throw new IllegalArgumentException("Mock requires key `type` with string value"); - Class javaType = getJavaType((String)type); - if (javaType==null) throw new IllegalArgumentException("Mock cannot resolve parent type `"+type+"` in definition of `"+typeName+"`"); + Maybe> javaType = getJavaTypeMaybe((String)type); + if (javaType.isAbsent()) throw new IllegalArgumentException("Mock cannot resolve parent type `"+type+"` in definition of `"+typeName+"`: "+ + ((Maybe.Absent)javaType).getException()); Object interfaceTypes = ((Map)yamlObject).remove("interfaceTypes"); if (((Map)yamlObject).isEmpty()) yamlObject = null; - types.put(typeName, new MockRegisteredType(typeName, (String)type, javaType, (Collection)interfaceTypes, serializers, yamlObject)); + types.put(typeName, new MockRegisteredType(typeName, (String)type, javaType.get(), (Collection)interfaceTypes, serializers, yamlObject)); } @Override @@ -165,11 +175,17 @@ protected String getDefaultTypeNameOfClass(Class type) { } @Override - public void collectSerializers(String typeName, Collection serializers, Set typesVisited) { + public Iterable getSerializersForType(String typeName) { + Set result = MutableSet.of(); + collectSerializers(typeName, result, MutableSet.of()); + return result; + } + + protected void collectSerializers(String typeName, Collection serializers, Set typesVisited) { if (typeName==null || !typesVisited.add(typeName)) return; MockRegisteredType rt = types.get(typeName); - if (rt==null || rt.serializers==null) return; - serializers.addAll(rt.serializers); + if (rt==null) return; + if (rt.serializers!=null) serializers.addAll(rt.serializers); if (rt.parentType!=null) { collectSerializers(rt.parentType, serializers, typesVisited); } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlBasicTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlBasicTests.java index 14ae35dec3..1aaae2d896 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlBasicTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlBasicTests.java @@ -229,7 +229,7 @@ public void testFailOnUnknownType() { Asserts.shouldHaveFailedPreviously("Got "+ytc.lastReadResult+" when we should have failed due to unknown type shape"); } catch (Exception e) { try { - Asserts.expectedFailureContainsIgnoreCase(e, "shape", "unknown type"); + Asserts.expectedFailureContainsIgnoreCase(e, "no ", "type", "shape", "available"); } catch (Throwable e2) { log.warn("Failure detail: "+e, e); throw Exceptions.propagate(e2); From 8e80c6a58c870b3bfe0234ec90d8093c379121a1 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 7 Sep 2016 18:04:19 +0100 Subject: [PATCH 27/77] support annotations for serializers --- .../camp/yoml/BrooklynYomlTypeRegistry.java | 2 +- .../brooklyn/util/yoml/annotations/Alias.java | 36 ++++++ .../annotations/YomlAllFieldsAtTopLevel.java | 31 +++++ .../yoml/annotations/YomlFieldAtTopLevel.java | 35 ++++++ .../yoml/serializers/AllFieldsExplicit.java | 2 + .../yoml/serializers/ConvertSingletonMap.java | 4 + .../util/yoml/serializers/ExplicitField.java | 27 +++++ .../org/apache/brooklyn/util/yoml/sketch.md | 3 +- .../util/yoml/tests/MockYomlTypeRegistry.java | 6 +- .../util/yoml/tests/YomlAnnotationTests.java | 111 ++++++++++++++++++ .../util/yoml/tests/YomlTestFixture.java | 52 ++++++++ 11 files changed, 303 insertions(+), 6 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/Alias.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAllFieldsAtTopLevel.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFieldAtTopLevel.java create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlAnnotationTests.java diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java index 11eb502502..fd56044e26 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java @@ -345,7 +345,7 @@ protected void collectSerializers(Object type, Collection result List serializers = ((BasicRegisteredType)type).getCache().get(CACHED_SERIALIZERS); if (serializers!=null) { canUpdateCache = false; - supers.addAll(serializers); + result.addAll(serializers); } else { // TODO don't cache if it's a snapshot version // (also should invalidate any subtypes, but actually any subtypes of snapshots diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/Alias.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/Alias.java new file mode 100644 index 0000000000..474a419f56 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/Alias.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.annotations; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target({ TYPE, FIELD }) +/** Indicates that a class or field should be known by a set of given aliases */ +public @interface Alias { + + String[] value() default {}; + String preferred() default ""; + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAllFieldsAtTopLevel.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAllFieldsAtTopLevel.java new file mode 100644 index 0000000000..e1fc7f59a8 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAllFieldsAtTopLevel.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.annotations; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target({ TYPE }) +/** Indicates that all fields should be settable at the top-level when reading yoml */ +public @interface YomlAllFieldsAtTopLevel { +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFieldAtTopLevel.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFieldAtTopLevel.java new file mode 100644 index 0000000000..940a882a74 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFieldAtTopLevel.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.annotations; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target({ FIELD }) +/** Indicates that this field should be settable at the top-level when reading yoml */ +public @interface YomlFieldAtTopLevel { + + // could allow other configuration for the fields in ExplicitField + // (that constructor looks at this annotation) + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java index fd44f33400..00baa6ca84 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java @@ -21,10 +21,12 @@ import java.util.List; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; import org.apache.brooklyn.util.yoml.internal.YomlUtils; /* Adds ExplicitField instances for all fields declared on the type */ +@Alias("all-fields-explicit") public class AllFieldsExplicit extends YomlSerializerComposition { protected YomlSerializerWorker newWorker() { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java index 24e311d8f8..421f9eccc9 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java @@ -22,6 +22,8 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; import org.apache.brooklyn.util.yoml.internal.YomlUtils; @@ -31,6 +33,8 @@ * || merge-with-map-value * defaults: { type: explicit-field } */ +@YomlAllFieldsAtTopLevel +@Alias("convert-singleton-map") public class ConvertSingletonMap extends YomlSerializerComposition { public ConvertSingletonMap() { } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java index 5534befcda..ab2d4d95aa 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java @@ -18,16 +18,22 @@ */ package org.apache.brooklyn.util.yoml.serializers; +import java.lang.reflect.Field; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; +import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.YomlContext.StandardPhases; +import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; import com.google.common.base.Objects; @@ -37,8 +43,29 @@ *

* On write, after FieldsInMapUnderFields sets the `fields` map, * look for the field name, and rewrite under the preferred alias at the root. */ +@YomlAllFieldsAtTopLevel +@Alias("explicit-field") public class ExplicitField extends YomlSerializerComposition { + public ExplicitField() {} + public ExplicitField(Field f) { + fieldName = keyName = f.getName(); + + Alias alias = f.getAnnotation(Alias.class); + if (alias!=null) { + aliases = MutableList.of(); + if (Strings.isNonBlank(alias.preferred())) { + keyName = alias.preferred(); + aliases.add(alias.preferred()); + } + aliases.add(f.getName()); + aliases.addAll(Arrays.asList(alias.value())); + } + + // if there are other things on ytf +// YomlFieldAtTopLevel ytf = f.getAnnotation(YomlFieldAtTopLevel.class); + } + protected YomlSerializerWorker newWorker() { return new Worker(); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md index f096b56732..9fd30a51c0 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md @@ -534,6 +534,7 @@ to be shown. ### TODO +* annotations (basic is done, but various "if" situations) * complex syntax, type as key, etc * config/data keys @@ -626,5 +627,3 @@ convert-map-to-list (default-key, default-value) * if V is a non-empty map, then the corresponding list entry is the map V with `{ : K }` added * otherwise, the corresponding list entry is `{ : K, : V }` - -OLD \ No newline at end of file diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java index 7931c528fc..7742015b6d 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java @@ -46,10 +46,10 @@ static class MockRegisteredType { final Set interfaceTypes; final Class javaType; - final List serializers; + final Collection serializers; final Object yamlDefinition; - public MockRegisteredType(String id, String parentType, Class javaType, Collection interfaceTypes, List serializers, Object yamlDefinition) { + public MockRegisteredType(String id, String parentType, Class javaType, Collection interfaceTypes, Collection serializers, Object yamlDefinition) { super(); this.id = id; this.parentType = parentType; @@ -126,7 +126,7 @@ protected Maybe> getJavaTypeInternal(MockRegisteredType registeredType, public void put(String typeName, Class javaType) { put(typeName, javaType, null); } - public void put(String typeName, Class javaType, List serializers) { + public void put(String typeName, Class javaType, Collection serializers) { types.put(typeName, new MockRegisteredType(typeName, "java:"+javaType.getName(), javaType, MutableSet.of(), serializers, null)); } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlAnnotationTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlAnnotationTests.java new file mode 100644 index 0000000000..4534d7cb05 --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlAnnotationTests.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.tests; + +import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlFieldAtTopLevel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import com.google.common.base.Objects; + +/** Tests that the default serializers can read/write types and fields. + *

+ * And shows how to use them at a low level. + */ +public class YomlAnnotationTests { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(YomlAnnotationTests.class); + + static class ExplicitFieldsAtTopLevelExamples { + @Alias("shape") + static class Shape { + @YomlFieldAtTopLevel + String name; + @YomlFieldAtTopLevel + @Alias(value={"kolor","couleur"}, preferred="colour") + String color; + + @Override + public boolean equals(Object xo) { + if (xo==null || !Objects.equal(getClass(), xo.getClass())) return false; + Shape x = (Shape) xo; + return Objects.equal(name, x.name) && Objects.equal(color, x.color); + } + @Override + public String toString() { + return Objects.toStringHelper(this).add("name", name).add("color", color).omitNullValues().toString(); + } + @Override + public int hashCode() { return Objects.hashCode(name, color); } + + public Shape name(String name) { this.name = name; return this; } + public Shape color(String color) { this.color = color; return this; } + } + } + + @Test + public void testYomlFieldsAtTopLevel() { + ExplicitFieldsAtTopLevelExamples.Shape shape = new ExplicitFieldsAtTopLevelExamples.Shape().name("nifty_shape").color("blue"); + YomlTestFixture.newInstance(). + addTypeWithAnnotations(ExplicitFieldsAtTopLevelExamples.Shape.class). + read("{ name: nifty_shape, couleur: blue }", "shape").assertResult(shape). + write(shape).assertResult("{ type: "+ExplicitFieldsAtTopLevelExamples.Shape.class.getName()+", name: nifty_shape, colour: blue }"); + } + + static class ExplicitFieldsAllExamples { + @YomlAllFieldsAtTopLevel + @Alias("shape") + static class Shape { + String name; + @YomlFieldAtTopLevel + @Alias(value={"kolor","couleur"}, preferred="colour") + String color; + + @Override + public boolean equals(Object xo) { + if (xo==null || !Objects.equal(getClass(), xo.getClass())) return false; + Shape x = (Shape) xo; + return Objects.equal(name, x.name) && Objects.equal(color, x.color); + } + @Override + public String toString() { + return Objects.toStringHelper(this).add("name", name).add("color", color).omitNullValues().toString(); + } + @Override + public int hashCode() { return Objects.hashCode(name, color); } + + public Shape name(String name) { this.name = name; return this; } + public Shape color(String color) { this.color = color; return this; } + } + } + + @Test + public void testYomlAllFields() { + ExplicitFieldsAllExamples.Shape shape = new ExplicitFieldsAllExamples.Shape().name("nifty_shape").color("blue"); + YomlTestFixture.newInstance(). + addTypeWithAnnotations(ExplicitFieldsAllExamples.Shape.class). + read("{ name: nifty_shape, couleur: blue }", "shape").assertResult(shape). + write(shape).assertResult("{ type: "+ExplicitFieldsAllExamples.Shape.class.getName()+", name: nifty_shape, colour: blue }"); + } + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java index d6441a5b5b..f2a86aebf9 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java @@ -18,14 +18,22 @@ */ package org.apache.brooklyn.util.yoml.tests; +import java.lang.reflect.Field; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.brooklyn.util.collections.Jsonya; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.Yoml; import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlFieldAtTopLevel; +import org.apache.brooklyn.util.yoml.serializers.ExplicitField; import org.testng.Assert; public class YomlTestFixture { @@ -106,4 +114,48 @@ static void assertEqualsIgnoringQuotes(Object s1, Object s2, String message) { public YomlTestFixture addType(String name, Class type, List serializers) { tr.put(name, type, serializers); return this; } public YomlTestFixture addType(String name, String yamlDefinition) { tr.put(name, yamlDefinition); return this; } public YomlTestFixture addType(String name, String yamlDefinition, List serializers) { tr.put(name, yamlDefinition, serializers); return this; } + + public YomlTestFixture addTypeWithAnnotations(Class type) { + return addTypeWithAnnotations(null, type); + } + public YomlTestFixture addTypeWithAnnotations(String name, Class type) { + MutableSet names = MutableSet.of(); + + Alias overallAlias = type.getAnnotation(Alias.class); + if (name!=null) { + names.addIfNotNull(name); + } + if (overallAlias!=null) { + if (Strings.isNonBlank(overallAlias.preferred())) { + names.add( overallAlias.preferred() ); + } + names.addAll( Arrays.asList(overallAlias.value()) ); + } + if (name==null) { + names.add(type.getName()); + } + + Set serializers = findSerializerAnnotations(type); + for (String n: names) { + tr.put(n, type, serializers); + } + return this; + } + + public static Set findSerializerAnnotations(Class type) { + Set result = MutableSet.of(); + if (!type.getSuperclass().equals(Object.class)) { + result.addAll(findSerializerAnnotations(type.getSuperclass())); + } + + YomlAllFieldsAtTopLevel allFields = type.getAnnotation(YomlAllFieldsAtTopLevel.class); + for (Field f: type.getDeclaredFields()) { + YomlFieldAtTopLevel ytf = f.getAnnotation(YomlFieldAtTopLevel.class); + if (ytf!=null || allFields!=null) + result.add(new ExplicitField(f)); + } + + return result; + } + } From 928089ee5d51805c608260c30f395a2a646e1b36 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 7 Sep 2016 22:48:05 +0100 Subject: [PATCH 28/77] support yoml annotations in transformer --- .../camp/yoml/BrooklynYomlTypeRegistry.java | 19 +++++ .../camp/yoml/YomlTypePlanTransformer.java | 69 ++++++++++++------- .../camp/yoml/YomlTypeRegistryTest.java | 19 +++++ .../annotations/YomlAllFieldsAtTopLevel.java | 3 +- .../yoml/annotations/YomlAnnotations.java | 68 ++++++++++++++++++ .../util/yoml/tests/YomlTestFixture.java | 44 +----------- 6 files changed, 155 insertions(+), 67 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java index fd56044e26..5e72bcb737 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java @@ -17,11 +17,14 @@ * under the License. */package org.apache.brooklyn.camp.yoml; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import javax.annotation.Nullable; + import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry; @@ -45,6 +48,7 @@ import org.apache.brooklyn.util.yoml.Yoml; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.YomlTypeRegistry; +import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations; import org.apache.brooklyn.util.yoml.internal.YomlUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -389,8 +393,23 @@ public static RegisteredType newYomlRegisteredType(RegisteredTypeKind kind, YomlTypeImplementationPlan plan = new YomlTypeImplementationPlan(planData, javaConcreteType, serializers); RegisteredType result = kind==RegisteredTypeKind.SPEC ? RegisteredTypes.spec(symbolicName, version, plan) : RegisteredTypes.bean(symbolicName, version, plan); + RegisteredTypes.addSuperType(result, javaConcreteType); RegisteredTypes.addSuperTypes(result, superTypesAsClassOrRegisteredType); return result; } + public static RegisteredType newYomlRegisteredType(RegisteredTypeKind bean, @Nullable String symbolicName, String version, Class clazz) { + Set names = YomlAnnotations.findTypeNamesFromAnnotations(clazz, symbolicName, false); + Set serializers = YomlAnnotations.findSerializerAnnotations(clazz); + RegisteredType type = BrooklynYomlTypeRegistry.newYomlRegisteredType(RegisteredTypeKind.BEAN, + // symbolicName, version, + names.iterator().next(), version, + // planData - null means just use the java type (could have done this earlier), + null, + // javaConcreteType, superTypesAsClassOrRegisteredType, serializers) + clazz, Arrays.asList(clazz), serializers); + type = RegisteredTypes.addAliases(type, names); + return type; + } + } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java index db98a003fb..488ea5b34a 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java @@ -28,10 +28,12 @@ import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer; import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.Yoml; +import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.YomlSerializer; -import org.apache.brooklyn.util.yoml.YomlTypeRegistry; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -46,22 +48,29 @@ * - test programmatic addition and parse (beans) with manual objects * - figure out supertypes and use that to determine java type * - attach custom serializers (on plan) - * - * NEXT * - test serializers * - support serializers by annotation - * - support specs from yoml + * - set serializers when adding to catalog and test + * + * NEXT * - $brooklyn:object(format): + * - catalog impl supports format + * + * (initdish can now be made to work) + * + * THEN + * - support specs from yoml * - type registry api, add arbitrary types via yoml, specifying format * - catalog impl in yoml as test? * - REST API for deploy accepts specific format, can call yoml (can we test this earlier?) * - * THEN + * AND THEN * - generate its own documentation - * - persist to yoml + * - persistence switches to using yoml, warns if any types are not yoml-ized * - yoml allows `constructor: [list]` and `constructor: { mode: static, type: factory, method: newInstance, args: ... }` * and maybe even `constructor: { mode: chain, steps: [ { mode: constructor, type: Foo.Builder }, { mode: method, method: bar, args: [ true ] }, { mode: method, method: build } ] }` - * - type access control and java instantiation access control ? + * - type access control -- ie restrict who can see what types + * - java instantiation access control - ie permission required to access java in custom types (deployed or added to catalog) */ public class YomlTypePlanTransformer extends AbstractTypePlanTransformer { @@ -75,17 +84,15 @@ public YomlTypePlanTransformer() { @Override protected double scoreForNullFormat(Object planData, RegisteredType type, RegisteredTypeLoadingContext context) { - Maybe> plan = RegisteredTypes.getAsYamlMap(planData); - if (plan.isAbsent()) return 0; int score = 0; - if (plan.get().containsKey("type")) score += 5; - if (plan.get().containsKey("services")) score += 2; - // TODO these should become legacy - if (plan.get().containsKey("brooklyn.locations")) score += 1; - if (plan.get().containsKey("brooklyn.policies")) score += 1; - - if (score==0) return 0.1; - return (1.0 - 1.0/score); + Maybe> plan = RegisteredTypes.getAsYamlMap(planData); + if (plan.isPresent()) { + score += 1; + if (plan.get().containsKey("type")) score += 5; + if (plan.get().containsKey("services")) score += 2; + } + if (type instanceof YomlTypeImplementationPlan) score += 100; + return (1.0 - 8.0/(score+8)); } @Override @@ -102,7 +109,7 @@ protected double scoreForNonmatchingNonnullFormat(String planFormat, Object plan @Override protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception { - YomlTypeRegistry tr = new BrooklynYomlTypeRegistry(mgmt, context); + BrooklynYomlTypeRegistry tr = new BrooklynYomlTypeRegistry(mgmt, context); Yoml y = Yoml.newInstance(tr); // TODO could cache the parse, could cache the instantiation instructions Object data = type.getPlan().getPlanData(); @@ -110,14 +117,27 @@ protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext co Class expectedSuperType = context.getExpectedJavaSuperType(); String expectedSuperTypeName = tr.getTypeNameOfClass(expectedSuperType); - if (data instanceof String) { + if (data==null || (data instanceof String)) { + if (Strings.isBlank((String)data)) { + // blank plan means to use the java type + Maybe> jt = tr.getJavaTypeInternal(type, context); + if (jt.isAbsent()) throw new YomlException("Type '"+type+"' has no plan or java type in its definition"); + try { + return jt.get().newInstance(); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + throw new YomlException("Type '"+type+"' has no plan and its java type cannot be instantiated", e); + } + } + + // normal processing return y.read((String) data, expectedSuperTypeName); - } else { - // could do this -// return y.readFromYamlObject(data, expectedSuperTypeName); - // but it should always be a string... - throw new IllegalArgumentException("The implementation plan for '"+type+"' should be a string in order to process as YOML"); } + + // could do this +// return y.readFromYamlObject(data, expectedSuperTypeName); + // but we require always a string + throw new IllegalArgumentException("The implementation plan for '"+type+"' should be a string in order to process as YOML"); } @Override @@ -140,7 +160,6 @@ public YomlTypeImplementationPlan(String planData, Class javaType, Iterablefields block. */ public @interface YomlAllFieldsAtTopLevel { } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java new file mode 100644 index 0000000000..6e35f6ab13 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.annotations; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Set; + +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.serializers.ExplicitField; + +public class YomlAnnotations { + + public static Set findTypeNamesFromAnnotations(Class type, String optionalDefaultPreferredName, boolean includeJavaTypeNameEvenIfOthers) { + MutableSet names = MutableSet.of(); + + Alias overallAlias = type.getAnnotation(Alias.class); + if (optionalDefaultPreferredName!=null) { + names.addIfNotNull(optionalDefaultPreferredName); + } + if (overallAlias!=null) { + if (Strings.isNonBlank(overallAlias.preferred())) { + names.add( overallAlias.preferred() ); + } + names.addAll( Arrays.asList(overallAlias.value()) ); + } + if (includeJavaTypeNameEvenIfOthers || names.isEmpty()) { + names.add(type.getName()); + } + + return names; + } + + public static Set findSerializerAnnotations(Class type) { + Set result = MutableSet.of(); + if (!type.getSuperclass().equals(Object.class)) { + result.addAll(findSerializerAnnotations(type.getSuperclass())); + } + + YomlAllFieldsAtTopLevel allFields = type.getAnnotation(YomlAllFieldsAtTopLevel.class); + for (Field f: type.getDeclaredFields()) { + YomlFieldAtTopLevel ytf = f.getAnnotation(YomlFieldAtTopLevel.class); + if (ytf!=null || allFields!=null) + result.add(new ExplicitField(f)); + } + + return result; + } + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java index f2a86aebf9..b0a89fae18 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java @@ -18,22 +18,16 @@ */ package org.apache.brooklyn.util.yoml.tests; -import java.lang.reflect.Field; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.brooklyn.util.collections.Jsonya; -import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.Yoml; import org.apache.brooklyn.util.yoml.YomlSerializer; -import org.apache.brooklyn.util.yoml.annotations.Alias; -import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; -import org.apache.brooklyn.util.yoml.annotations.YomlFieldAtTopLevel; -import org.apache.brooklyn.util.yoml.serializers.ExplicitField; +import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations; import org.testng.Assert; public class YomlTestFixture { @@ -119,43 +113,11 @@ public YomlTestFixture addTypeWithAnnotations(Class type) { return addTypeWithAnnotations(null, type); } public YomlTestFixture addTypeWithAnnotations(String name, Class type) { - MutableSet names = MutableSet.of(); - - Alias overallAlias = type.getAnnotation(Alias.class); - if (name!=null) { - names.addIfNotNull(name); - } - if (overallAlias!=null) { - if (Strings.isNonBlank(overallAlias.preferred())) { - names.add( overallAlias.preferred() ); - } - names.addAll( Arrays.asList(overallAlias.value()) ); - } - if (name==null) { - names.add(type.getName()); - } - - Set serializers = findSerializerAnnotations(type); - for (String n: names) { + Set serializers = YomlAnnotations.findSerializerAnnotations(type); + for (String n: YomlAnnotations.findTypeNamesFromAnnotations(type, name, false)) { tr.put(n, type, serializers); } return this; } - - public static Set findSerializerAnnotations(Class type) { - Set result = MutableSet.of(); - if (!type.getSuperclass().equals(Object.class)) { - result.addAll(findSerializerAnnotations(type.getSuperclass())); - } - - YomlAllFieldsAtTopLevel allFields = type.getAnnotation(YomlAllFieldsAtTopLevel.class); - for (Field f: type.getDeclaredFields()) { - YomlFieldAtTopLevel ytf = f.getAnnotation(YomlFieldAtTopLevel.class); - if (ytf!=null || allFields!=null) - result.add(new ExplicitField(f)); - } - return result; - } - } From bcd65dd3112fe631f9255f0e878b6dc9070f685e Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 8 Sep 2016 13:19:44 +0100 Subject: [PATCH 29/77] support `$brooklyn:object-yoml` in DSL --- .../spi/dsl/BrooklynDslDeferredSupplier.java | 1 - .../spi/dsl/BrooklynDslInterpreter.java | 4 + .../spi/dsl/methods/BrooklynDslCommon.java | 96 +++++++++++++++--- .../brooklyn/spi/dsl/parse/DslParser.java | 2 +- .../camp/yoml/YomlTypePlanTransformer.java | 17 +++- .../yoml/ObjectYomlInBrooklynDslTest.java | 98 +++++++++++++++++++ .../core/catalog/internal/CatalogUtils.java | 2 +- 7 files changed, 200 insertions(+), 20 deletions(-) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java index 86f1c825e6..4e3437dd0f 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java @@ -37,7 +37,6 @@ import org.apache.brooklyn.util.core.task.ImmediateSupplier; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.exceptions.Exceptions; -import org.apache.brooklyn.util.guava.Maybe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java index f43d33c4b9..93e76574aa 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java @@ -37,6 +37,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.CaseFormat; import com.google.common.base.Optional; /** @@ -168,6 +169,9 @@ public Object evaluateOn(Object o, FunctionWithArgs f, boolean deepEvaluation) { String fn = f.getFunction(); fn = Strings.removeFromStart(fn, "$brooklyn:"); + if (fn.contains("-")) { + fn = CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, fn); + } if (fn.startsWith("function.")) { // If the function name starts with 'function.', then we look for the function in BrooklynDslCommon.Functions // As all functions in BrooklynDslCommon.Functions are static, we don't need to worry whether a class diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java index 8fb48cff8f..f5a41c3832 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java @@ -31,6 +31,7 @@ import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.mgmt.ExecutionContext; +import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.objs.Configurable; import org.apache.brooklyn.api.sensor.Sensor; @@ -39,7 +40,7 @@ import org.apache.brooklyn.camp.brooklyn.spi.creation.EntitySpecConfiguration; import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslDeferredSupplier; import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent.Scope; -import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.config.external.ExternalConfigSupplier; import org.apache.brooklyn.core.entity.EntityDynamicType; import org.apache.brooklyn.core.entity.EntityInternal; @@ -48,13 +49,13 @@ import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.persist.DeserializingClassRenamesProvider; import org.apache.brooklyn.core.sensor.DependentConfiguration; +import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.ClassLoaderUtils; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.core.flags.FlagUtils; import org.apache.brooklyn.util.core.task.Tasks; -import org.apache.brooklyn.util.core.task.ValueResolver; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; @@ -141,6 +142,7 @@ public static Sensor sensor(String clazzName, String sensorName) { try { // TODO Should use catalog's classloader, rather than ClassLoaderUtils; how to get that? Should we return a future?! // Should have the catalog's loader at this point in a thread local + // eg CatalogUtils.getClassLoadingContext(entity) String mappedClazzName = DeserializingClassRenamesProvider.findMappedName(clazzName); Class clazz = new ClassLoaderUtils(BrooklynDslCommon.class).loadClass(mappedClazzName); @@ -208,6 +210,10 @@ public static Object object(Map arguments) { } } } + + public static Object objectYoml(Object parseTree) { + return new DslYomlObject(parseTree); + } // String manipulation @@ -411,7 +417,6 @@ public DslObject( this.config = MutableMap.copyOf(config); } - @Override public Maybe getImmediately() { final Class clazz = getOrLoadType(); @@ -456,7 +461,7 @@ class UnavailableException extends RuntimeException { @Override public Task newTask() { final Class clazz = getOrLoadType(); - final ExecutionContext executionContext = ((EntityInternal)entity()).getExecutionContext(); + final ExecutionContext executionContext = entity().getExecutionContext(); final Function resolver = new Function() { @Override public Object apply(Object value) { @@ -490,13 +495,18 @@ public Object call() throws Exception { protected Class getOrLoadType() { Class type = this.type; - if (type == null) { - EntityInternal entity = entity(); - try { - type = new ClassLoaderUtils(BrooklynDslCommon.class, entity).loadClass(typeName); - } catch (ClassNotFoundException e) { - throw Exceptions.propagate(e); - } + if (type != null) return type; + + EntityInternal entity = entity(); + Maybe> typeM = CatalogUtils.getClassLoadingContext(entity).tryLoadClass(typeName); + + if (typeM.isPresent()) return typeM.get(); + + try { + type = new ClassLoaderUtils(BrooklynDslCommon.class, entity).loadClass(typeName); + } catch (ClassNotFoundException e) { + // prefer the exception from typeM + typeM.get(); // (will always throw) } return type; } @@ -560,10 +570,73 @@ public boolean equals(Object obj) { @Override public String toString() { + // TODO more faithfully represent the input; see comments at DslYomlObject.toString return "$brooklyn:object(\""+(type != null ? type.getName() : typeName)+"\")"; } } + /** Deferred execution of Object creation from YOML input. Note no expected type can be set currently, + * and none is inferred. */ + protected static class DslYomlObject extends BrooklynDslDeferredSupplier { + + private static final long serialVersionUID = 8878388748085419L; + + private final Object parseTree; + + public DslYomlObject(Object parseTree) { + this.parseTree = parseTree; + } + + @Override + public Maybe getImmediately() { + final ExecutionContext executionContext = ((EntityInternal)entity()).getExecutionContext(); + Maybe parseTreeResolved = Tasks.resolving(parseTree, Object.class).context(executionContext).deep(true).immediately(true).getMaybe(); + if (parseTreeResolved.isAbsent()) return parseTreeResolved; + return Maybe.of(create(entity().getManagementContext(), entity(), parseTreeResolved.get())); + } + + @Override + public Task newTask() { + return Tasks.builder().displayName("Instantiating yoml supplied in DSL") + .tag(BrooklynTaskTags.TRANSIENT_TASK_TAG) + .dynamic(false) + .body(new Callable() { + @Override + public Object call() throws Exception { + return create(entity().getManagementContext(), entity(), parseTree); + }}) + .build(); + } + + public static Object create(ManagementContext mgmt, Entity entity, Object parseTree) { + return mgmt.getTypeRegistry().createBeanFromPlan("yoml", parseTree, + RegisteredTypeLoadingContexts.loader(CatalogUtils.getClassLoadingContext(entity())), null); + } + + @Override + public int hashCode() { + return Objects.hashCode(parseTree); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + DslYomlObject that = DslYomlObject.class.cast(obj); + return Objects.equal(this.parseTree, that.parseTree); + } + + @Override + public String toString() { + // TODO this will generally not generate reparseable output for maps etc + // ideally we'd have access to the original parse node and contents and could embed that here + // (but that requires refactoring the parser) + // that would also allow us to pass the string plan as is preferred in YomlTypePlanTransformer + return "$brooklyn:object-yoml("+JavaStringEscapes.wrapJavaString(Strings.toString(parseTree))+")"; + } + + } + /** * Defers to management context's {@link ExternalConfigSupplierRegistry} to resolve values at runtime. * The name of the appropriate {@link ExternalConfigSupplier} is captured, along with the key of @@ -652,7 +725,6 @@ public String apply(@Nullable String s) { protected static class DslRegexReplacer extends BrooklynDslDeferredSupplier> { private static final long serialVersionUID = -2900037495440842269L; - private Object pattern; private Object replacement; diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/DslParser.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/DslParser.java index 7b0f359dc6..3eaa08bdb5 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/DslParser.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/DslParser.java @@ -78,7 +78,7 @@ public Object next() { char c = expression.charAt(index); if (Character.isJavaIdentifierPart(c)) ; // these chars also permitted - else if (".:".indexOf(c)>=0) ; + else if (".:-".indexOf(c)>=0) ; // other things e.g. whitespace, parentheses, etc, skip else break; index++; diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java index 488ea5b34a..4017afadb7 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java @@ -51,14 +51,16 @@ * - test serializers * - support serializers by annotation * - set serializers when adding to catalog and test + * - $brooklyn:object-yoml: * * NEXT - * - $brooklyn:object(format): + * - support $brooklyn DSL in yoml * - catalog impl supports format * * (initdish can now be made to work) * * THEN + * - update docs for above, including $brooklyn:object-yoml, yoml overview * - support specs from yoml * - type registry api, add arbitrary types via yoml, specifying format * - catalog impl in yoml as test? @@ -134,10 +136,15 @@ protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext co return y.read((String) data, expectedSuperTypeName); } - // could do this -// return y.readFromYamlObject(data, expectedSuperTypeName); - // but we require always a string - throw new IllegalArgumentException("The implementation plan for '"+type+"' should be a string in order to process as YOML"); + if (type.getSymbolicName()!=null) { + throw new IllegalArgumentException("The implementation plan for '"+type+"' should be a string in order to process as YOML"); + } + + // we do this (supporint non-string and pre-parsed plans) in order to support the YAML DSL + // and other limited use cases (see DslYomlObject); + // NB this is only for ad hoc plans (code above) and we plan to deprecate as soon as we can + // get the underlying string in the $brooklyn DSL context + return y.readFromYamlObject(data, expectedSuperTypeName); } @Override diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java new file mode 100644 index 0000000000..5973ad658f --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.yoml; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.google.api.client.repackaged.com.google.common.base.Joiner; +import com.google.common.collect.Iterables; + +public class ObjectYomlInBrooklynDslTest extends AbstractYamlTest { + + private static final Logger log = LoggerFactory.getLogger(ObjectYomlInBrooklynDslTest.class); + + private BasicBrooklynTypeRegistry registry() { + return (BasicBrooklynTypeRegistry) mgmt().getTypeRegistry(); + } + + private void add(RegisteredType type) { + add(type, false); + } + private void add(RegisteredType type, boolean canForce) { + registry().addToLocalUnpersistedTypeRegistry(type, canForce); + } + + @YomlAllFieldsAtTopLevel + public static class ItemA { + String name; + /* required for 'object.fields' */ public void setName(String name) { this.name = name; } + @Override public String toString() { return super.toString()+"[name="+name+"]"; } + } + + private final static RegisteredType SAMPLE_TYPE = BrooklynYomlTypeRegistry.newYomlRegisteredType( + RegisteredTypeKind.BEAN, null, "1", ItemA.class); + + void doTest(String ...lines) throws Exception { + add(SAMPLE_TYPE); + String yaml = Joiner.on("\n").join( + "services:", + "- type: org.apache.brooklyn.core.test.entity.TestEntity", + " brooklyn.config:", + " test.obj:"); + yaml += Joiner.on("\n ").join("", "", (Object[])lines); + + Entity app = createStartWaitAndLogApplication(yaml); + Entity entity = Iterables.getOnlyElement( app.getChildren() ); + + Object obj = entity.config().get(ConfigKeys.newConfigKey(Object.class, "test.obj")); + log.info("Object for "+JavaClassNames.callerNiceClassAndMethod(1)+" : "+obj); + Asserts.assertInstanceOf(obj, ItemA.class); + Assert.assertEquals(((ItemA)obj).name, "bob"); + } + + @Test + public void testOldStyle() throws Exception { + doTest( + "$brooklyn:object:", + " type: "+ItemA.class.getName(), + " object.fields:", + " name: bob"); + } + + @Test + public void testYomlSyntax() throws Exception { + doTest( + "$brooklyn:object-yoml:", + " type: "+ItemA.class.getName(), + " name: bob"); + } + +} diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java index dce5493cc3..32b4b2d3ce 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogUtils.java @@ -73,7 +73,7 @@ public static BrooklynClassLoadingContext newClassLoadingContext(ManagementConte public static BrooklynClassLoadingContext newClassLoadingContext(ManagementContext mgmt, RegisteredType item) { return newClassLoadingContext(mgmt, item.getId(), item.getLibraries(), null); } - + /** made @Beta in 0.9.0 because we're not sure to what extent to support stacking loaders; * only a couple places currently rely on such stacking, in general the item and the bundles *are* the context, * and life gets hard if we support complex stacking! */ From 52a20093acd8010a69dd8e4188e439f967d3a715 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 8 Sep 2016 15:56:19 +0100 Subject: [PATCH 30/77] background type coercion extensions --- .../util/core/flags/TypeCoercions.java | 4 ++++ .../util/core/task/ValueResolver.java | 23 ++++++++++++++++++- .../coerce/PrimitiveStringTypeCoercions.java | 3 +-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java b/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java index 5eb9141a04..c7d34c6bda 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java @@ -69,6 +69,10 @@ private TypeCoercions() {} BrooklynInitialization.initTypeCoercionStandardAdapters(); } + public static TypeCoercer getInstance() { + return coercer; + } + public static void initStandardAdapters() { new BrooklynCommonAdaptorTypeCoercions(coercer).registerAllAdapters(); registerDeprecatedBrooklynAdapters(); diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/ValueResolver.java b/core/src/main/java/org/apache/brooklyn/util/core/task/ValueResolver.java index 4c3cbe2575..4dd8b7ddb4 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/task/ValueResolver.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/task/ValueResolver.java @@ -35,6 +35,7 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.apache.brooklyn.util.javalang.coerce.TypeCoercer; import org.apache.brooklyn.util.repeat.Repeater; import org.apache.brooklyn.util.time.CountdownTimer; import org.apache.brooklyn.util.time.Duration; @@ -301,7 +302,7 @@ protected Maybe getMaybeInternal() { if (timerU==null && timeout!=null) timerU = timeout.countdownTimer(); final CountdownTimer timer = timerU; - if (timer!=null && !timer.isRunning()) + if (timer!=null && !timer.isNotPaused()) timer.start(); checkTypeNotNull(); @@ -471,4 +472,24 @@ protected Object getOriginalValue() { public String toString() { return JavaClassNames.cleanSimpleClassName(this)+"["+JavaClassNames.cleanSimpleClassName(type)+" "+value+"]"; } + + /** Returns a quick resolving type coercer. May allow more underlying {@link ValueResolver} customization in the future. */ + public static class ResolvingTypeCoercer implements TypeCoercer { + @Override + public T coerce(Object input, Class type) { + return tryCoerce(input, type).get(); + } + + @Override + public Maybe tryCoerce(Object input, Class type) { + return new ValueResolver(input, type).timeout(REAL_QUICK_WAIT).getMaybe(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Maybe tryCoerce(Object input, TypeToken type) { + return (Maybe) tryCoerce(input, type.getRawType()); + } + } + } \ No newline at end of file diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/PrimitiveStringTypeCoercions.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/PrimitiveStringTypeCoercions.java index cad798d7ad..db01453cbf 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/PrimitiveStringTypeCoercions.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/PrimitiveStringTypeCoercions.java @@ -22,7 +22,6 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.javalang.Boxing; import org.apache.brooklyn.util.javalang.JavaClassNames; import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes; @@ -32,7 +31,7 @@ public class PrimitiveStringTypeCoercions { public PrimitiveStringTypeCoercions() {} - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({ "unchecked" }) public static Maybe tryCoerce(Object value, Class targetType) { //deal with primitive->primitive casting if (isPrimitiveOrBoxer(targetType) && isPrimitiveOrBoxer(value.getClass())) { From 617417c5c1b16937a2b146108365fb73dd1b3d5e Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 8 Sep 2016 15:56:32 +0100 Subject: [PATCH 31/77] support resolution of objects eg $brooklyn dsl suppliers within yoml input --- .../camp/yoml/YomlTypePlanTransformer.java | 3 ++ .../yoml/ObjectYomlInBrooklynDslTest.java | 19 +++++++++-- .../serializers/InstantiateTypePrimitive.java | 32 ++++++++++++------- .../InstantiateTypeWorkerAbstract.java | 3 +- .../YomlSerializerComposition.java | 14 ++++++++ 5 files changed, 56 insertions(+), 15 deletions(-) diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java index 4017afadb7..dc373eb04e 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java @@ -28,6 +28,7 @@ import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer; import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.core.task.ValueResolver; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; @@ -113,6 +114,8 @@ protected double scoreForNonmatchingNonnullFormat(String planFormat, Object plan protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception { BrooklynYomlTypeRegistry tr = new BrooklynYomlTypeRegistry(mgmt, context); Yoml y = Yoml.newInstance(tr); + y.getConfig().coercer = new ValueResolver.ResolvingTypeCoercer(); + // TODO could cache the parse, could cache the instantiation instructions Object data = type.getPlan().getPlanData(); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java index 5973ad658f..11c0ca290a 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java @@ -23,6 +23,7 @@ import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.javalang.JavaClassNames; @@ -60,7 +61,7 @@ public static class ItemA { private final static RegisteredType SAMPLE_TYPE = BrooklynYomlTypeRegistry.newYomlRegisteredType( RegisteredTypeKind.BEAN, null, "1", ItemA.class); - void doTest(String ...lines) throws Exception { + Entity doDeploy(String ...lines) throws Exception { add(SAMPLE_TYPE); String yaml = Joiner.on("\n").join( "services:", @@ -70,8 +71,11 @@ void doTest(String ...lines) throws Exception { yaml += Joiner.on("\n ").join("", "", (Object[])lines); Entity app = createStartWaitAndLogApplication(yaml); - Entity entity = Iterables.getOnlyElement( app.getChildren() ); + return Iterables.getOnlyElement( app.getChildren() ); + } + void doTest(String ...lines) throws Exception { + Entity entity = doDeploy(lines); Object obj = entity.config().get(ConfigKeys.newConfigKey(Object.class, "test.obj")); log.info("Object for "+JavaClassNames.callerNiceClassAndMethod(1)+" : "+obj); Asserts.assertInstanceOf(obj, ItemA.class); @@ -95,4 +99,15 @@ public void testYomlSyntax() throws Exception { " name: bob"); } + @Test + public void testYomlSyntaxUsindDslAgain() throws Exception { + Entity entity = doDeploy( + "$brooklyn:object-yoml:", + " type: "+ItemA.class.getName(), + " name: $brooklyn:self().attributeWhenReady(\"test.sensor\")"); + entity.sensors().set(Sensors.newStringSensor("test.sensor"), "bobella"); + Object obj = entity.config().get(ConfigKeys.newConfigKey(Object.class, "test.obj")); + Assert.assertEquals(((ItemA)obj).name, "bobella"); + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java index 3781e5e74e..b6ff4de9f4 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java @@ -42,7 +42,7 @@ public void read() { if (!canDoRead()) return; Class expectedJavaType; - Maybe value; + Maybe value = Maybe.absent(); if (isJsonPrimitiveObject(getYamlObject())) { // pure primitive - we must know the type and then we should simply be able to coerce @@ -55,19 +55,27 @@ public void read() { if (value.isAbsent()) return; } else { - // not primitive; it should be of {type: ..., value: ...} format with type being the primitive + // not primitive; either should be coercible or should be of {type: ..., value: ...} format with type being the primitive - String typeName = readingTypeFromFieldOrExpected(); - if (typeName==null) return; - expectedJavaType = config.getTypeRegistry().getJavaTypeMaybe(typeName).orNull(); - if (expectedJavaType==null) expectedJavaType = getSpecialKnownTypeName(typeName); - if (!isJsonPrimitiveType(expectedJavaType) && !isJsonMarkerType(typeName)) return; - - value = readingValueFromTypeValueMap(); - if (value.isAbsent()) return; - if (tryCoerceAndNoteError(value.get(), expectedJavaType).isAbsent()) return; + expectedJavaType = getExpectedTypeJava(); + if (!isJsonComplexObject(getYamlObject()) && (expectedJavaType!=null || isJsonMarkerTypeExpected())) { + // if it's not a json map/list (and not a primitive) than try a coercion; + // maybe a bit odd to call that "primitive" but it is primitive in the sense it is pass-through unparsed + value = tryCoerceAndNoteError(getYamlObject(), expectedJavaType); + } - removeTypeAndValueKeys(); + if (value.isAbsent()) { + String typeName = readingTypeFromFieldOrExpected(); + if (typeName==null) return; + expectedJavaType = config.getTypeRegistry().getJavaTypeMaybe(typeName).orNull(); + if (expectedJavaType==null) expectedJavaType = getSpecialKnownTypeName(typeName); + if (!isJsonPrimitiveType(expectedJavaType) && !isJsonMarkerType(typeName)) return; + + value = readingValueFromTypeValueMap(); + if (value.isAbsent()) return; + if (tryCoerceAndNoteError(value.get(), expectedJavaType).isAbsent()) return; + removeTypeAndValueKeys(); + } } storeReadObjectAndAdvance(value.get(), false); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java index 99b50a0c4a..e0cebd7ff9 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java @@ -91,7 +91,8 @@ protected Maybe tryCoerceAndNoteError(Object value, Class expectedJavaType Maybe coerced = config.getCoercer().tryCoerce(value, expectedJavaType); if (coerced.isAbsent()) { // type present but not coercible - error - ReadingTypeOnBlackboard.get(blackboard).addNote("Cannot interpret '"+value+"' as primitive "+expectedJavaType); + ReadingTypeOnBlackboard.get(blackboard).addNote("Cannot interpret or coerce '"+value+"' as "+ + config.getTypeRegistry().getTypeNameOfClass(expectedJavaType)); } return coerced; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java index 2a3a575142..536f7856f3 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java @@ -18,6 +18,7 @@ */ package org.apache.brooklyn.util.yoml.serializers; +import java.util.Collection; import java.util.Map; import java.util.Set; @@ -33,6 +34,7 @@ import org.apache.brooklyn.util.yoml.internal.YomlConfig; import org.apache.brooklyn.util.yoml.internal.YomlConverter; import org.apache.brooklyn.util.yoml.internal.YomlUtils; +import org.apache.brooklyn.util.yoml.internal.YomlUtils.JsonMarker; public abstract class YomlSerializerComposition implements YomlSerializer { @@ -130,12 +132,24 @@ protected Set findAllKeyManglesYamlKeys(String targetKey) { return result; } + /** true iff the object is a string or java primitive type */ protected boolean isJsonPrimitiveObject(Object o) { if (o==null) return true; if (o instanceof String) return true; if (Boxing.isPrimitiveOrBoxedObject(o)) return true; return false; } + + /** true iff the object is a map or collection (not recursing; for that see {@link #isJsonPureObject(Object)} */ + protected boolean isJsonComplexObject(Object o) { + return (o instanceof Map || o instanceof Collection); + } + + /** true iff the object is a primitive type or a map or collection of pure objects; + * see {@link JsonMarker#isPureJson(Object)} (which this simply proxies for convenience) */ + protected boolean isJsonPureObject(Object o) { + return YomlUtils.JsonMarker.isPureJson(o); + } public abstract void read(); public abstract void write(); From 6252a8ab4c9207a5e8d6f36f1901cc89ea501ab7 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 9 Sep 2016 08:35:49 +0100 Subject: [PATCH 32/77] support $brooklyn dsl when *parsing* yoml input --- .../spi/dsl/BrooklynDslInterpreter.java | 4 +- .../camp/yoml/BrooklynYomlTypeRegistry.java | 9 +- .../camp/yoml/YomlTypePlanTransformer.java | 41 ++- .../camp/yoml/types/YomlInitializers.java | 288 ++++++++++++++++++ .../yoml/BrooklynDslInYomlStringPlanTest.java | 86 ++++++ 5 files changed, 412 insertions(+), 16 deletions(-) create mode 100644 camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java index 93e76574aa..b3d0194bda 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java @@ -32,13 +32,13 @@ import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode; import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode.Role; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.CaseFormat; -import com.google.common.base.Optional; /** * {@link PlanInterpreter} which understands the $brooklyn DSL @@ -184,7 +184,7 @@ public Object evaluateOn(Object o, FunctionWithArgs f, boolean deepEvaluation) { for (Object arg: f.getArgs()) { args.add( deepEvaluation ? evaluate(arg, true) : arg ); } - Optional v = Reflections.invokeMethodWithArgs(o, fn, args); + Maybe v = Reflections.invokeMethodFromArgs(o, fn, args); if (v.isPresent()) return v.get(); } catch (Exception e) { Exceptions.propagateIfFatal(e); diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java index 5e72bcb737..ac3e9dd0e9 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java @@ -388,20 +388,21 @@ protected void collectSerializers(Object type, Collection result public static RegisteredType newYomlRegisteredType(RegisteredTypeKind kind, String symbolicName, String version, String planData, Class javaConcreteType, - Iterable superTypesAsClassOrRegisteredType, + Iterable addlSuperTypesAsClassOrRegisteredType, Iterable serializers) { YomlTypeImplementationPlan plan = new YomlTypeImplementationPlan(planData, javaConcreteType, serializers); RegisteredType result = kind==RegisteredTypeKind.SPEC ? RegisteredTypes.spec(symbolicName, version, plan) : RegisteredTypes.bean(symbolicName, version, plan); RegisteredTypes.addSuperType(result, javaConcreteType); - RegisteredTypes.addSuperTypes(result, superTypesAsClassOrRegisteredType); + RegisteredTypes.addSuperTypes(result, addlSuperTypesAsClassOrRegisteredType); return result; } - public static RegisteredType newYomlRegisteredType(RegisteredTypeKind bean, @Nullable String symbolicName, String version, Class clazz) { + /** null symbolic name means to take it from annotations or the class name */ + public static RegisteredType newYomlRegisteredType(RegisteredTypeKind kind, @Nullable String symbolicName, String version, Class clazz) { Set names = YomlAnnotations.findTypeNamesFromAnnotations(clazz, symbolicName, false); Set serializers = YomlAnnotations.findSerializerAnnotations(clazz); - RegisteredType type = BrooklynYomlTypeRegistry.newYomlRegisteredType(RegisteredTypeKind.BEAN, + RegisteredType type = BrooklynYomlTypeRegistry.newYomlRegisteredType(kind, // symbolicName, version, names.iterator().next(), version, // planData - null means just use the java type (could have done this earlier), diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java index dc373eb04e..87e229e7e3 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java @@ -24,6 +24,10 @@ import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; +import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslInterpreter; +import org.apache.brooklyn.camp.spi.resolve.PlanInterpreter; +import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationContext; +import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode; import org.apache.brooklyn.core.typereg.AbstractFormatSpecificTypeImplementationPlan; import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer; import org.apache.brooklyn.core.typereg.RegisteredTypes; @@ -35,6 +39,7 @@ import org.apache.brooklyn.util.yoml.Yoml; import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.yaml.snakeyaml.Yaml; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -53,9 +58,9 @@ * - support serializers by annotation * - set serializers when adding to catalog and test * - $brooklyn:object-yoml: + * - support $brooklyn DSL in yoml * * NEXT - * - support $brooklyn DSL in yoml * - catalog impl supports format * * (initdish can now be made to work) @@ -65,6 +70,7 @@ * - support specs from yoml * - type registry api, add arbitrary types via yoml, specifying format * - catalog impl in yoml as test? + * - type registry persistence * - REST API for deploy accepts specific format, can call yoml (can we test this earlier?) * * AND THEN @@ -122,6 +128,7 @@ protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext co Class expectedSuperType = context.getExpectedJavaSuperType(); String expectedSuperTypeName = tr.getTypeNameOfClass(expectedSuperType); + Object parsedInput; if (data==null || (data instanceof String)) { if (Strings.isBlank((String)data)) { // blank plan means to use the java type @@ -135,19 +142,33 @@ protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext co } } - // normal processing - return y.read((String) data, expectedSuperTypeName); + // else we need to parse to json objects, then translate it (below) + parsedInput = new Yaml().load((String) data); + + } else { + // we do this (supporint non-string and pre-parsed plans) in order to support the YAML DSL + // and other limited use cases (see DslYomlObject); + // NB this is only for ad hoc plans (code above) and we may deprecate it altogether as soon as we can + // get the underlying string in the $brooklyn DSL context + + if (type.getSymbolicName()!=null) { + throw new IllegalArgumentException("The implementation plan for '"+type+"' should be a string in order to process as YOML"); + } + parsedInput = data; + } - if (type.getSymbolicName()!=null) { - throw new IllegalArgumentException("The implementation plan for '"+type+"' should be a string in order to process as YOML"); + // in either case, now run interpreters (dsl) if it's a m=ap, then translate + + if (parsedInput instanceof Map) { + List interpreters = MutableList.of(new BrooklynDslInterpreter()); + @SuppressWarnings("unchecked") + PlanInterpretationNode interpretation = new PlanInterpretationNode( + new PlanInterpretationContext((Map)parsedInput, interpreters)); + parsedInput = interpretation.getNewValue(); } - // we do this (supporint non-string and pre-parsed plans) in order to support the YAML DSL - // and other limited use cases (see DslYomlObject); - // NB this is only for ad hoc plans (code above) and we plan to deprecate as soon as we can - // get the underlying string in the $brooklyn DSL context - return y.readFromYamlObject(data, expectedSuperTypeName); + return y.readFromYamlObject(parsedInput, expectedSuperTypeName); } @Override diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java new file mode 100644 index 0000000000..3e9ea7e5cd --- /dev/null +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java @@ -0,0 +1,288 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.yoml.types; + +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.camp.yoml.BrooklynYomlTypeRegistry; +import org.apache.brooklyn.core.BrooklynVersion; +import org.apache.brooklyn.core.effector.ssh.SshCommandEffector; +import org.apache.brooklyn.core.sensor.ssh.SshCommandSensor; +import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; +import org.apache.brooklyn.util.yoml.YomlSerializer; + +import com.google.common.annotations.Beta; + +/* + +TYPES - effector +- script / bash / ssh +- invoke-effector +- publish-sensor +- initdish-effector +- parallel +- sequence + + - ssh + - install-file + - set-sensor + - set-config + + +SEQ +* initd +* specify pre-conditions & post-conditions +* specify what must run before/after + +a +b after a +c before b + +EXTENSIONS: +* local task parameters -- could pass params to any subtask +* acquire-semaphore (cancel holder, timeout + +* parallel +* run over entities -- concurrency: 16 +* conditional +* jumping? +* set sensors/config +* access results of previous tasks + + +STYLE NOTES + + brooklyn.initializers: + - type: org.apache.brooklyn.core.effector.ssh.SshCommandEffector + brooklyn.config: + name: sayHiNetcat + description: Echo a small hello string to the netcat entity + command: | + echo $message | nc $TARGET_HOSTNAME 4321 + parameters: + message: + description: The string to pass to netcat + defaultValue: hi netcat + +effectors: + say-hi: + type: script + env: + name: $brooklyn:config("name") + name: $brooklyn:${name} + script: + echo hello ${name:-world} + + publish-name: + type: publish-sensor + sensor: name + value: $brooklyn:formatString("%s (%s)", config("name"), $("baz")) + parameters: + foo: # nothing + bar: { description: The bar, default-value: B } + baz: Z # default + + start: + type: initdish-effector + impl: + 8.2-something: + type: invoke-effector + effector: say-hi + parameters: + name: Bob + 8.2-something: + invoke-effector: say-hi + 8.2-something: + invoke-effector: + effector: say-hi + parameters: + name: Bob + 8.2-something: + "saying hi": + type: invoke-effector + effector: say-hi + parameters: + name: Bob + + +COMPARISON TO OLD + +we used to say: + + brooklyn.initializers: + - type: org.apache.brooklyn.core.effector.ssh.SshCommandEffector + brooklyn.config: + name: sayHiNetcat + description: Echo a small hello string to the netcat entity + command: | + echo $message | nc $TARGET_HOSTNAME 4321 + parameters: + message: + description: The string to pass to netcat + defaultValue: hi netcat + - type: org.apache.brooklyn.core.sensor.ssh.SshCommandSensor + brooklyn.config: + name: output.last + period: 1s + command: tail -1 server-input + +now we say: + + brooklyn.initializers: + say-hi-netcat: + type: ssh-effector + description: Echo a small hello string to the netcat entity + script: | + echo $message | nc $TARGET_HOSTNAME 4321 + parameters: + message: + description: The string to pass to netcat + default-value: hi netcat + output.last: + type: ssh-sensor + period: 1s + command: tail -1 server-input + +benefits: + - readable: more concise description and supports aliases, maps (which plays nice with merge) + - extensible: *much* easier to define new items, including recursive + - introspective: auto-generate docs and support code completion with descriptions + - bi-directional: we can persist in the same formal + + + +OTHER THOUGHTS + +effectors: + - type: CommandSequenceEffector + name: start + impl: + - sequence_identifier: 0-provision [optional] + type: my.ProvisionEffectorTaskFactory + - seq_id: 1-install + type: script + script: | + echo foo ${entity.config.foo} + + + 01-install: + parallel: + - bash: | + echo foo ${entity.config.foo} + - copy: + from: URL + to: ${entity.sensor['run.dir']}/file.txt + 02-loop-entities: + foreach: + expression: $brooklyn:component['x'].descendants + var: x + command: + effector: + name: restart + parameters: + restart_machine: auto + 03-condition: + conditional: + if: $x + then: + bash: echo yup + else: + - if: $y + then: + bash: echo y + - bash: echo none + 04-jump: + goto: 02-install + + + + + +catalog: + id: my-basic-1 +services: +- type: vanilla-software-process + effectors: + start: + 0-provision: get-machine + 1-install: + - copy: + from: classpath://foo.tar + to: /tmp/foo/foo.tar + - bash: + - cd /tmp/foo + - unzip foo.tar + - bash: foo.sh + 2-launch: + - cd /tmp/foo ; ./foo.sh + policy: + - type: Hook1 + + triggers: + - on: $brooklyn:component("db").sensor("database_url") + do: + - bash: + mysql install table + - publish_sensor basic-ready true + +---- + +services: +- type: my-basic-2 + effectors: + start: + 1.5.11-post-install: + bash: | +echo custom step + + 1.7-another-post-install: + + */ +public class YomlInitializers { + + /** + * Adds the given type to the registry. + * As the registry is not yet persisted this method must be called explicitly to initialize any management context using it. + * This method will be deleted when there is a catalog-style init/persistence mechanism. */ + @Beta + public static void addLocalType(ManagementContext mgmt, RegisteredType type) { + ((BasicBrooklynTypeRegistry) mgmt.getTypeRegistry()).addToLocalUnpersistedTypeRegistry(type, false); + } + + /** As {@link #addLocalType(ManagementContext, RegisteredType)} */ @Beta + public static void addLocalBean(ManagementContext mgmt, Class clazz) { + addLocalType(mgmt, BrooklynYomlTypeRegistry.newYomlRegisteredType(RegisteredTypeKind.BEAN, null, BrooklynVersion.get(), clazz)); + } + + /** As {@link #addLocalType(ManagementContext, RegisteredType)} */ @Beta + public static void addLocalBean(ManagementContext mgmt, String symbolicName, String planYaml, + Class javaConcreteType, + Iterable addlSuperTypesAsClassOrRegisteredType, + Iterable serializers) { + addLocalType(mgmt, BrooklynYomlTypeRegistry.newYomlRegisteredType(RegisteredTypeKind.BEAN, null, BrooklynVersion.get(), + planYaml, javaConcreteType, addlSuperTypesAsClassOrRegisteredType, serializers)); + } + + public static void install(ManagementContext mgmt) { + addLocalBean(mgmt, SshCommandEffector.class); + addLocalBean(mgmt, SshCommandSensor.class); + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java new file mode 100644 index 0000000000..92e0d1e0d4 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.yoml; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; +import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.google.api.client.repackaged.com.google.common.base.Joiner; +import com.google.common.collect.Iterables; + +public class BrooklynDslInYomlStringPlanTest extends AbstractYamlTest { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(BrooklynDslInYomlStringPlanTest.class); + + private BasicBrooklynTypeRegistry registry() { + return (BasicBrooklynTypeRegistry) mgmt().getTypeRegistry(); + } + + private void add(RegisteredType type) { + add(type, false); + } + private void add(RegisteredType type, boolean canForce) { + registry().addToLocalUnpersistedTypeRegistry(type, canForce); + } + + @YomlAllFieldsAtTopLevel + public static class ItemA { + String name; + @Override public String toString() { return super.toString()+"[name="+name+"]"; } + } + + private final static RegisteredType SAMPLE_TYPE_BASE = BrooklynYomlTypeRegistry.newYomlRegisteredType( + RegisteredTypeKind.BEAN, "item-base", "1", ItemA.class); + + private final static RegisteredType SAMPLE_TYPE_TEST = BrooklynYomlTypeRegistry.newYomlRegisteredType( + RegisteredTypeKind.BEAN, "item-w-dsl", "1", "{ type: item-base, name: '$brooklyn:self().attributeWhenReady(\"test.sensor\")' }", + ItemA.class, null, null); + + @Test + public void testYomlParserRespectsDsl() throws Exception { + add(SAMPLE_TYPE_BASE); + add(SAMPLE_TYPE_TEST); + + String yaml = Joiner.on("\n").join( + "services:", + "- type: org.apache.brooklyn.core.test.entity.TestEntity", + " brooklyn.config:", + " test.obj:", + " $brooklyn:object-yoml: item-w-dsl"); + + Entity app = createStartWaitAndLogApplication(yaml); + Entity entity = Iterables.getOnlyElement( app.getChildren() ); + + entity.sensors().set(Sensors.newStringSensor("test.sensor"), "bob"); + Object obj = entity.config().get(ConfigKeys.newConfigKey(Object.class, "test.obj")); + Assert.assertEquals(((ItemA)obj).name, "bob"); + } + +} From 4fad3c28e22b16ced9b87703bc563be62ba068be Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 9 Sep 2016 12:19:05 +0100 Subject: [PATCH 33/77] tidy how explicit field annotations are detected --- .../yoml/annotations/YomlAnnotations.java | 28 +++++++++++++------ .../util/yoml/internal/YomlUtils.java | 10 +++++-- .../yoml/serializers/AllFieldsExplicit.java | 15 ++-------- .../util/yoml/serializers/ExplicitField.java | 6 +++- 4 files changed, 34 insertions(+), 25 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java index 6e35f6ab13..04873a29ab 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java @@ -20,11 +20,15 @@ import java.lang.reflect.Field; import java.util.Arrays; +import java.util.List; +import java.util.Map; import java.util.Set; +import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; import org.apache.brooklyn.util.yoml.serializers.ExplicitField; public class YomlAnnotations { @@ -49,18 +53,24 @@ public static Set findTypeNamesFromAnnotations(Class type, String opt return names; } + public static List findExplicitFieldSerializers(Class t, boolean requireAnnotation) { + List result = MutableList.of(); + Map fields = YomlUtils.getAllNonTransientNonStaticFields(t, null); + for (Map.Entry f: fields.entrySet()) { + if (!requireAnnotation || f.getValue().isAnnotationPresent(YomlFieldAtTopLevel.class)) + result.add(new ExplicitField(f.getKey(), f.getValue())); + } + return result; + } + public static Set findSerializerAnnotations(Class type) { Set result = MutableSet.of(); - if (!type.getSuperclass().equals(Object.class)) { - result.addAll(findSerializerAnnotations(type.getSuperclass())); - } - + + // explicit fields YomlAllFieldsAtTopLevel allFields = type.getAnnotation(YomlAllFieldsAtTopLevel.class); - for (Field f: type.getDeclaredFields()) { - YomlFieldAtTopLevel ytf = f.getAnnotation(YomlFieldAtTopLevel.class); - if (ytf!=null || allFields!=null) - result.add(new ExplicitField(f)); - } + result.addAll(findExplicitFieldSerializers(type, allFields==null)); + + // (so far the above is the only type of serializer we pick up from annotations) return result; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java index b1542ed03c..570bf80d56 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java @@ -25,6 +25,7 @@ import java.util.Map; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.javalang.Boxing; import org.apache.brooklyn.util.javalang.FieldOrderings; import org.apache.brooklyn.util.javalang.ReflectionPredicates; @@ -138,8 +139,8 @@ private boolean parse(String s) { public int subTypeCount() { return subTypes.size(); } } - public static List getAllNonTransientNonStaticFieldNames(Class type, T optionalInstanceToRequireNonNullFieldValue) { - List result = MutableList.of(); + public static Map getAllNonTransientNonStaticFields(Class type, T optionalInstanceToRequireNonNullFieldValue) { + Map result = MutableMap.of(); List fields = Reflections.findFields(type, null, FieldOrderings.ALPHABETICAL_FIELD_THEN_SUB_BEST_FIRST); @@ -153,13 +154,16 @@ public static List getAllNonTransientNonStaticFieldNames(Class ty // if field is shadowed use FQN name = f.getDeclaringClass().getCanonicalName()+"."+name; } - result.add(name); + result.put(name, f); } } lastF = f; } return result; } + public static List getAllNonTransientNonStaticFieldNames(Class type, T optionalInstanceToRequireNonNullFieldValue) { + return MutableList.copyOf(getAllNonTransientNonStaticFields(type, optionalInstanceToRequireNonNullFieldValue).keySet()); + } @SuppressWarnings("unchecked") public static List getAllNonTransientNonStaticFieldNamesUntyped(Class type, Object optionalInstanceToRequireNonNullFieldValue) { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java index 00baa6ca84..f4831a9bf5 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java @@ -18,12 +18,9 @@ */ package org.apache.brooklyn.util.yoml.serializers; -import java.util.List; - -import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; -import org.apache.brooklyn.util.yoml.internal.YomlUtils; /* Adds ExplicitField instances for all fields declared on the type */ @Alias("all-fields-explicit") @@ -48,14 +45,8 @@ protected void run() { // mark done blackboard.put(DoneAllFieldsExplicit.class.getName(), new DoneAllFieldsExplicit()); - SerializersOnBlackboard serializers = SerializersOnBlackboard.get(blackboard); - - List fields = YomlUtils.getAllNonTransientNonStaticFieldNames(getJavaObject().getClass(), null); - for (String f: fields) { - ExplicitField ef = new ExplicitField(); - ef.fieldName = f; - serializers.addInstantiatedTypeSerializers(MutableList.of(ef)); - } + SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers( + YomlAnnotations.findExplicitFieldSerializers(getJavaObject().getClass(), false)); context.phaseRestart(); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java index ab2d4d95aa..c542e37ffe 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java @@ -49,7 +49,11 @@ public class ExplicitField extends YomlSerializerComposition { public ExplicitField() {} public ExplicitField(Field f) { - fieldName = keyName = f.getName(); + this(f.getName(), f); + } + /** preferred constructor for dealing with shadowed fields using superclass.field naming convention */ + public ExplicitField(String name, Field f) { + fieldName = keyName = name; Alias alias = f.getAnnotation(Alias.class); if (alias!=null) { From 1ff3ca75130330761bf53ef9c18eaf6b731569a7 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 12 Sep 2016 13:31:11 +0100 Subject: [PATCH 34/77] add config-key constructor support big change as it requires collecting more information once the type is known but before we instantiate it, and passing instantiation information to nested types --- .../typereg/RegisteredTypeLoadingContext.java | 6 + .../RegisteredTypeLoadingContexts.java | 43 +++- .../util/collections/CollectionMerger.java | 4 +- .../util/collections/MutableList.java | 10 +- .../brooklyn/util/collections/MutableMap.java | 14 ++ .../util/javalang/ReflectionPredicates.java | 6 + .../brooklyn/util/javalang/Reflections.java | 43 +++- .../org/apache/brooklyn/util/yoml/Yoml.java | 6 +- .../brooklyn/util/yoml/YomlContext.java | 2 +- .../yoml/annotations/YomlAnnotations.java | 24 +- .../internal/ConstructionInstruction.java | 149 ++++++++++++ .../internal/SerializersOnBlackboard.java | 16 +- .../util/yoml/internal/YomlConfig.java | 64 ++++- .../util/yoml/internal/YomlConverter.java | 8 +- .../util/yoml/internal/YomlUtils.java | 30 ++- .../ConfigInMapUnderConfigSerializer.java | 76 ++++++ .../ExplicitConfigKeySerializer.java | 127 ++++++++++ .../util/yoml/serializers/ExplicitField.java | 88 ++++--- .../serializers/ExplicitFieldsBlackboard.java | 8 +- .../serializers/FieldsInMapUnderFields.java | 96 +++++--- .../yoml/serializers/InstantiateTypeEnum.java | 11 +- .../InstantiateTypeFromRegistry.java | 57 +++-- ...antiateTypeFromRegistryUsingConfigMap.java | 227 ++++++++++++++++++ .../serializers/InstantiateTypePrimitive.java | 12 +- .../InstantiateTypeWorkerAbstract.java | 19 +- .../serializers/JavaFieldsOnBlackboard.java | 49 +++- .../serializers/YamlKeysOnBlackboard.java | 6 +- .../util/yoml/tests/MockYomlTypeRegistry.java | 9 +- .../util/yoml/tests/YomlAnnotationTests.java | 4 +- .../util/yoml/tests/YomlConfigTests.java | 115 +++++++++ .../util/yoml/tests/YomlTestFixture.java | 2 +- 31 files changed, 1165 insertions(+), 166 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitConfigKeySerializer.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigTests.java diff --git a/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java b/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java index d37666e820..c14ad57a7c 100644 --- a/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java +++ b/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java @@ -27,6 +27,7 @@ import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; public interface RegisteredTypeLoadingContext { @@ -47,4 +48,9 @@ public interface RegisteredTypeLoadingContext { /** A loader to use, supplying additional search paths */ @Nullable public BrooklynClassLoadingContext getLoader(); + /** Optional instructions on how to invoke the constructor, + * for use when a caller needs to specify a special constructor or factory method + * and/or specify parameters */ + @Nullable public ConstructionInstruction getConstructorInstruction(); + } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeLoadingContexts.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeLoadingContexts.java index 0368ba3c96..f922699629 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeLoadingContexts.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeLoadingContexts.java @@ -18,8 +18,7 @@ */ package org.apache.brooklyn.core.typereg; -import groovy.xml.Entity; - +import java.util.Arrays; import java.util.Set; import javax.annotation.Nonnull; @@ -34,11 +33,14 @@ import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableSet; +import groovy.xml.Entity; + public class RegisteredTypeLoadingContexts { private static final Logger log = LoggerFactory.getLogger(RegisteredTypeLoadingContexts.class); @@ -48,7 +50,8 @@ public final static class BasicRegisteredTypeLoadingContext implements Registere @Nullable private RegisteredTypeKind kind; @Nullable private Class expectedSuperType; @Nonnull private Set encounteredTypes = ImmutableSet.of(); - @Nullable BrooklynClassLoadingContext loader; + @Nullable private BrooklynClassLoadingContext loader; + @Nullable private ConstructionInstruction constructorInstruction; private BasicRegisteredTypeLoadingContext() {} @@ -57,8 +60,9 @@ public BasicRegisteredTypeLoadingContext(@Nullable RegisteredTypeLoadingContext this.kind = source.getExpectedKind(); this.expectedSuperType = source.getExpectedJavaSuperType(); - this.encounteredTypes = source.getAlreadyEncounteredTypes(); + this.encounteredTypes = MutableSet.copyOf(source.getAlreadyEncounteredTypes()); this.loader = source.getLoader(); + this.constructorInstruction = source.getConstructorInstruction(); } @Override @@ -83,6 +87,11 @@ public BrooklynClassLoadingContext getLoader() { return loader; } + @Override + public ConstructionInstruction getConstructorInstruction() { + return constructorInstruction; + } + @Override public String toString() { return JavaClassNames.cleanSimpleClassName(this)+"["+kind+","+expectedSuperType+","+encounteredTypes+"]"; @@ -222,15 +231,29 @@ static Class expectedSuperType) { result.expectedSuperType = expectedSuperType; return this; } + public Builder addEncounteredTypes(String... encounteredTypes) { result.encounteredTypes.addAll(Arrays.asList(encounteredTypes)); return this; } + public Builder loader(BrooklynClassLoadingContext loader) { result.loader = loader; return this; } + public Builder constructorInstruction(ConstructionInstruction constructorInstruction) { result.constructorInstruction = constructorInstruction; return this; } + + public RegisteredTypeLoadingContext build() { return new BasicRegisteredTypeLoadingContext(result); } + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionMerger.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionMerger.java index c9baac00f2..62e66b22ac 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionMerger.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/CollectionMerger.java @@ -227,8 +227,8 @@ public void recordVisit(Object o) { protected boolean isTrivial(Object o) { if (o == null) return true; - if (o instanceof Map && ((Map)o).isEmpty()) return true; - if (o instanceof Iterable && Iterables.isEmpty(((Iterable)o))) return true; + if (o instanceof Map && ((Map)o).isEmpty()) return true; + if (o instanceof Iterable && Iterables.isEmpty(((Iterable)o))) return true; Class clazz = o.getClass(); return clazz.isEnum() || clazz.isPrimitive() || TRIVIAL_CLASSES.contains(clazz); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableList.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableList.java index 6bdc3e09e6..fee30efe17 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableList.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableList.java @@ -48,7 +48,7 @@ public static MutableList of(V v1) { return result; } - public static MutableList of(V v1, V v2, V ...vv) { + public static MutableList of(V v1, V v2, @SuppressWarnings("unchecked") V ...vv) { MutableList result = new MutableList(); result.add(v1); result.add(v2); @@ -129,7 +129,7 @@ public Builder add(V value) { return this; } - public Builder add(V value1, V value2, V ...values) { + public Builder add(V value1, V value2, @SuppressWarnings("unchecked") V ...values) { result.add(value1); result.add(value2); for (V v: values) result.add(v); @@ -195,7 +195,7 @@ public ImmutableList buildImmutable() { return ImmutableList.copyOf(result); } - public Builder addLists(Iterable ...items) { + public Builder addLists(@SuppressWarnings("unchecked") Iterable ...items) { for (Iterable item: items) { addAll(item); } @@ -216,7 +216,7 @@ public MutableList appendIfNotNull(V item) { } /** as {@link List#add(Object)} but accepting multiple, and fluent style */ - public MutableList append(V item1, V item2, V ...items) { + public MutableList append(V item1, V item2, @SuppressWarnings("unchecked") V ...items) { add(item1); add(item2); for (V item: items) add(item); @@ -224,7 +224,7 @@ public MutableList append(V item1, V item2, V ...items) { } /** as {@link List#add(Object)} but excluding nulls, accepting multiple, and fluent style */ - public MutableList appendIfNotNull(V item1, V item2, V ...items) { + public MutableList appendIfNotNull(V item1, V item2, @SuppressWarnings("unchecked") V ...items) { if (item1!=null) add(item1); if (item2!=null) add(item2); for (V item: items) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableMap.java index 71f9aa8c13..0cf5b261dd 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/collections/MutableMap.java @@ -197,11 +197,25 @@ public Builder put(Entry entry) { return this; } + public Builder putIfAbsent(Entry entry) { + if (!result.containsKey(entry.getKey())) result.put(entry.getKey(), entry.getValue()); + return this; + } + public Builder putAll(Map map) { result.add(map); return this; } + public Builder putAllAbsent(Map map) { + if (map!=null) { + for (Map.Entry entry: map.entrySet()) { + putIfAbsent(entry); + } + } + return this; + } + public Builder remove(K key) { result.remove(key); return this; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/ReflectionPredicates.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/ReflectionPredicates.java index 33952964e7..6006b74517 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/ReflectionPredicates.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/ReflectionPredicates.java @@ -30,23 +30,28 @@ public class ReflectionPredicates { public static Predicate MODIFIERS_PRIVATE = new ModifiersPrivate(); private static class ModifiersPrivate implements Predicate { @Override public boolean apply(Integer modifiers) { return Modifier.isPrivate(modifiers); } + @Override public String toString() { return "private"; } } public static Predicate MODIFIERS_PUBLIC = new ModifiersPublic(); private static class ModifiersPublic implements Predicate { @Override public boolean apply(Integer modifiers) { return Modifier.isPublic(modifiers); } + @Override public String toString() { return "public"; } } public static Predicate MODIFIERS_PROTECTED = new ModifiersProtected(); private static class ModifiersProtected implements Predicate { @Override public boolean apply(Integer modifiers) { return Modifier.isProtected(modifiers); } + @Override public String toString() { return "protected"; } } public static Predicate MODIFIERS_TRANSIENT = new ModifiersTransient(); private static class ModifiersTransient implements Predicate { @Override public boolean apply(Integer modifiers) { return Modifier.isTransient(modifiers); } + @Override public String toString() { return "transient"; } } public static Predicate MODIFIERS_STATIC = new ModifiersStatic(); private static class ModifiersStatic implements Predicate { @Override public boolean apply(Integer modifiers) { return Modifier.isStatic(modifiers); } + @Override public String toString() { return "static"; } } public static Predicate fieldModifiers(Predicate modifiersCheck) { return new FieldModifiers(modifiersCheck); } @@ -54,6 +59,7 @@ private static class FieldModifiers implements Predicate { private Predicate modifiersCheck; private FieldModifiers(Predicate modifiersCheck) { this.modifiersCheck = modifiersCheck; } @Override public boolean apply(Field f) { return modifiersCheck.apply(f.getModifiers()); } + @Override public String toString() { return "modifiers["+modifiersCheck+"]"; } } public static Predicate IS_FIELD_PUBLIC = fieldModifiers(MODIFIERS_PUBLIC); public static Predicate IS_FIELD_TRANSIENT = fieldModifiers(MODIFIERS_TRANSIENT); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java index 95bbf7fa6a..66cb327186 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java @@ -328,7 +328,9 @@ public static Maybe invokeConstructorFromArgs(Reflections reflections, Cl return Maybe.of((T) reflections.loadInstance(constructor, argsArray)); } } - return Maybe.absent("Constructor not found"); + if (argsArray==null || argsArray.length==0) + return Maybe.absent("No no-arg constructor availble for "+clazz); + return Maybe.absent("No matching constructor availble for "+clazz+", parameters "+Arrays.asList(argsArray)); } @@ -637,6 +639,41 @@ public static Method findMethod(Class clazz, String name, Class... paramet throw toThrowIfFails; } + /** Combination of {@link Class#getConstructors()} and {@link Class#getDeclaredConstructors()} returning a list with correct generics */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static List> getConstructors(Class clazz) { + return (List) MutableList.copyOf(Arrays.asList(clazz.getConstructors())).appendAll(Arrays.asList(clazz.getDeclaredConstructors())); + } + /** Returns any constructor exactly matching the given signature, including privates and on parent classes. */ + public static Maybe> findConstructorMaybe(Class clazz, Class... parameterTypes) { + if (clazz == null) return Maybe.absentNoTrace("class is null"); + Iterable> result = findConstructors(false, clazz, parameterTypes); + if (!result.iterator().hasNext()) return Maybe.absentNoTrace("no Constructors matching "+clazz.getName()+"("+Arrays.asList(parameterTypes)+")"); + return Maybe.of(result.iterator().next()); + } + /** Returns all constructors compatible with the given argument types, including privates and on parent classes and where the Constructor takes a supertype. */ + public static Iterable> findConstructorsCompatible(Class clazz, Class... parameterTypes) { + return findConstructors(true, clazz, parameterTypes); + } + private static Iterable> findConstructors(boolean allowCovariantParameterClasses, Class clazz, Class... parameterTypes) { + if (clazz == null) { + return Collections.emptySet(); + } + List> result = MutableList.of(); + + for (Constructor m: getConstructors(clazz)) { + if (m.getParameterTypes().length!=parameterTypes.length) continue; + parameters: for (int i=0; i @@ -728,7 +765,11 @@ public static List findFields(final Class clazz, Predicate filt if (!visited.add(nextclazz)) { continue; // already visited } + if (nextclazz.getSuperclass() != null) tovisit.add(nextclazz.getSuperclass()); + + // interfaces are only necessary for statics ... but the caller might be interested in that + // (could have a new method which returns non-statics only to optimize this) tovisit.addAll(Arrays.asList(nextclazz.getInterfaces())); result.addAll(Iterables.filter(Arrays.asList(nextclazz.getDeclaredFields()), diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java index 1cda234788..64184e630f 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java @@ -52,11 +52,7 @@ public static Yoml newInstance(YomlTypeRegistry typeRegistry) { } private static Yoml newInstance(YomlTypeRegistry typeRegistry, List serializers) { - YomlConfig config = new YomlConfig(); - config.typeRegistry = typeRegistry; - config.serializersPost.addAll(serializers); - - return new Yoml(config); + return new Yoml(YomlConfig.Builder.builder().typeRegistry(typeRegistry).serializersPost(serializers).build()); } public YomlConfig getConfig() { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContext.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContext.java index 393ab81a51..840503d1d9 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContext.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContext.java @@ -38,7 +38,7 @@ public abstract class YomlContext { String phaseCurrent = null; int phaseCurrentStep = -1; - Set phasesFollowing = MutableSet.of(StandardPhases.MANIPULATING, StandardPhases.HANDLING_TYPE, StandardPhases.HANDLING_FIELDS); + Set phasesFollowing = MutableSet.of(StandardPhases.MANIPULATING, StandardPhases.HANDLING_TYPE, StandardPhases.HANDLING_TYPE, StandardPhases.HANDLING_FIELDS); List phasesPreceding = MutableList.of(); public static interface StandardPhases { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java index 04873a29ab..5fd984205a 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java @@ -30,15 +30,16 @@ import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.internal.YomlUtils; import org.apache.brooklyn.util.yoml.serializers.ExplicitField; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; public class YomlAnnotations { - public static Set findTypeNamesFromAnnotations(Class type, String optionalDefaultPreferredName, boolean includeJavaTypeNameEvenIfOthers) { + public static Set findTypeNamesFromAnnotations(Class type, String optionalDefaultPreferredTypeName, boolean includeJavaTypeNameEvenIfOthers) { MutableSet names = MutableSet.of(); Alias overallAlias = type.getAnnotation(Alias.class); - if (optionalDefaultPreferredName!=null) { - names.addIfNotNull(optionalDefaultPreferredName); + if (optionalDefaultPreferredTypeName!=null) { + names.addIfNotNull(optionalDefaultPreferredTypeName); } if (overallAlias!=null) { if (Strings.isNonBlank(overallAlias.preferred())) { @@ -63,8 +64,23 @@ public static List findExplicitFieldSerializers(Class t, boole return result; } - public static Set findSerializerAnnotations(Class type) { + public static Set findConfigBagSerializerAnnotations(Class type, + String fieldNameForConfigToAutodetect, String keyNameForConfigWhenSerialized) { + + // TODO autodetect, add map + + return MutableSet.copyOf(InstantiateTypeFromRegistryUsingConfigMap.newConfigKeySerializersForType( + fieldNameForConfigToAutodetect, keyNameForConfigWhenSerialized, type)); + } + + public static Set findSerializerAnnotations(Class type, + String fieldNamesForConfigToAutodetect, String keyNameForConfigWhenSerialized) { + Set result = MutableSet.of(); + + if (fieldNamesForConfigToAutodetect!=null) { + result.addAll(findConfigBagSerializerAnnotations(type, fieldNamesForConfigToAutodetect, keyNameForConfigWhenSerialized)); + } // explicit fields YomlAllFieldsAtTopLevel allFields = type.getAnnotation(YomlAllFieldsAtTopLevel.class); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java new file mode 100644 index 0000000000..27363ae945 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.internal; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.javalang.Reflections; + +/** Capture instructions on how an object should be created. + *

+ * This is used when we need information from an outer instance definition in order to construct the object. + * (The default pathway is to use a no-arg constructor and to apply the outer definitions to the instance. + * But that doesn't necessarily work if a specific constructor or static method is being expected.) + */ +public interface ConstructionInstruction { + + public Class getType(); + public List getArgs(); + public Maybe create(); + + public ConstructionInstruction getOuterInstruction(); + + public static class Factory { + + public static ConstructionInstruction newDefault(Class type, ConstructionInstruction optionalOuter) { + if (optionalOuter!=null) { + if (optionalOuter instanceof ConstructorWithArgsInstruction) + return newUsingConstructorWithArgs(type, null, optionalOuter); + } + return newUsingConstructorWithArgs(type, null, null); + } + + public static ConstructionInstruction newUsingConstructorWithArgs(Class type, List args, ConstructionInstruction optionalOuter) { + return new ConstructorWithArgsInstruction(type, args, (ConstructorWithArgsInstruction)optionalOuter); + } + + /** Merge arg lists, as follows: + * if either list is null, take the other; + * otherwise require them to be the same length. + *

+ * For each entry, if they are maps, merge deep preferring the latter's values when they are not maps. + * If they are anything else, we prefer the latter. + */ + public static List mergeArgumentLists(List olderArgs, List args) { + List newArgs; + if (olderArgs==null || olderArgs.isEmpty()) newArgs = MutableList.copyOf(args); + else if (args==null) newArgs = MutableList.copyOf(olderArgs); + else { + if (olderArgs.size() != args.size()) + throw new IllegalStateException("Incompatible arguments, sizes "+olderArgs.size()+" and "+args.size()+": "+olderArgs+" and "+args); + // merge + newArgs = MutableList.of(); + Iterator i1 = olderArgs.iterator(); + Iterator i2 = args.iterator(); + while (i2.hasNext()) { + Object o1 = i1.next(); + Object o2 = i2.next(); + if ((o2 instanceof Map) && (o1 instanceof Map)) { + o2 = mergeMapsDeep((Map)o1, (Map)o2); + } + newArgs.add(o2); + } + } + + return newArgs; + } + + public static Map mergeMapsDeep(Map o1, Map o2) { + MutableMap result = MutableMap.copyOf(o1); + if (o2!=null) { + for (Map.Entry e2: o2.entrySet()) { + Object v2 = e2.getValue(); + if (v2 instanceof Map) { + Object old = result.get(e2.getKey()); + if (old instanceof Map) { + v2 = mergeMapsDeep((Map)old, (Map)v2); + } + } + result.put(e2.getKey(), v2); + } + } + return result; + } + } + + public static class ConstructorWithArgsInstruction implements ConstructionInstruction { + private final Class type; + private final List args; + private final ConstructorWithArgsInstruction outerInstruction; + + protected ConstructorWithArgsInstruction(Class type, List args, @Nullable ConstructorWithArgsInstruction outerInstruction) { + this.type = type; + this.args = args; + this.outerInstruction = outerInstruction; + } + + @Override public Class getType() { return type; } + @Override public List getArgs() { return args; } + @Override public ConstructionInstruction getOuterInstruction() { return outerInstruction; } + + @Override + public Maybe create() { + return create(null, null); + } + + protected Maybe create(Class typeConstraintSoFar, List argsSoFar) { + if (typeConstraintSoFar==null) typeConstraintSoFar = type; + if (type!=null) { + if (typeConstraintSoFar==null || typeConstraintSoFar.isAssignableFrom(type)) typeConstraintSoFar = type; + else if (type.isAssignableFrom(typeConstraintSoFar)) { /* fine */ } + else { + throw new IllegalStateException("Incompatible expected types "+typeConstraintSoFar+" and "+type); + } + } + + List newArgs = Factory.mergeArgumentLists(argsSoFar, args); + + if (outerInstruction!=null) { + return outerInstruction.create(typeConstraintSoFar, newArgs); + } + + return Reflections.invokeConstructorFromArgsIncludingPrivate(typeConstraintSoFar, newArgs.toArray()); + } + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java index bed13dd46d..83cdaa3d24 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java @@ -53,20 +53,20 @@ public static SerializersOnBlackboard create(Map blackboard) { private List expectedTypeSerializers = MutableList.of(); private List postSerializers = MutableList.of(); - public void addInstantiatedTypeSerializers(Iterable newInstantiatedTypeSerializers) { - addNewSerializers(instantiatedTypeSerializers, newInstantiatedTypeSerializers); + public boolean addInstantiatedTypeSerializers(Iterable newInstantiatedTypeSerializers) { + return addNewSerializers(instantiatedTypeSerializers, newInstantiatedTypeSerializers); } - public void addExpectedTypeSerializers(Iterable newExpectedTypeSerializers) { - addNewSerializers(expectedTypeSerializers, newExpectedTypeSerializers); + public boolean addExpectedTypeSerializers(Iterable newExpectedTypeSerializers) { + return addNewSerializers(expectedTypeSerializers, newExpectedTypeSerializers); } - public void addPostSerializers(List newPostSerializers) { - addNewSerializers(postSerializers, newPostSerializers); + public boolean addPostSerializers(List newPostSerializers) { + return addNewSerializers(postSerializers, newPostSerializers); } - protected static void addNewSerializers(List addTo, Iterable elementsToAddIfNotPresent) { + protected static boolean addNewSerializers(List addTo, Iterable elementsToAddIfNotPresent) { MutableSet newOnes = MutableSet.copyOf(elementsToAddIfNotPresent); newOnes.removeAll(addTo); - addTo.addAll(newOnes); + return addTo.addAll(newOnes); } public Iterable getSerializers() { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfig.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfig.java index 9d228b08ef..4dd2140619 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfig.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfig.java @@ -26,19 +26,59 @@ import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.YomlTypeRegistry; -public class YomlConfig { - - public YomlTypeRegistry typeRegistry; - public TypeCoercer coercer = TypeCoercerExtensible.newDefault(); - - public List serializersPost = MutableList.of(); - - public YomlTypeRegistry getTypeRegistry() { - return typeRegistry; +import com.google.common.collect.ImmutableList; + +public interface YomlConfig { + + public YomlTypeRegistry getTypeRegistry(); + public TypeCoercer getCoercer(); + public List getSerializersPost(); + public ConstructionInstruction getConstructionInstruction(); + + public static class BasicYomlConfig implements YomlConfig { + private BasicYomlConfig() {} + private BasicYomlConfig(YomlConfig original) { + this.typeRegistry = original.getTypeRegistry(); + this.coercer = original.getCoercer(); + this.serializersPost = original.getSerializersPost(); + this.constructionInstruction = original.getConstructionInstruction(); + } + + private YomlTypeRegistry typeRegistry; + private TypeCoercer coercer = TypeCoercerExtensible.newDefault(); + private List serializersPost = MutableList.of(); + private ConstructionInstruction constructionInstruction; + + public YomlTypeRegistry getTypeRegistry() { + return typeRegistry; + } + + public TypeCoercer getCoercer() { + return coercer; + } + + public List getSerializersPost() { + return ImmutableList.copyOf(serializersPost); + } + + public ConstructionInstruction getConstructionInstruction() { + return constructionInstruction; + } } - public TypeCoercer getCoercer() { - return coercer; + + public static class Builder { + public static Builder builder() { return new Builder(); } + public static Builder builder(YomlConfig source) { return new Builder(source); } + + final BasicYomlConfig result; + protected Builder() { result = new BasicYomlConfig(); } + protected Builder(YomlConfig source) { result = new BasicYomlConfig(source); } + public Builder typeRegistry(YomlTypeRegistry tr) { result.typeRegistry = tr; return this; } + public Builder coercer(TypeCoercer x) { result.coercer = x; return this; } + public Builder serializersPost(List x) { result.serializersPost = x; return this; } + public Builder constructionInstruction(ConstructionInstruction x) { result.constructionInstruction = x; return this; } + + public YomlConfig build() { return new BasicYomlConfig(result); } } - } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java index 5c097a627d..0bd899c5e8 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java @@ -26,7 +26,9 @@ import org.apache.brooklyn.util.yoml.YomlContextForWrite; import org.apache.brooklyn.util.yoml.YomlRequirement; import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.serializers.JavaFieldsOnBlackboard; import org.apache.brooklyn.util.yoml.serializers.ReadingTypeOnBlackboard; +import org.apache.brooklyn.util.yoml.serializers.YamlKeysOnBlackboard; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +37,6 @@ public class YomlConverter { private static final Logger log = LoggerFactory.getLogger(YomlConverter.class); - private final YomlConfig config; public YomlConverter(YomlConfig config) { @@ -66,9 +67,9 @@ protected void loopOverSerializers(YomlContext context) { // find the serializers known so far; store on blackboard so they could be edited SerializersOnBlackboard serializers = SerializersOnBlackboard.create(blackboard); if (context.getExpectedType()!=null) { - serializers.addExpectedTypeSerializers(config.typeRegistry.getSerializersForType(context.getExpectedType())); + serializers.addExpectedTypeSerializers(config.getTypeRegistry().getSerializersForType(context.getExpectedType())); } - serializers.addPostSerializers(config.serializersPost); + serializers.addPostSerializers(config.getSerializersPost()); if (context instanceof YomlContextForRead) { // read needs instantiated so that these errors display before manipulating errors and others @@ -77,6 +78,7 @@ protected void loopOverSerializers(YomlContext context) { if (log.isTraceEnabled()) log.trace("YOML now looking at "+context.getJsonPath()+"/ = "+context.getJavaObject()+" <-> "+context.getYamlObject()+" ("+context.getExpectedType()+")"); while (context.phaseAdvance()) { + if (log.isTraceEnabled()) log.trace("read "+context.getJsonPath()+"/ = "+context.getJavaObject()+" entering phase "+context.phaseCurrent()+": "+YamlKeysOnBlackboard.peek(blackboard)+" / "+JavaFieldsOnBlackboard.peek(blackboard)+" / "+JavaFieldsOnBlackboard.peek(blackboard, "config")); while (context.phaseStepAdvance() Map getAllNonTransientNonStaticFields(Class type, T optionalInstanceToRequireNonNullFieldValue) { + return getAllFields(type, optionalInstanceToRequireNonNullFieldValue, + Predicates.and(ReflectionPredicates.IS_FIELD_NON_TRANSIENT, ReflectionPredicates.IS_FIELD_NON_STATIC)); + } + public static Map getAllNonTransientStaticFields(Class type) { + return getAllFields(type, null, + Predicates.and(ReflectionPredicates.IS_FIELD_NON_TRANSIENT, ReflectionPredicates.IS_FIELD_STATIC)); + } + + /** Finds all fields on a type, including inherited, including statics from interfaces, subject to the optional filter, + * and optionally requiring a non-null value for the field on a given instant. + * These are ordered in {@link FieldOrderings#ALPHABETICAL_FIELD_THEN_SUB_BEST_FIRST} order, + * with shadowed fields prefixed by the name of the superclass and ".". + * + * @param type Class to scan + * @param optionalInstanceToRequireNonNullFieldValue An instance, which if supplied, is used to exclude + * fields for which this instance has a null value + * @param filter Filter to apply on fields + * @return + */ + public static Map getAllFields(Class type, @Nullable T optionalInstanceToRequireNonNullFieldValue, @Nullable Predicate filter) { Map result = MutableMap.of(); List fields = Reflections.findFields(type, null, FieldOrderings.ALPHABETICAL_FIELD_THEN_SUB_BEST_FIRST); Field lastF = null; for (Field f: fields) { - if (ReflectionPredicates.IS_FIELD_NON_TRANSIENT.apply(f) && ReflectionPredicates.IS_FIELD_NON_STATIC.apply(f)) { + if (filter==null || filter.apply(f)) { if (optionalInstanceToRequireNonNullFieldValue==null || Reflections.getFieldValueMaybe(optionalInstanceToRequireNonNullFieldValue, f).isPresentAndNonNull()) { String name = f.getName(); if (lastF!=null && lastF.getName().equals(f.getName())) { // if field is shadowed use FQN - name = f.getDeclaringClass().getCanonicalName()+"."+name; + String fqn = f.getDeclaringClass().getCanonicalName(); + if (Strings.isBlank(fqn)) fqn = f.getDeclaringClass().getName(); + name = fqn+"."+name; } result.put(name, f); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java new file mode 100644 index 0000000000..da1682d988 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.serializers; + +import java.util.Map; + +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yoml.YomlContext; + +public class ConfigInMapUnderConfigSerializer extends FieldsInMapUnderFields { + + String keyNameForConfigWhenSerialized; + + public ConfigInMapUnderConfigSerializer(String keyNameForConfigWhenSerialized) { + this.keyNameForConfigWhenSerialized = keyNameForConfigWhenSerialized; + } + + protected YomlSerializerWorker newWorker() { + return new Worker(); + } + + @Override + protected String getExpectedPhaseRead() { + return YomlContext.StandardPhases.MANIPULATING; + } + + @Override + protected String getKeyNameForMapOfGeneralValues() { + return keyNameForConfigWhenSerialized; + } + + public class Worker extends FieldsInMapUnderFields.Worker { + + @Override + public void read() { + if (!context.willDoPhase( + InstantiateTypeFromRegistryUsingConfigMap.PHASE_INSTANTIATE_TYPE_DEFERRED)) return; + if (JavaFieldsOnBlackboard.peek(blackboard, "config")==null) return; + + super.read(); + } + + protected boolean shouldHaveJavaObject() { return false; } + + @Override + protected boolean setKeyValueForJavaObjectOnRead(Object key, Object value) throws IllegalAccessException { + JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard, "config"); + fib.fieldsFromReadToConstructJava.put(Strings.toString(key), value); + return true; + } + + protected Map writePrepareGeneralMap() { + JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); + if (fib==null) return null; + return fib.configToWriteFromJava; + } + + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitConfigKeySerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitConfigKeySerializer.java new file mode 100644 index 0000000000..d504511b54 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitConfigKeySerializer.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.serializers; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.config.ConfigKey.HasConfigKey; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ExplicitConfigKeySerializer extends ExplicitField { + + private static final Logger log = LoggerFactory.getLogger(ExplicitConfigKeySerializer.class); + + String keyNameForConfigWhenSerialized = null; + + public ExplicitConfigKeySerializer(String keyNameForConfigWhenSerialized, ConfigKey configKey, Field optionalFieldForAnnotations) { + super(configKey.getName(), optionalFieldForAnnotations); + this.keyNameForConfigWhenSerialized = keyNameForConfigWhenSerialized; + this.configKey = configKey; + if (configKey.hasDefaultValue()) { + defaultValue = configKey.getDefaultValue(); + } + + // TODO yoml config key: + // - constraints + // - description + } + + /** The {@link ConfigKey#getName()} this serializer acts on */ + protected final ConfigKey configKey; + + @Override + protected String getKeyNameForMapOfGeneralValues() { + return keyNameForConfigWhenSerialized; + } + + public static Set> findConfigKeys(Class clazz) { + MutableMap> result = MutableMap.of(); + + for (Field f: YomlUtils.getAllNonTransientStaticFields(clazz).values()) { + try { + f.setAccessible(true); + Object ckO = f.get(null); + + ConfigKey ck = null; + if (ckO instanceof ConfigKey) ck = (ConfigKey)ckO; + else if (ckO instanceof HasConfigKey) ck = ((HasConfigKey)ckO).getConfigKey(); + + if (ck==null) continue; + if (result.containsKey(ck.getName())) continue; + + result.put(ck.getName(), ck); + + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.warn("Unable to access static config key "+f+" (ignoring): "+e, e); + } + } + + return MutableSet.copyOf(result.values()); + } + + /** only useful in conjuction with {@link InstantiateTypeFromRegistryUsingConfigMap} static serializer factory methods */ + public static Map findExplicitConfigKeySerializers(String keyNameForConfigWhenSerialized, Class clazz) { + MutableMap result = MutableMap.of(); + + for (Field f: YomlUtils.getAllNonTransientStaticFields(clazz).values()) { + try { + f.setAccessible(true); + Object ckO = f.get(null); + + ConfigKey ck = null; + if (ckO instanceof ConfigKey) ck = (ConfigKey)ckO; + else if (ckO instanceof HasConfigKey) ck = ((HasConfigKey)ckO).getConfigKey(); + + if (ck==null) continue; + if (result.containsKey(ck.getName())) continue; + + result.put(ck.getName(), new ExplicitConfigKeySerializer(keyNameForConfigWhenSerialized, ck, f)); + + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.warn("Unable to access static config key "+f+" (ignoring): "+e, e); + } + + } + + return result; + } + + protected YomlSerializerWorker newWorker() { + return new Worker(); + } + + public class Worker extends ExplicitField.Worker { + protected boolean canDoRead() { + return !hasJavaObject() && context.willDoPhase(InstantiateTypeFromRegistryUsingConfigMap.PHASE_INSTANTIATE_TYPE_DEFERRED); + } + } + + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java index c542e37ffe..0ac7116ea0 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java @@ -34,6 +34,8 @@ import org.apache.brooklyn.util.yoml.YomlContext.StandardPhases; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.common.base.Objects; @@ -47,6 +49,8 @@ @Alias("explicit-field") public class ExplicitField extends YomlSerializerComposition { + private static final Logger log = LoggerFactory.getLogger(ExplicitField.class); + public ExplicitField() {} public ExplicitField(Field f) { this(f.getName(), f); @@ -90,45 +94,54 @@ protected YomlSerializerWorker newWorker() { /** by default when multiple explicit-field serializers are supplied for the same {@link #fieldName}, all aliases are accepted; * set this false to restrict to those in the first such serializer */ - Boolean aliasesInherited; + protected Boolean aliasesInherited; /** by default aliases are taken case-insensitive, with mangling supported, * and including the {@link #fieldName} as an alias; * set false to disallow all these, recognising only the explicitly noted * {@link #keyName} and {@link #aliases} as keys (but still defaulting to {@link #fieldName} if {@link #keyName} is absent) */ - Boolean aliasesStrict; + protected Boolean aliasesStrict; public static enum FieldConstraint { REQUIRED } /** by default fields can be left null; set {@link FieldConstraint#REQUIRED} to require a value to be supplied (or a default set); * other constraints may be introduded, and API may change, but keyword `required` will be coercible to this */ - FieldConstraint constraint; + protected FieldConstraint constraint; /** a default value to use when reading (and to use to determine whether to omit the field when writing) */ // TODO would be nice to support maybe here, not hard here, but it makes it hard to set from yaml // also keyword `default` as alias - Object defaultValue; + protected Object defaultValue; + + protected String getKeyNameForMapOfGeneralValues() { + return FieldsInMapUnderFields.KEY_NAME_FOR_MAP_OF_FIELD_VALUES; + } public class Worker extends YomlSerializerWorker { String PREPARING_EXPLICIT_FIELDS = "preparing-explicit-fields"; protected String getPreferredKeyName() { - String result = ExplicitFieldsBlackboard.get(blackboard).getKeyName(fieldName); + String result = getExplicitFieldsBlackboard().getKeyName(fieldName); if (result!=null) return result; return fieldName; } + + protected ExplicitFieldsBlackboard getExplicitFieldsBlackboard() { + return ExplicitFieldsBlackboard.get(blackboard, getKeyNameForMapOfGeneralValues()); + } protected Iterable getKeyNameAndAliases() { MutableSet keyNameAndAliases = MutableSet.of(); keyNameAndAliases.addIfNotNull(getPreferredKeyName()); - if (!ExplicitFieldsBlackboard.get(blackboard).isAliasesStrict(fieldName)) { + if (!getExplicitFieldsBlackboard().isAliasesStrict(fieldName)) { keyNameAndAliases.addIfNotNull(fieldName); } - keyNameAndAliases.addAll(ExplicitFieldsBlackboard.get(blackboard).getAliases(fieldName)); + keyNameAndAliases.addAll(getExplicitFieldsBlackboard().getAliases(fieldName)); return keyNameAndAliases; } protected boolean readyForMainEvent() { if (!context.seenPhase(YomlContext.StandardPhases.HANDLING_TYPE)) return false; + if (context.willDoPhase(YomlContext.StandardPhases.HANDLING_TYPE)) return false; if (!context.seenPhase(PREPARING_EXPLICIT_FIELDS)) { if (context.isPhase(YomlContext.StandardPhases.MANIPULATING)) { // interrupt the manipulating phase to do a preparing phase @@ -139,44 +152,49 @@ protected boolean readyForMainEvent() { } if (context.isPhase(PREPARING_EXPLICIT_FIELDS)) { // do the pre-main pass to determine what is required for explicit fields and what the default is - ExplicitFieldsBlackboard.get(blackboard).setKeyNameIfUnset(fieldName, keyName); - ExplicitFieldsBlackboard.get(blackboard).addAliasIfNotDisinherited(fieldName, alias); - ExplicitFieldsBlackboard.get(blackboard).addAliasesIfNotDisinherited(fieldName, aliases); - ExplicitFieldsBlackboard.get(blackboard).setAliasesInheritedIfUnset(fieldName, aliasesInherited); - ExplicitFieldsBlackboard.get(blackboard).setAliasesStrictIfUnset(fieldName, aliasesStrict); - ExplicitFieldsBlackboard.get(blackboard).setConstraintIfUnset(fieldName, constraint); - if (ExplicitFieldsBlackboard.get(blackboard).getDefault(fieldName).isAbsent() && defaultValue!=null) { - ExplicitFieldsBlackboard.get(blackboard).setUseDefaultFrom(fieldName, ExplicitField.this, defaultValue); + getExplicitFieldsBlackboard().setKeyNameIfUnset(fieldName, keyName); + getExplicitFieldsBlackboard().addAliasIfNotDisinherited(fieldName, alias); + getExplicitFieldsBlackboard().addAliasesIfNotDisinherited(fieldName, aliases); + getExplicitFieldsBlackboard().setAliasesInheritedIfUnset(fieldName, aliasesInherited); + getExplicitFieldsBlackboard().setAliasesStrictIfUnset(fieldName, aliasesStrict); + getExplicitFieldsBlackboard().setConstraintIfUnset(fieldName, constraint); + if (getExplicitFieldsBlackboard().getDefault(fieldName).isAbsent() && defaultValue!=null) { + getExplicitFieldsBlackboard().setUseDefaultFrom(fieldName, ExplicitField.this, defaultValue); } // TODO combine aliases, other items return false; } - if (ExplicitFieldsBlackboard.get(blackboard).isFieldDone(fieldName)) return false; + if (getExplicitFieldsBlackboard().isFieldDone(fieldName)) return false; if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return false; return true; } + protected boolean canDoRead() { return hasJavaObject(); } + public void read() { - if (!readyForMainEvent()) return; - if (!hasJavaObject()) return; + if (!readyForMainEvent()) return; + if (!canDoRead()) return; if (!isYamlMap()) return; if (!hasYamlKeysOnBlackboard()) return; @SuppressWarnings("unchecked") - Map fields = peekFromYamlKeysOnBlackboard("fields", Map.class).orNull(); + Map fields = peekFromYamlKeysOnBlackboard(getKeyNameForMapOfGeneralValues(), Map.class).orNull(); if (fields==null) { // create the fields if needed; FieldsInFieldsMap will remove (even if empty) fields = MutableMap.of(); - YamlKeysOnBlackboard.getOrCreate(blackboard, null).yamlKeysToReadToJava.put("fields", fields); + YamlKeysOnBlackboard.getOrCreate(blackboard, null).yamlKeysToReadToJava.put(getKeyNameForMapOfGeneralValues(), fields); } int keysMatched = 0; for (String aliasO: getKeyNameAndAliases()) { - Set aliasMangles = ExplicitFieldsBlackboard.get(blackboard).isAliasesStrict(fieldName) ? + Set aliasMangles = getExplicitFieldsBlackboard().isAliasesStrict(fieldName) ? Collections.singleton(aliasO) : findAllKeyManglesYamlKeys(aliasO); for (String alias: aliasMangles) { Maybe value = peekFromYamlKeysOnBlackboard(alias, Object.class); if (value.isAbsent()) continue; + if (log.isTraceEnabled()) { + log.trace(ExplicitField.this+": found "+alias+" for "+fieldName); + } boolean fieldAlreadyKnown = fields.containsKey(fieldName); if (value.isPresent() && fieldAlreadyKnown) { // already present @@ -193,7 +211,7 @@ public void read() { } if (keysMatched==0) { // set a default if there is one - Maybe value = ExplicitFieldsBlackboard.get(blackboard).getDefault(fieldName); + Maybe value = getExplicitFieldsBlackboard().getDefault(fieldName); if (value.isPresentAndNonNull()) { fields.put(fieldName, value.get()); keysMatched++; @@ -201,7 +219,7 @@ public void read() { } if (keysMatched>0) { // repeat the preparing phase if we set any keys, so that remapping can apply - ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); + getExplicitFieldsBlackboard().setFieldDone(fieldName); context.phaseInsert(StandardPhases.MANIPULATING); } } @@ -211,22 +229,22 @@ public void write() { if (!isYamlMap()) return; @SuppressWarnings("unchecked") - Map fields = getFromYamlMap("fields", Map.class).orNull(); + Map fields = getFromYamlMap(getKeyNameForMapOfGeneralValues(), Map.class).orNull(); /* - * if fields is null either we are too early (not yet set by instantiate-type) + * if fields is null either we are too early (not yet set by instantiate-type / FieldsInMapUnderFields) * or too late (already read in to java), so we bail -- this yaml key cannot be handled at this time */ if (fields==null) return; - Maybe dv = ExplicitFieldsBlackboard.get(blackboard).getDefault(fieldName); + Maybe dv = getExplicitFieldsBlackboard().getDefault(fieldName); Maybe valueToSet; if (!fields.containsKey(fieldName)) { // field not present, so omit (if field is not required and no default, or if default value is present and null) // else write an explicit null - if ((dv.isPresent() && dv.isNull()) || (ExplicitFieldsBlackboard.get(blackboard).getConstraint(fieldName).orNull()!=FieldConstraint.REQUIRED && dv.isAbsent())) { + if ((dv.isPresent() && dv.isNull()) || (getExplicitFieldsBlackboard().getConstraint(fieldName).orNull()!=FieldConstraint.REQUIRED && dv.isAbsent())) { // if default is null, or if not required and no default, we can suppress - ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); + getExplicitFieldsBlackboard().setFieldDone(fieldName); return; } // default is non-null or field is required, so write the explicit null @@ -236,25 +254,29 @@ public void write() { valueToSet = Maybe.of(fields.remove(fieldName)); if (dv.isPresent() && Objects.equal(dv.get(), valueToSet.get())) { // suppress if it equals the default - ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); + getExplicitFieldsBlackboard().setFieldDone(fieldName); valueToSet = Maybe.absent(); } } if (valueToSet.isPresent()) { - ExplicitFieldsBlackboard.get(blackboard).setFieldDone(fieldName); + getExplicitFieldsBlackboard().setFieldDone(fieldName); Object oldValue = getYamlMap().put(getPreferredKeyName(), valueToSet.get()); if (oldValue!=null && !oldValue.equals(valueToSet.get())) { throw new IllegalStateException("Conflicting values for `"+getPreferredKeyName()+"`"); } // and move the `fields` object to the end - getYamlMap().remove("fields"); + getYamlMap().remove(getKeyNameForMapOfGeneralValues()); if (!fields.isEmpty()) - getYamlMap().put("fields", fields); + getYamlMap().put(getKeyNameForMapOfGeneralValues(), fields); // rerun this phase again, as we've changed it context.phaseInsert(StandardPhases.MANIPULATING); } } } - + + @Override + public String toString() { + return "explicit-field["+fieldName+"->"+keyName+":"+alias+"/"+aliases+"]"; + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldsBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldsBlackboard.java index 33b05da663..2444ec7926 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldsBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldsBlackboard.java @@ -37,12 +37,12 @@ public class ExplicitFieldsBlackboard implements YomlRequirement { public static final String KEY = ExplicitFieldsBlackboard.class.getCanonicalName(); - - public static ExplicitFieldsBlackboard get(Map blackboard) { - Object v = blackboard.get(KEY); + + public static ExplicitFieldsBlackboard get(Map blackboard, String mode) { + Object v = blackboard.get(KEY+":"+mode); if (v==null) { v = new ExplicitFieldsBlackboard(); - blackboard.put(KEY, v); + blackboard.put(KEY+":"+mode, v); } return (ExplicitFieldsBlackboard) v; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java index 2613fc51bc..95c898be3f 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java @@ -29,52 +29,79 @@ import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.YomlContext.StandardPhases; import org.apache.brooklyn.util.yoml.YomlContextForRead; import org.apache.brooklyn.util.yoml.YomlContextForWrite; import org.apache.brooklyn.util.yoml.internal.YomlUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class FieldsInMapUnderFields extends YomlSerializerComposition { + private static final Logger log = LoggerFactory.getLogger(FieldsInMapUnderFields.class); + + public static String KEY_NAME_FOR_MAP_OF_FIELD_VALUES = "fields"; + protected YomlSerializerWorker newWorker() { return new Worker(); } - public static class Worker extends YomlSerializerWorker { + protected String getKeyNameForMapOfGeneralValues() { + return KEY_NAME_FOR_MAP_OF_FIELD_VALUES; + } + + protected String getExpectedPhaseRead() { + return YomlContext.StandardPhases.HANDLING_FIELDS; + } + + public class Worker extends YomlSerializerWorker { + + protected boolean setKeyValueForJavaObjectOnRead(Object key, Object value) + throws IllegalAccessException { + Maybe ffm = Reflections.findFieldMaybe(getJavaObject().getClass(), Strings.toString(key)); + if (ffm.isAbsentOrNull()) { + // just skip (could throw, but leave it in case something else recognises it) + return false; + } else { + Field ff = ffm.get(); + if (Modifier.isStatic(ff.getModifiers())) { + // as above + return false; + } else { + String fieldType = YomlUtils.getFieldTypeName(ff, config); + Object v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, fieldType) ); + + ff.setAccessible(true); + ff.set(getJavaObject(), v2); + return true; + } + } + } + + protected boolean shouldHaveJavaObject() { return true; } + public void read() { - if (!context.isPhase(YomlContext.StandardPhases.HANDLING_FIELDS)) return; - if (!hasJavaObject()) return; + if (!context.isPhase(getExpectedPhaseRead())) return; + if (hasJavaObject() != shouldHaveJavaObject()) return; @SuppressWarnings("unchecked") - Map fields = peekFromYamlKeysOnBlackboard("fields", Map.class).orNull(); + Map fields = peekFromYamlKeysOnBlackboard(getKeyNameForMapOfGeneralValues(), Map.class).orNull(); if (fields==null) return; boolean changed = false; for (Object f: MutableList.copyOf( ((Map)fields).keySet() )) { Object v = ((Map)fields).get(f); try { - Maybe ffm = Reflections.findFieldMaybe(getJavaObject().getClass(), Strings.toString(f)); - if (ffm.isAbsentOrNull()) { - // just skip (could throw, but leave it in case something else recognises it?) - } else { - Field ff = ffm.get(); - if (Modifier.isStatic(ff.getModifiers())) { - // as above - } else { - String fieldType = YomlUtils.getFieldTypeName(ff, config); - Object v2 = converter.read( new YomlContextForRead(v, context.getJsonPath()+"/"+f, fieldType) ); - - ff.setAccessible(true); - ff.set(getJavaObject(), v2); - ((Map)fields).remove(Strings.toString(f)); - changed = true; - } + if (setKeyValueForJavaObjectOnRead(f, v)) { + ((Map)fields).remove(Strings.toString(f)); + changed = true; } } catch (Exception e) { throw Exceptions.propagate(e); } } if (changed) { if (((Map)fields).isEmpty()) { - removeFromYamlKeysOnBlackboard("fields"); + removeFromYamlKeysOnBlackboard(getKeyNameForMapOfGeneralValues()); } // restart (there is normally nothing after this so could equally continue with rerun) context.phaseRestart(); @@ -82,14 +109,23 @@ public void read() { } public void write() { - if (!context.isPhase(YomlContext.StandardPhases.HANDLING_FIELDS)) return; + if (!context.isPhase(StandardPhases.HANDLING_FIELDS)) return; if (!isYamlMap()) return; - if (getFromYamlMap("fields", Map.class).isPresent()) return; - JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); - if (fib==null || fib.fieldsToWriteFromJava.isEmpty()) return; + if (getFromYamlMap(getKeyNameForMapOfGeneralValues(), Map.class).isPresent()) return; + Map fields = writePrepareGeneralMap(); + if (fields!=null && !fields.isEmpty()) { + setInYamlMap(getKeyNameForMapOfGeneralValues(), fields); + // restart in case a serializer moves the `fields` map somewhere else + context.phaseRestart(); + } + } + + protected Map writePrepareGeneralMap() { + JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); + if (fib==null || fib.fieldsToWriteFromJava==null || fib.fieldsToWriteFromJava.isEmpty()) return null; Map fields = MutableMap.of(); - + for (String f: MutableList.copyOf(fib.fieldsToWriteFromJava)) { Maybe v = Reflections.getFieldValueMaybe(getJavaObject(), f); if (v.isPresent()) { @@ -104,12 +140,10 @@ public void write() { } } } - - if (!fields.isEmpty()) { - setInYamlMap("fields", fields); - // restart in case a serializer moves the `fields` map somewhere else - context.phaseRestart(); + if (log.isTraceEnabled()) { + log.trace(this+": built fields map "+fields); } + return fields; } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java index 6e15a81a88..8f2c64dfae 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java @@ -88,9 +88,14 @@ public void write() { Object result = ((Enum)getJavaObject()).name(); if (wrap) { - result = writingMapWithTypeAndLiteralValue( - config.getTypeRegistry().getTypeName(getJavaObject()), - result); + String typeName = config.getTypeRegistry().getTypeName(getJavaObject()); + if (addSerializersForDiscoveredRealType(typeName)) { + // if new serializers, bail out and we'll re-run + context.phaseRestart(); + return; + } + + result = writingMapWithTypeAndLiteralValue(typeName, result); } context.phaseInsert(YomlContext.StandardPhases.MANIPULATING); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java index e7b9ba7695..2f3cc3ad7b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java @@ -48,35 +48,42 @@ public void read() { } if (type==null) return; + if (addSerializersForDiscoveredRealType(type)) { + // added new serializers, need to restart phase + // in case another serializer wants to create it + context.phaseRestart(); + return; + } + + if (!readType(type)) return; + + if (isYamlMap()) { + removeFromYamlKeysOnBlackboard("type"); + } + } - Maybe resultM = config.getTypeRegistry().newInstanceMaybe((String)type, Yoml.newInstance(config)); + protected boolean readType(String type) { + Maybe resultM = config.getTypeRegistry().newInstanceMaybe(type, Yoml.newInstance(config)); if (resultM.isAbsent()) { String message = "Unable to create type '"+type+"'"; RuntimeException exc = null; - Maybe> jt = config.getTypeRegistry().getJavaTypeMaybe((String)type); + Maybe> jt = config.getTypeRegistry().getJavaTypeMaybe(type); if (jt.isAbsent()) { exc = ((Maybe.Absent)jt).getException(); } else { exc = ((Maybe.Absent)resultM).getException(); } warn(new IllegalStateException(message, exc)); - return; + return false; } - addSerializers(type); storeReadObjectAndAdvance(resultM.get(), true); - - if (isYamlMap()) { - removeFromYamlKeysOnBlackboard("type"); - } + return true; } - + public void write() { - if (!context.isPhase(YomlContext.StandardPhases.HANDLING_TYPE)) return; - if (hasYamlObject()) return; - if (!hasJavaObject()) return; - if (JavaFieldsOnBlackboard.isPresent(blackboard)) return; + if (!canDoWrite()) return; if (Reflections.hasSpecialSerializationMethods(getJavaObject().getClass())) { warn("Cannot write "+getJavaObject().getClass()+" using default strategy as it has custom serializaton methods"); @@ -84,20 +91,32 @@ public void write() { } // common primitives and maps/lists will have been handled - // TODO support osgi + + // (osgi syntax isn't supported, because we expect items to be in the registry) + + String typeName = getJavaObject().getClass().equals(getExpectedTypeJava()) ? null : config.getTypeRegistry().getTypeName(getJavaObject()); + if (addSerializersForDiscoveredRealType(typeName)) { + // if new serializers, bail out and we'll re-run + context.phaseRestart(); + return; + } MutableMap map = writingMapWithType( - // explicitly write the type unless it is the expected one - getJavaObject().getClass().equals(getExpectedTypeJava()) ? null : config.getTypeRegistry().getTypeName(getJavaObject())); + // explicitly write the type (unless it is the expected one) + typeName); - // collect fields - JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); - fib.fieldsToWriteFromJava.addAll(YomlUtils.getAllNonTransientNonStaticFieldNamesUntyped(getJavaObject().getClass(), getJavaObject())); + writingPopulateBlackboard(); context.phaseInsert(YomlContext.StandardPhases.HANDLING_FIELDS, YomlContext.StandardPhases.MANIPULATING); storeWriteObjectAndAdvance(map); return; } + protected void writingPopulateBlackboard() { + // collect fields + JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); + fib.fieldsToWriteFromJava.addAll(YomlUtils.getAllNonTransientNonStaticFieldNamesUntyped(getJavaObject().getClass(), getJavaObject())); + } + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java new file mode 100644 index 0000000000..d0fbe04133 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java @@ -0,0 +1,227 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.serializers; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.yoml.Yoml; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; +import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; +import org.apache.brooklyn.util.yoml.internal.YomlConfig; + +import com.google.common.base.Preconditions; + +public class InstantiateTypeFromRegistryUsingConfigMap extends InstantiateTypeFromRegistry { + + public static final String PHASE_INSTANTIATE_TYPE_DEFERRED = "handling-type-deferred-after-config"; + + String keyNameForConfigWhenSerialized = null; + String fieldNameForConfigInJavaIfPreset = null; + + // don't currently fully support inferring setup from annotations; we need the field above. + // easily could automate with a YomlConfigMap annotation - but for now make it explicit + // (for now this field can be used to load explicit config keys, if the field name is supplied) + boolean inferByScanning = false; + + public static Set newConfigKeyClassScanningSerializers(String fieldNameForConfigInJava, String keyNameForConfigWhenSerialized) { + Preconditions.checkNotNull(fieldNameForConfigInJava); + Preconditions.checkNotNull(keyNameForConfigWhenSerialized); + + InstantiateTypeFromRegistryUsingConfigMap instantiator = new InstantiateTypeFromRegistryUsingConfigMap(); + instantiator.keyNameForConfigWhenSerialized = keyNameForConfigWhenSerialized; + instantiator.fieldNameForConfigInJavaIfPreset = fieldNameForConfigInJava; + instantiator.inferByScanning = true; + + return MutableSet.of( + new ConfigInMapUnderConfigSerializer(keyNameForConfigWhenSerialized), + instantiator); + } + + public static Set newConfigKeySerializersForType( + String fieldNameForConfigInJava, String keyNameForConfigWhenSerialized, Class type) { + Preconditions.checkNotNull(fieldNameForConfigInJava); + Preconditions.checkNotNull(keyNameForConfigWhenSerialized); + + InstantiateTypeFromRegistryUsingConfigMap instantiator = new InstantiateTypeFromRegistryUsingConfigMap(); + instantiator.keyNameForConfigWhenSerialized = keyNameForConfigWhenSerialized; + instantiator.fieldNameForConfigInJavaIfPreset = fieldNameForConfigInJava; + instantiator.inferByScanning = false; + + MutableSet result = MutableSet.of( + new ConfigInMapUnderConfigSerializer(keyNameForConfigWhenSerialized), + instantiator); + result.addAll(ExplicitConfigKeySerializer.findExplicitConfigKeySerializers(keyNameForConfigWhenSerialized, type).values()); + + return result; + } + + protected InstantiateTypeFromRegistryUsingConfigMap() {} + + protected YomlSerializerWorker newWorker() { + return new Worker(); + } + + class Worker extends InstantiateTypeFromRegistry.Worker { + + @Override + public void read() { + if (context.isPhase(PHASE_INSTANTIATE_TYPE_DEFERRED)) { + readFinallyCreate(); + } else { + super.read(); + } + } + + @Override + protected boolean readType(String type) { + Class clazz = config.getTypeRegistry().getJavaTypeMaybe(type).orNull(); + if (!isConfigurable(clazz)) return false; + + // prepare blackboard, annotations, then do handling_config + JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.create(blackboard, keyNameForConfigWhenSerialized); + fib.typeNameFromReadToConstructJavaLater = type; + fib.typeFromReadToConstructJavaLater = clazz; + fib.fieldsFromReadToConstructJava = MutableMap.of(); + + addSerializersForDiscoveredRealType(type); + addExtraTypeSerializers(clazz); + + context.phaseInsert(YomlContext.StandardPhases.MANIPULATING, PHASE_INSTANTIATE_TYPE_DEFERRED); + context.phaseAdvance(); + return true; + } + + protected void addExtraTypeSerializers(Class clazz) { + if (inferByScanning) { + SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers( + ExplicitConfigKeySerializer.findExplicitConfigKeySerializers(keyNameForConfigWhenSerialized, clazz).values()); + } + } + + protected void readFinallyCreate() { + if (hasJavaObject()) return; + + // this is running in a later phase, after the brooklyn.config map has been set up + // instantiate with special constructor + JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard, keyNameForConfigWhenSerialized); + Class type = fib.typeFromReadToConstructJavaLater; + if (type==null) return; + + Preconditions.checkNotNull(keyNameForConfigWhenSerialized); + + YomlConfig newConfig = YomlConfig.Builder.builder(config).constructionInstruction( + ConstructionInstruction.Factory.newUsingConstructorWithArgs( + type, MutableList.of(fib.fieldsFromReadToConstructJava), config.getConstructionInstruction())) + .build(); + + Maybe resultM = config.getTypeRegistry().newInstanceMaybe(fib.typeNameFromReadToConstructJavaLater, Yoml.newInstance(newConfig)); + + if (resultM.isAbsent()) { + warn(new IllegalStateException("Unable to create type '"+type+"'", ((Maybe.Absent)resultM).getException())); + return; + } + + fib.fieldsFromReadToConstructJava.clear(); + storeReadObjectAndAdvance(resultM.get(), true); + } + + @Override + protected boolean canDoWrite() { + if (!super.canDoWrite()) return false; + if (!isConfigurable(getJavaObject().getClass())) return false; + if (!hasValidConfigFieldSoWeCanWriteConfigMap()) return false; + + return true; + } + + protected boolean hasValidConfigFieldSoWeCanWriteConfigMap() { + if (fieldNameForConfigInJavaIfPreset!=null) { + // check that the given field exists and is usable + Maybe f = Reflections.findFieldMaybe(getJavaObject().getClass(), fieldNameForConfigInJavaIfPreset); + if (f.isAbsent()) return false; + if (!Map.class.isAssignableFrom( f.get().getType() )) return false; + } + + // support autodetect here (will fail later if not discoverable; could discover here) + return true; + } + + @Override + protected void writingPopulateBlackboard() { + super.writingPopulateBlackboard(); + + try { + String configMapKeyName = fieldNameForConfigInJavaIfPreset; + if (configMapKeyName==null) { + if (!inferByScanning) { + throw new IllegalStateException("no config key name set and not allowed to infer; " + + "this serializer should only be used when the config key name is specified"); + } else { + // optionally: we could support annotation on the type to learn the key name; + // but without that we just write as fields + throw new UnsupportedOperationException("config key name must be set explicitly"); + } + } + // write clues for ConfigInMapUnder... + + JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); + Field f = Reflections.findFieldMaybe(getJavaObject().getClass(), fieldNameForConfigInJavaIfPreset).get(); + f.setAccessible(true); + @SuppressWarnings("unchecked") + Map configMap = (Map)f.get(getJavaObject()); + if (configMap!=null) { + fib.configToWriteFromJava = MutableMap.copyOf(configMap); + } + + // suppress wherever the config is stored + fib.fieldsToWriteFromJava.remove(configMapKeyName); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + warn(new IllegalStateException("Unable to retieve config map in "+getJavaObject(), e)); + return; + } + + addExtraTypeSerializers(getJavaObject().getClass()); + } + + } + + /** configurable if it has a map constructor and at least one public static config key */ + protected boolean isConfigurable(Class type) { + if (type==null) return false; + if (findConstructorMaybe(type).isAbsent()) return false; + if (ExplicitConfigKeySerializer.findConfigKeys(type).isEmpty()) return false; + return true; + } + + protected Maybe findConstructorMaybe(Class type) { + return Reflections.findConstructorMaybe(type, Map.class); + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java index b6ff4de9f4..34aeccfffe 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java @@ -94,10 +94,14 @@ public void write() { // not expecting a primitive/json; bail out if it's not a primitive (map/list might decide to write `json` as the type) if (!isJsonPrimitiveObject(getJavaObject())) return; - MutableMap map = writingMapWithTypeAndLiteralValue( - config.getTypeRegistry().getTypeName(getJavaObject()), - getJavaObject()); - + String typeName = config.getTypeRegistry().getTypeName(getJavaObject()); + if (addSerializersForDiscoveredRealType(typeName)) { + // if new serializers, bail out and we'll re-run + context.phaseRestart(); + return; + } + + MutableMap map = writingMapWithTypeAndLiteralValue(typeName, getJavaObject()); context.phaseInsert(YomlContext.StandardPhases.MANIPULATING); storeWriteObjectAndAdvance(map); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java index e0cebd7ff9..341d313a2c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Set; +import javax.annotation.Nullable; + import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; @@ -72,10 +74,17 @@ protected boolean canDoWrite() { return true; } - protected void addSerializers(String type) { - if (!type.equals(context.getExpectedType())) { - SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.typeRegistry.getSerializersForType(type)); + /** invoked on read and write to apply the appropriate serializers one the real type is known, + * e.g. by looking up in registry. name of type will not be null but if it equals the java type + * that may mean that annotation-scanning is appropriate. */ + protected boolean addSerializersForDiscoveredRealType(@Nullable String type) { + if (type!=null) { + // (if null, we were writing what was expected, and we'll have added from expected type serializers) + if (!type.equals(context.getExpectedType())) { + return SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.getTypeRegistry().getSerializersForType(type)); + } } + return false; } protected void storeReadObjectAndAdvance(Object result, boolean addPhases) { @@ -126,13 +135,13 @@ protected void removeTypeAndValueKeys() { removeFromYamlKeysOnBlackboard("type", "value"); } - protected MutableMap writingMapWithType(String typeName) { + /** null type-name means we are writing the expected type */ + protected MutableMap writingMapWithType(@Nullable String typeName) { JavaFieldsOnBlackboard.create(blackboard).fieldsToWriteFromJava = MutableList.of(); MutableMap map = MutableMap.of(); if (typeName!=null) { map.put("type", typeName); - addSerializers(typeName); } return map; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/JavaFieldsOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/JavaFieldsOnBlackboard.java index d974780408..c24b3cbb81 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/JavaFieldsOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/JavaFieldsOnBlackboard.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; +import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.YomlRequirement; @@ -33,27 +34,61 @@ public class JavaFieldsOnBlackboard implements YomlRequirement { private static String KEY = JavaFieldsOnBlackboard.class.getName(); public static boolean isPresent(Map blackboard) { - return blackboard.containsKey(KEY); + return isPresent(blackboard, null); } public static JavaFieldsOnBlackboard peek(Map blackboard) { - return (JavaFieldsOnBlackboard) blackboard.get(KEY); + return peek(blackboard, null); } public static JavaFieldsOnBlackboard getOrCreate(Map blackboard) { - if (!isPresent(blackboard)) { blackboard.put(KEY, new JavaFieldsOnBlackboard()); } - return peek(blackboard); + return getOrCreate(blackboard, null); } public static JavaFieldsOnBlackboard create(Map blackboard) { + return create(blackboard, null); + } + + private static String key(String label) { + return KEY + (Strings.isNonBlank(label) ? ":"+label : ""); + } + public static boolean isPresent(Map blackboard, String label) { + return blackboard.containsKey(key(label)); + } + public static JavaFieldsOnBlackboard peek(Map blackboard, String label) { + return (JavaFieldsOnBlackboard) blackboard.get(key(label)); + } + public static JavaFieldsOnBlackboard getOrCreate(Map blackboard, String label) { + if (!isPresent(blackboard)) { blackboard.put(key(label), new JavaFieldsOnBlackboard()); } + return peek(blackboard, label); + } + public static JavaFieldsOnBlackboard create(Map blackboard, String label) { if (isPresent(blackboard)) { throw new IllegalStateException("Already present"); } - blackboard.put(KEY, new JavaFieldsOnBlackboard()); - return peek(blackboard); + blackboard.put(key(label), new JavaFieldsOnBlackboard()); + return peek(blackboard, label); } List fieldsToWriteFromJava; + + String typeNameFromReadToConstructJavaLater; + Class typeFromReadToConstructJavaLater; + Map fieldsFromReadToConstructJava; + Map configToWriteFromJava; + @Override public void checkCompletion(YomlContext context) { - if (!fieldsToWriteFromJava.isEmpty()) { + if (fieldsToWriteFromJava!=null && !fieldsToWriteFromJava.isEmpty()) { throw new YomlException("Incomplete write of Java object data: "+fieldsToWriteFromJava, context); } + if (fieldsFromReadToConstructJava!=null && !fieldsFromReadToConstructJava.isEmpty()) { + throw new YomlException("Incomplete use of constructor fields creating Java object: "+fieldsFromReadToConstructJava, context); + } + if (configToWriteFromJava!=null && !configToWriteFromJava.isEmpty()) { + throw new YomlException("Incomplete write of config keys: "+configToWriteFromJava, context); + } } + + @Override + public String toString() { + return super.toString()+"("+fieldsToWriteFromJava+"; "+typeNameFromReadToConstructJavaLater+": "+fieldsFromReadToConstructJava+")"; + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java index 8e3bab4848..722a89d09e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java @@ -59,5 +59,9 @@ public void checkCompletion(YomlContext context) { throw new YomlException("Incomplete read of YAML keys: "+yamlKeysToReadToJava, context); } } - + + @Override + public String toString() { + return super.toString()+"("+yamlKeysToReadToJava+")"; + } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java index 7742015b6d..bf47a537ef 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java @@ -28,12 +28,12 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Boxing; -import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yaml.Yamls; import org.apache.brooklyn.util.yoml.Yoml; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.YomlTypeRegistry; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; import org.apache.brooklyn.util.yoml.internal.YomlUtils; import com.google.common.collect.Iterables; @@ -74,12 +74,15 @@ public Maybe newInstanceMaybe(String typeName, Yoml yoml) { if (type.parentType==null && type.javaType!=null) parentTypeName = getDefaultTypeNameOfClass(type.javaType); return Maybe.of(yoml.readFromYamlObject(type.yamlDefinition, parentTypeName)); } + Maybe> javaType = getJavaTypeInternal(type, typeName); - if (javaType.isAbsent()) { + ConstructionInstruction constructor = yoml.getConfig().getConstructionInstruction(); + if (javaType.isAbsent() && constructor==null) { if (type==null) return Maybe.absent("Unknown type `"+typeName+"`"); return Maybe.absent(new IllegalStateException("Incomplete hierarchy for "+type, ((Maybe.Absent)javaType).getException())); } - return Reflections.invokeConstructorFromArgsIncludingPrivate(javaType.get()); + + return ConstructionInstruction.Factory.newDefault(javaType.get(), constructor).create(); } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlAnnotationTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlAnnotationTests.java index 4534d7cb05..377a7e8b14 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlAnnotationTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlAnnotationTests.java @@ -69,7 +69,7 @@ public void testYomlFieldsAtTopLevel() { YomlTestFixture.newInstance(). addTypeWithAnnotations(ExplicitFieldsAtTopLevelExamples.Shape.class). read("{ name: nifty_shape, couleur: blue }", "shape").assertResult(shape). - write(shape).assertResult("{ type: "+ExplicitFieldsAtTopLevelExamples.Shape.class.getName()+", name: nifty_shape, colour: blue }"); + write(shape).assertResult("{ type: shape, colour: blue, name: nifty_shape }"); } static class ExplicitFieldsAllExamples { @@ -105,7 +105,7 @@ public void testYomlAllFields() { YomlTestFixture.newInstance(). addTypeWithAnnotations(ExplicitFieldsAllExamples.Shape.class). read("{ name: nifty_shape, couleur: blue }", "shape").assertResult(shape). - write(shape).assertResult("{ type: "+ExplicitFieldsAllExamples.Shape.class.getName()+", name: nifty_shape, colour: blue }"); + write(shape).assertResult("{ type: shape, colour: blue, name: nifty_shape }"); } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigTests.java new file mode 100644 index 0000000000..0132b4cda6 --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigTests.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.tests; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.config.ConfigInheritance; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.Jsonya; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import com.google.common.base.Predicate; +import com.google.common.reflect.TypeToken; + +/** Tests that the default serializers can read/write types and fields. + *

+ * And shows how to use them at a low level. + */ +public class YomlConfigTests { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(YomlConfigTests.class); + + static class MockConfigKey implements ConfigKey { + String name; + Class type; + T defaultValue; + + public MockConfigKey(Class type, String name) { + this.name = name; + this.type = type; + } + + @Override public String getDescription() { return null; } + @Override public String getName() { return name; } + @Override public Collection getNameParts() { return MutableList.of(name); } + @Override public TypeToken getTypeToken() { return TypeToken.of(type); } + @Override public Class getType() { return type; } + + @Override public String getTypeName() { throw new UnsupportedOperationException(); } + @Override public T getDefaultValue() { return defaultValue; } + @Override public boolean hasDefaultValue() { return defaultValue!=null; } + @Override public boolean isReconfigurable() { return false; } + @Override public ConfigInheritance getTypeInheritance() { return null; } + @Override public ConfigInheritance getParentInheritance() { return null; } + @Override public ConfigInheritance getInheritance() { return null; } + @Override public Predicate getConstraint() { return null; } + @Override public boolean isValueValid(T value) { return true; } + } + + static class S1 { + Map keys = MutableMap.of(); + static ConfigKey K1 = new MockConfigKey(String.class, "k1"); + S1(Map keys) { this.keys.putAll(keys); } + } + static class S2 { + ConfigKey K2; + } + + @Test + public void testRead() { + YomlTestFixture y = YomlTestFixture.newInstance(); + + Set serializers = YomlAnnotations.findSerializerAnnotations(S1.class, "keys", "config"); + y.tr.put("s1", S1.class, serializers); + // TODO autodetect above, then do this +// y.addType("s1", S1.class); + + y.read("{ type: s1, k1: foo }", null); + + Asserts.assertInstanceOf(y.lastReadResult, S1.class); + Asserts.assertEquals(((S1)y.lastReadResult).keys.get("k1"), "foo"); + } + + @Test + public void testWrite() { + YomlTestFixture y = YomlTestFixture.newInstance(); + + Set serializers = YomlAnnotations.findSerializerAnnotations(S1.class, "keys", "config"); + y.tr.put("s1", S1.class, serializers); + // TODO autodetect above, then do this +// y.addType("s1", S1.class); + + S1 s1 = new S1(MutableMap.of("k1", "foo")); + y.write(s1); + YomlTestFixture.assertEqualsIgnoringQuotes(Jsonya.newInstance().add(y.lastWriteResult).toString(), "{ type: s1, k1: foo }", "wrong serialization"); + } + + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java index b0a89fae18..59b699b0fc 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java @@ -113,7 +113,7 @@ public YomlTestFixture addTypeWithAnnotations(Class type) { return addTypeWithAnnotations(null, type); } public YomlTestFixture addTypeWithAnnotations(String name, Class type) { - Set serializers = YomlAnnotations.findSerializerAnnotations(type); + Set serializers = YomlAnnotations.findSerializerAnnotations(type, null, null); for (String n: YomlAnnotations.findTypeNamesFromAnnotations(type, name, false)) { tr.put(n, type, serializers); } From 9aaa2ed0d26acc8a17839d0fd72240349ccdefbb Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 12 Sep 2016 15:03:34 +0100 Subject: [PATCH 35/77] cleanups of config key handling, including nesting and annotations plus minor updates to how YomlConfig is initialized --- .../org/apache/brooklyn/util/yoml/Yoml.java | 21 +- .../brooklyn/util/yoml/YomlContext.java | 6 +- .../yoml/annotations/YomlAnnotations.java | 38 ++-- .../annotations/YomlConstructorConfigMap.java | 47 +++++ .../util/yoml/internal/YomlConfig.java | 31 ++- .../util/yoml/internal/YomlConverter.java | 6 +- .../ConfigInMapUnderConfigSerializer.java | 47 ++++- .../ExplicitConfigKeySerializer.java | 13 +- ...ield.java => ExplicitFieldSerializer.java} | 40 ++-- .../serializers/ExplicitFieldsBlackboard.java | 22 +- .../serializers/FieldsInMapUnderFields.java | 10 +- .../InstantiateTypeFromRegistry.java | 8 +- ...antiateTypeFromRegistryUsingConfigMap.java | 71 ++++--- .../util/yoml/tests/ConfigKeyTests.java | 190 ++++++++++++++++++ .../util/yoml/tests/ExplicitFieldTests.java | 4 +- .../util/yoml/tests/YomlConfigTests.java | 115 ----------- .../util/yoml/tests/YomlTestFixture.java | 32 ++- 17 files changed, 470 insertions(+), 231 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConstructorConfigMap.java rename utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/{ExplicitField.java => ExplicitFieldSerializer.java} (89%) create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConfigKeyTests.java delete mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigTests.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java index 64184e630f..346bc75f7b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java @@ -18,17 +18,8 @@ */ package org.apache.brooklyn.util.yoml; -import java.util.List; - -import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.yoml.internal.YomlConfig; import org.apache.brooklyn.util.yoml.internal.YomlConverter; -import org.apache.brooklyn.util.yoml.serializers.FieldsInMapUnderFields; -import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeEnum; -import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistry; -import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeList; -import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeMap; -import org.apache.brooklyn.util.yoml.serializers.InstantiateTypePrimitive; public class Yoml { @@ -42,17 +33,7 @@ public static Yoml newInstance(YomlConfig config) { } public static Yoml newInstance(YomlTypeRegistry typeRegistry) { - return newInstance(typeRegistry, MutableList.of( - new FieldsInMapUnderFields(), - new InstantiateTypePrimitive(), - new InstantiateTypeEnum(), - new InstantiateTypeList(), - new InstantiateTypeMap(), - new InstantiateTypeFromRegistry() )); - } - - private static Yoml newInstance(YomlTypeRegistry typeRegistry, List serializers) { - return new Yoml(YomlConfig.Builder.builder().typeRegistry(typeRegistry).serializersPost(serializers).build()); + return new Yoml(YomlConfig.Builder.builder().typeRegistry(typeRegistry).serializersPostAddDefaults().build()); } public YomlConfig getConfig() { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContext.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContext.java index 840503d1d9..a45b0b7b58 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContext.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContext.java @@ -104,5 +104,9 @@ public void phasesFinished() { if (phaseCurrent!=null) phasesPreceding.add(phaseCurrent); phasesFollowing = MutableSet.of(); phaseAdvance(); } - + + @Override + public String toString() { + return super.toString()+"["+getJsonPath()+"]"; + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java index 5fd984205a..56494f3031 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java @@ -20,6 +20,8 @@ import java.lang.reflect.Field; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -29,7 +31,7 @@ import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.internal.YomlUtils; -import org.apache.brooklyn.util.yoml.serializers.ExplicitField; +import org.apache.brooklyn.util.yoml.serializers.ExplicitFieldSerializer; import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; public class YomlAnnotations { @@ -54,33 +56,31 @@ public static Set findTypeNamesFromAnnotations(Class type, String opt return names; } - public static List findExplicitFieldSerializers(Class t, boolean requireAnnotation) { - List result = MutableList.of(); + public static Collection findExplicitFieldSerializers(Class t, boolean requireAnnotation) { + List result = MutableList.of(); Map fields = YomlUtils.getAllNonTransientNonStaticFields(t, null); for (Map.Entry f: fields.entrySet()) { if (!requireAnnotation || f.getValue().isAnnotationPresent(YomlFieldAtTopLevel.class)) - result.add(new ExplicitField(f.getKey(), f.getValue())); + result.add(new ExplicitFieldSerializer(f.getKey(), f.getValue())); } return result; } - - public static Set findConfigBagSerializerAnnotations(Class type, - String fieldNameForConfigToAutodetect, String keyNameForConfigWhenSerialized) { - - // TODO autodetect, add map - - return MutableSet.copyOf(InstantiateTypeFromRegistryUsingConfigMap.newConfigKeySerializersForType( - fieldNameForConfigToAutodetect, keyNameForConfigWhenSerialized, type)); - } - public static Set findSerializerAnnotations(Class type, - String fieldNamesForConfigToAutodetect, String keyNameForConfigWhenSerialized) { + public static Collection findConfigMapSerializers(Class t) { + YomlConstructorConfigMap ann = t.getAnnotation(YomlConstructorConfigMap.class); + if (ann==null) return Collections.emptyList(); + return InstantiateTypeFromRegistryUsingConfigMap.newConfigKeySerializersForType( + t, + ann.value(), ann.writeAsKey()!=null ? ann.writeAsKey() : ann.value(), + ann.validateAheadOfTime(), ann.requireStaticKeys()); + } + + public static Set findSerializerAnnotations(Class type) { Set result = MutableSet.of(); - - if (fieldNamesForConfigToAutodetect!=null) { - result.addAll(findConfigBagSerializerAnnotations(type, fieldNamesForConfigToAutodetect, keyNameForConfigWhenSerialized)); - } + + // if it takes a config map + result.addAll(findConfigMapSerializers(type)); // explicit fields YomlAllFieldsAtTopLevel allFields = type.getAnnotation(YomlAllFieldsAtTopLevel.class); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConstructorConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConstructorConfigMap.java new file mode 100644 index 0000000000..e86cbf6898 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConstructorConfigMap.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.annotations; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.apache.brooklyn.config.ConfigKey; + +/** + * Indicates that a class should be yoml-serialized using a one-arg constructor taking a map of config. + * Types will be inferred where possible based on the presence of {@link ConfigKey} static fields in the type. + */ +@Retention(RUNTIME) +@Target({ TYPE }) +public @interface YomlConstructorConfigMap { + /** YOML needs to know which field contains the config at serialization time. */ + String value(); + /** By default YOML reads/writes unrecognised key values against a key with the same name as {@link #value()}. + * This can be set to use a different key in the YAML. */ + String writeAsKey() default ""; + + /** Validate that a suitable field and constructor exist, failing fast if not */ + boolean validateAheadOfTime() default true; + + /** Skip if there are no declared config keys (default false) */ + boolean requireStaticKeys() default false; +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfig.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfig.java index 4dd2140619..79405b6c58 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfig.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfig.java @@ -18,6 +18,7 @@ */ package org.apache.brooklyn.util.yoml.internal; +import java.util.Collection; import java.util.List; import org.apache.brooklyn.util.collections.MutableList; @@ -25,6 +26,12 @@ import org.apache.brooklyn.util.javalang.coerce.TypeCoercerExtensible; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.YomlTypeRegistry; +import org.apache.brooklyn.util.yoml.serializers.FieldsInMapUnderFields; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeEnum; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistry; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeList; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeMap; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypePrimitive; import com.google.common.collect.ImmutableList; @@ -38,10 +45,12 @@ public interface YomlConfig { public static class BasicYomlConfig implements YomlConfig { private BasicYomlConfig() {} private BasicYomlConfig(YomlConfig original) { - this.typeRegistry = original.getTypeRegistry(); - this.coercer = original.getCoercer(); - this.serializersPost = original.getSerializersPost(); - this.constructionInstruction = original.getConstructionInstruction(); + if (original!=null) { + this.typeRegistry = original.getTypeRegistry(); + this.coercer = original.getCoercer(); + this.serializersPost = original.getSerializersPost(); + this.constructionInstruction = original.getConstructionInstruction(); + } } private YomlTypeRegistry typeRegistry; @@ -76,9 +85,21 @@ public static class Builder { protected Builder(YomlConfig source) { result = new BasicYomlConfig(source); } public Builder typeRegistry(YomlTypeRegistry tr) { result.typeRegistry = tr; return this; } public Builder coercer(TypeCoercer x) { result.coercer = x; return this; } - public Builder serializersPost(List x) { result.serializersPost = x; return this; } + public Builder serializersPostReplace(List x) { result.serializersPost = x; return this; } + public Builder serializersPostAdd(Collection x) { result.serializersPost.addAll(x); return this; } + public Builder serializersPostAddDefaults() { return serializersPostAdd(getDefaultSerializers()); } public Builder constructionInstruction(ConstructionInstruction x) { result.constructionInstruction = x; return this; } public YomlConfig build() { return new BasicYomlConfig(result); } + + public static List getDefaultSerializers() { + return MutableList.of( + new FieldsInMapUnderFields(), + new InstantiateTypePrimitive(), + new InstantiateTypeEnum(), + new InstantiateTypeList(), + new InstantiateTypeMap(), + new InstantiateTypeFromRegistry() ); + } } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java index 0bd899c5e8..397a07042b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java @@ -78,8 +78,12 @@ protected void loopOverSerializers(YomlContext context) { if (log.isTraceEnabled()) log.trace("YOML now looking at "+context.getJsonPath()+"/ = "+context.getJavaObject()+" <-> "+context.getYamlObject()+" ("+context.getExpectedType()+")"); while (context.phaseAdvance()) { - if (log.isTraceEnabled()) log.trace("read "+context.getJsonPath()+"/ = "+context.getJavaObject()+" entering phase "+context.phaseCurrent()+": "+YamlKeysOnBlackboard.peek(blackboard)+" / "+JavaFieldsOnBlackboard.peek(blackboard)+" / "+JavaFieldsOnBlackboard.peek(blackboard, "config")); while (context.phaseStepAdvance() writePrepareGeneralMap() { JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); - if (fib==null) return null; - return fib.configToWriteFromJava; + if (fib==null || fib.configToWriteFromJava==null) return null; + Map configMap = MutableMap.of(); + + for (Map.Entry entry: fib.configToWriteFromJava.entrySet()) { + // NB: won't normally have a type, the explicit config keys will take those + String optionalType = getType(entry.getKey(), entry.getValue()); + Object v = converter.write(new YomlContextForWrite(entry.getValue(), context.getJsonPath()+"/"+entry.getKey(), optionalType) ); + configMap.put(entry.getKey(), v); + } + for (String key: configMap.keySet()) fib.configToWriteFromJava.remove(key); + + return configMap; + } + + protected String getType(String key, Object value) { + ExplicitFieldsBlackboard efb = ExplicitFieldsBlackboard.get(blackboard, getKeyNameForMapOfGeneralValues()); + Class type = efb.getDeclaredType(key); + String optionalType = null; + if (type!=null && (value==null || type.isInstance(value))) optionalType = config.getTypeRegistry().getTypeNameOfClass(type); + return optionalType; } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitConfigKeySerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitConfigKeySerializer.java index d504511b54..467702f6ed 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitConfigKeySerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitConfigKeySerializer.java @@ -32,11 +32,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ExplicitConfigKeySerializer extends ExplicitField { +public class ExplicitConfigKeySerializer extends ExplicitFieldSerializer { private static final Logger log = LoggerFactory.getLogger(ExplicitConfigKeySerializer.class); - String keyNameForConfigWhenSerialized = null; + final String keyNameForConfigWhenSerialized; public ExplicitConfigKeySerializer(String keyNameForConfigWhenSerialized, ConfigKey configKey, Field optionalFieldForAnnotations) { super(configKey.getName(), optionalFieldForAnnotations); @@ -117,11 +117,16 @@ protected YomlSerializerWorker newWorker() { return new Worker(); } - public class Worker extends ExplicitField.Worker { + public class Worker extends ExplicitFieldSerializer.Worker { protected boolean canDoRead() { return !hasJavaObject() && context.willDoPhase(InstantiateTypeFromRegistryUsingConfigMap.PHASE_INSTANTIATE_TYPE_DEFERRED); } + + @Override + protected void prepareExplicitFields() { + super.prepareExplicitFields(); + getExplicitFieldsBlackboard().setDeclaredTypeIfUnset(fieldName, configKey.getType()); + } } - } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldSerializer.java similarity index 89% rename from utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldSerializer.java index 0ac7116ea0..a6dd890a83 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitField.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldSerializer.java @@ -47,16 +47,16 @@ * look for the field name, and rewrite under the preferred alias at the root. */ @YomlAllFieldsAtTopLevel @Alias("explicit-field") -public class ExplicitField extends YomlSerializerComposition { +public class ExplicitFieldSerializer extends YomlSerializerComposition { - private static final Logger log = LoggerFactory.getLogger(ExplicitField.class); + private static final Logger log = LoggerFactory.getLogger(ExplicitFieldSerializer.class); - public ExplicitField() {} - public ExplicitField(Field f) { + public ExplicitFieldSerializer() {} + public ExplicitFieldSerializer(Field f) { this(f.getName(), f); } /** preferred constructor for dealing with shadowed fields using superclass.field naming convention */ - public ExplicitField(String name, Field f) { + public ExplicitFieldSerializer(String name, Field f) { fieldName = keyName = name; Alias alias = f.getAnnotation(Alias.class); @@ -117,7 +117,7 @@ protected String getKeyNameForMapOfGeneralValues() { public class Worker extends YomlSerializerWorker { - String PREPARING_EXPLICIT_FIELDS = "preparing-explicit-fields"; + final static String PREPARING_EXPLICIT_FIELDS = "preparing-explicit-fields"; protected String getPreferredKeyName() { String result = getExplicitFieldsBlackboard().getKeyName(fieldName); @@ -151,23 +151,27 @@ protected boolean readyForMainEvent() { } } if (context.isPhase(PREPARING_EXPLICIT_FIELDS)) { - // do the pre-main pass to determine what is required for explicit fields and what the default is - getExplicitFieldsBlackboard().setKeyNameIfUnset(fieldName, keyName); - getExplicitFieldsBlackboard().addAliasIfNotDisinherited(fieldName, alias); - getExplicitFieldsBlackboard().addAliasesIfNotDisinherited(fieldName, aliases); - getExplicitFieldsBlackboard().setAliasesInheritedIfUnset(fieldName, aliasesInherited); - getExplicitFieldsBlackboard().setAliasesStrictIfUnset(fieldName, aliasesStrict); - getExplicitFieldsBlackboard().setConstraintIfUnset(fieldName, constraint); - if (getExplicitFieldsBlackboard().getDefault(fieldName).isAbsent() && defaultValue!=null) { - getExplicitFieldsBlackboard().setUseDefaultFrom(fieldName, ExplicitField.this, defaultValue); - } - // TODO combine aliases, other items + prepareExplicitFields(); return false; } if (getExplicitFieldsBlackboard().isFieldDone(fieldName)) return false; if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return false; return true; } + + protected void prepareExplicitFields() { + // do the pre-main pass to determine what is required for explicit fields and what the default is + getExplicitFieldsBlackboard().setKeyNameIfUnset(fieldName, keyName); + getExplicitFieldsBlackboard().addAliasIfNotDisinherited(fieldName, alias); + getExplicitFieldsBlackboard().addAliasesIfNotDisinherited(fieldName, aliases); + getExplicitFieldsBlackboard().setAliasesInheritedIfUnset(fieldName, aliasesInherited); + getExplicitFieldsBlackboard().setAliasesStrictIfUnset(fieldName, aliasesStrict); + getExplicitFieldsBlackboard().setConstraintIfUnset(fieldName, constraint); + if (getExplicitFieldsBlackboard().getDefault(fieldName).isAbsent() && defaultValue!=null) { + getExplicitFieldsBlackboard().setUseDefaultFrom(fieldName, ExplicitFieldSerializer.this, defaultValue); + } + // TODO combine aliases, other items + } protected boolean canDoRead() { return hasJavaObject(); } @@ -193,7 +197,7 @@ public void read() { Maybe value = peekFromYamlKeysOnBlackboard(alias, Object.class); if (value.isAbsent()) continue; if (log.isTraceEnabled()) { - log.trace(ExplicitField.this+": found "+alias+" for "+fieldName); + log.trace(ExplicitFieldSerializer.this+": found "+alias+" for "+fieldName); } boolean fieldAlreadyKnown = fields.containsKey(fieldName); if (value.isPresent() && fieldAlreadyKnown) { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldsBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldsBlackboard.java index 2444ec7926..4a3d190706 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldsBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldsBlackboard.java @@ -32,7 +32,7 @@ import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.YomlRequirement; import org.apache.brooklyn.util.yoml.YomlSerializer; -import org.apache.brooklyn.util.yoml.serializers.ExplicitField.FieldConstraint; +import org.apache.brooklyn.util.yoml.serializers.ExplicitFieldSerializer.FieldConstraint; public class ExplicitFieldsBlackboard implements YomlRequirement { @@ -55,6 +55,7 @@ public static ExplicitFieldsBlackboard get(Map blackboard, String private final Map fieldsConstraints = MutableMap.of(); private final Map defaultValueForFieldComesFromSerializer = MutableMap.of(); private final Map defaultValueOfField = MutableMap.of(); + private final Map> declaredTypeOfFieldsAndAliases = MutableMap.of(); public String getKeyName(String fieldName) { return Maybe.ofDisallowingNull(keyNames.get(fieldName)).orNull(); @@ -78,7 +79,7 @@ public void setAliasesStrictIfUnset(String fieldName, Boolean aliasesStrict) { aliasesStricts.put(fieldName, aliasesStrict); } public void addAliasIfNotDisinherited(String fieldName, String alias) { - addAliasesIfNotDisinherited(fieldName, MutableList.of(alias)); + addAliasesIfNotDisinherited(fieldName, MutableList.of().appendIfNotNull(alias)); } public void addAliasesIfNotDisinherited(String fieldName, List aliases) { if (Boolean.FALSE.equals(aliasesInheriteds.get(fieldName))) { @@ -91,7 +92,7 @@ public void addAliasesIfNotDisinherited(String fieldName, List aliases) this.aliases.put(fieldName, aa); } if (aliases==null) return; - for (String alias: aliases) aa.add(alias); + for (String alias: aliases) { if (Strings.isNonBlank(alias)) { aa.add(alias); } } } public Collection getAliases(String fieldName) { Set aa = this.aliases.get(fieldName); @@ -140,4 +141,19 @@ public Maybe getDefault(String fieldName) { return Maybe.of(defaultValueOfField.get(fieldName)); } + /** optional, and must be called after aliases; not used for fields, is used for config keys */ + public void setDeclaredTypeIfUnset(String fieldName, Class type) { + setDeclaredTypeOfItemIfUnset(fieldName, type); + for (String alias: getAliases(fieldName)) + setDeclaredTypeOfItemIfUnset(alias, type); + } + protected void setDeclaredTypeOfItemIfUnset(String fieldName, Class type) { + if (declaredTypeOfFieldsAndAliases.get(fieldName)!=null) return; + declaredTypeOfFieldsAndAliases.put(fieldName, type); + } + /** only if {@link #setDeclaredTypeIfUnset(String, Class)} is being used (eg config keys) */ + public Class getDeclaredType(String key) { + return declaredTypeOfFieldsAndAliases.get(key); + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java index 95c898be3f..d2c5ad80a9 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java @@ -27,7 +27,6 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; -import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.YomlContext.StandardPhases; import org.apache.brooklyn.util.yoml.YomlContextForRead; @@ -56,9 +55,9 @@ protected String getExpectedPhaseRead() { public class Worker extends YomlSerializerWorker { - protected boolean setKeyValueForJavaObjectOnRead(Object key, Object value) + protected boolean setKeyValueForJavaObjectOnRead(String key, Object value) throws IllegalAccessException { - Maybe ffm = Reflections.findFieldMaybe(getJavaObject().getClass(), Strings.toString(key)); + Maybe ffm = Reflections.findFieldMaybe(getJavaObject().getClass(), key); if (ffm.isAbsentOrNull()) { // just skip (could throw, but leave it in case something else recognises it) return false; @@ -89,11 +88,12 @@ public void read() { if (fields==null) return; boolean changed = false; - for (Object f: MutableList.copyOf( ((Map)fields).keySet() )) { + for (Object fo: MutableList.copyOf( ((Map)fields).keySet() )) { + String f = (String)fo; Object v = ((Map)fields).get(f); try { if (setKeyValueForJavaObjectOnRead(f, v)) { - ((Map)fields).remove(Strings.toString(f)); + ((Map)fields).remove(f); changed = true; } } catch (Exception e) { throw Exceptions.propagate(e); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java index 2f3cc3ad7b..df444738ce 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java @@ -106,12 +106,16 @@ public void write() { typeName); writingPopulateBlackboard(); - - context.phaseInsert(YomlContext.StandardPhases.HANDLING_FIELDS, YomlContext.StandardPhases.MANIPULATING); + writingInsertPhases(); + storeWriteObjectAndAdvance(map); return; } + protected void writingInsertPhases() { + context.phaseInsert(YomlContext.StandardPhases.HANDLING_FIELDS, YomlContext.StandardPhases.MANIPULATING); + } + protected void writingPopulateBlackboard() { // collect fields JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java index d0fbe04133..9e41fe3a83 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java @@ -37,46 +37,58 @@ import com.google.common.base.Preconditions; +/** Special instantiator for when the class's constructor takes a Map of config */ public class InstantiateTypeFromRegistryUsingConfigMap extends InstantiateTypeFromRegistry { public static final String PHASE_INSTANTIATE_TYPE_DEFERRED = "handling-type-deferred-after-config"; String keyNameForConfigWhenSerialized = null; String fieldNameForConfigInJavaIfPreset = null; + boolean staticKeysRequired; // don't currently fully support inferring setup from annotations; we need the field above. // easily could automate with a YomlConfigMap annotation - but for now make it explicit // (for now this field can be used to load explicit config keys, if the field name is supplied) boolean inferByScanning = false; - public static Set newConfigKeyClassScanningSerializers(String fieldNameForConfigInJava, String keyNameForConfigWhenSerialized) { - Preconditions.checkNotNull(fieldNameForConfigInJava); - Preconditions.checkNotNull(keyNameForConfigWhenSerialized); + /** creates a set of serializers handling config for any type, with the given field/key combination; + * the given field will be checked at serialization time to determine whether this is applicable */ + public static Set newConfigKeyClassScanningSerializers( + String fieldNameForConfigInJava, String keyNameForConfigWhenSerialized, boolean requireStaticKeys) { - InstantiateTypeFromRegistryUsingConfigMap instantiator = new InstantiateTypeFromRegistryUsingConfigMap(); - instantiator.keyNameForConfigWhenSerialized = keyNameForConfigWhenSerialized; - instantiator.fieldNameForConfigInJavaIfPreset = fieldNameForConfigInJava; - instantiator.inferByScanning = true; - - return MutableSet.of( - new ConfigInMapUnderConfigSerializer(keyNameForConfigWhenSerialized), - instantiator); + return newConfigKeySerializersForType(null, + fieldNameForConfigInJava, keyNameForConfigWhenSerialized, + false, requireStaticKeys); } - public static Set newConfigKeySerializersForType( - String fieldNameForConfigInJava, String keyNameForConfigWhenSerialized, Class type) { - Preconditions.checkNotNull(fieldNameForConfigInJava); - Preconditions.checkNotNull(keyNameForConfigWhenSerialized); + /** creates a set of serializers handling config for the given type, for use in a type-specific serialization, + * permitting multiple field/key combos; if the given field is not found, the pair is excluded here */ + public static Set newConfigKeySerializersForType( + Class type, + String fieldNameForConfigInJava, String keyNameForConfigWhenSerialized, + boolean validateAheadOfTime, boolean requireStaticKeys) { + MutableSet result = MutableSet.of(); + if (fieldNameForConfigInJava==null) return result; InstantiateTypeFromRegistryUsingConfigMap instantiator = new InstantiateTypeFromRegistryUsingConfigMap(); - instantiator.keyNameForConfigWhenSerialized = keyNameForConfigWhenSerialized; instantiator.fieldNameForConfigInJavaIfPreset = fieldNameForConfigInJava; - instantiator.inferByScanning = false; + if (validateAheadOfTime) { + instantiator.findFieldMaybe(type).get(); + instantiator.findConstructorMaybe(type).get(); + } + instantiator.keyNameForConfigWhenSerialized = keyNameForConfigWhenSerialized; + instantiator.staticKeysRequired = false; - MutableSet result = MutableSet.of( - new ConfigInMapUnderConfigSerializer(keyNameForConfigWhenSerialized), - instantiator); - result.addAll(ExplicitConfigKeySerializer.findExplicitConfigKeySerializers(keyNameForConfigWhenSerialized, type).values()); + if (type!=null) { + instantiator.inferByScanning = false; + Map keys = ExplicitConfigKeySerializer.findExplicitConfigKeySerializers(keyNameForConfigWhenSerialized, type); + result.addAll(keys.values()); + } else { + instantiator.inferByScanning = true; + } + + result.add(new ConfigInMapUnderConfigSerializer(keyNameForConfigWhenSerialized)); + result.add(instantiator); return result; } @@ -209,17 +221,30 @@ protected void writingPopulateBlackboard() { addExtraTypeSerializers(getJavaObject().getClass()); } - + + protected void writingInsertPhases() { + super.writingInsertPhases(); + // for configs, we need to do this to get type info (and preferred aliases) + context.phaseInsert(ExplicitFieldSerializer.Worker.PREPARING_EXPLICIT_FIELDS); + } + } /** configurable if it has a map constructor and at least one public static config key */ protected boolean isConfigurable(Class type) { if (type==null) return false; if (findConstructorMaybe(type).isAbsent()) return false; - if (ExplicitConfigKeySerializer.findConfigKeys(type).isEmpty()) return false; + if (findFieldMaybe(type).isAbsent()) return false; + if (staticKeysRequired && ExplicitConfigKeySerializer.findConfigKeys(type).isEmpty()) return false; return true; } + protected Maybe findFieldMaybe(Class type) { + Maybe f = Reflections.findFieldMaybe(type, fieldNameForConfigInJavaIfPreset); + if (f.isPresent() && !Map.class.isAssignableFrom(f.get().getType())) f = Maybe.absent(); + return f; + } + protected Maybe findConstructorMaybe(Class type) { return Reflections.findConstructorMaybe(type, Map.class); } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConfigKeyTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConfigKeyTests.java new file mode 100644 index 0000000000..b6fbbad4fa --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConfigKeyTests.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.tests; + +import java.util.Collection; +import java.util.Map; + +import org.apache.brooklyn.config.ConfigInheritance; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.Jsonya; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.yoml.annotations.YomlConstructorConfigMap; +import org.apache.brooklyn.util.yoml.internal.YomlConfig; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import com.google.common.base.Predicate; +import com.google.common.reflect.TypeToken; + +/** Tests that the default serializers can read/write types and fields. + *

+ * And shows how to use them at a low level. + */ +public class ConfigKeyTests { + + @SuppressWarnings("unused") + private static final Logger log = LoggerFactory.getLogger(ConfigKeyTests.class); + + static class MockConfigKey implements ConfigKey { + String name; + Class type; + T defaultValue; + + public MockConfigKey(Class type, String name) { + this.name = name; + this.type = type; + } + + @Override public String getDescription() { return null; } + @Override public String getName() { return name; } + @Override public Collection getNameParts() { return MutableList.of(name); } + @Override public TypeToken getTypeToken() { return TypeToken.of(type); } + @Override public Class getType() { return type; } + + @Override public String getTypeName() { throw new UnsupportedOperationException(); } + @Override public T getDefaultValue() { return defaultValue; } + @Override public boolean hasDefaultValue() { return defaultValue!=null; } + @Override public boolean isReconfigurable() { return false; } + @Override public ConfigInheritance getTypeInheritance() { return null; } + @Override public ConfigInheritance getParentInheritance() { return null; } + @Override public ConfigInheritance getInheritance() { return null; } + @Override public Predicate getConstraint() { return null; } + @Override public boolean isValueValid(T value) { return true; } + } + + static class S1 { + static ConfigKey K1 = new MockConfigKey(String.class, "k1"); + Map keys = MutableMap.of(); + S1(Map keys) { this.keys.putAll(keys); } + + @Override + public boolean equals(Object obj) { + return (obj instanceof S1) && ((S1)obj).keys.equals(keys); + } + @Override + public int hashCode() { + return keys.hashCode(); + } + @Override + public String toString() { + return super.toString()+keys; + } + } + + @Test + public void testRead() { + YomlTestFixture y = YomlTestFixture.newInstance() + .addTypeWithAnnotationsAndConfig("s1", S1.class, MutableMap.of("keys", "config")); + + y.read("{ type: s1, k1: foo }", null); + + Asserts.assertInstanceOf(y.lastReadResult, S1.class); + Asserts.assertEquals(((S1)y.lastReadResult).keys.get("k1"), "foo"); + } + + @Test + public void testWrite() { + YomlTestFixture y = YomlTestFixture.newInstance() + .addTypeWithAnnotationsAndConfig("s1", S1.class, MutableMap.of("keys", "config")); + + S1 s1 = new S1(MutableMap.of("k1", "foo")); + y.write(s1); + YomlTestFixture.assertEqualsIgnoringQuotes(Jsonya.newInstance().add(y.lastWriteResult).toString(), "{ type: s1, k1: foo }", "wrong serialization"); + } + + @Test + public void testReadWrite() { + YomlTestFixture.newInstance() + .addTypeWithAnnotationsAndConfig("s1", S1.class, MutableMap.of("keys", "config")) + .reading("{ type: s1, k1: foo }").writing(new S1(MutableMap.of("k1", "foo"))) + .doReadWriteAssertingJsonMatch(); + } + + static class S2 extends S1 { + S2(Map keys) { super(keys); } + static ConfigKey K2 = new MockConfigKey(String.class, "k2"); + static ConfigKey KS = new MockConfigKey(S1.class, "ks"); + } + + @Test + public void testReadWriteInherited() { + YomlTestFixture.newInstance() + .addTypeWithAnnotationsAndConfig("s2", S2.class, MutableMap.of("keys", "config")) + .reading("{ type: s2, k1: foo, k2: bar }").writing(new S2(MutableMap.of("k1", "foo", "k2", "bar"))) + .doReadWriteAssertingJsonMatch(); + } + + @Test + public void testReadWriteNested() { + YomlTestFixture.newInstance() + .addTypeWithAnnotationsAndConfig("s1", S1.class, MutableMap.of("keys", "config")) + .addTypeWithAnnotationsAndConfig("s2", S2.class, MutableMap.of("keys", "config")) + .reading("{ type: s2, ks: { k1: foo } }").writing(new S2(MutableMap.of("ks", new S1(MutableMap.of("k1", "foo"))))) + .doReadWriteAssertingJsonMatch(); + } + @Test + public void testReadWriteNestedGlobalConfigKeySupport() { + YomlTestFixture y = YomlTestFixture.newInstance(YomlConfig.Builder.builder() + .serializersPostAdd(InstantiateTypeFromRegistryUsingConfigMap.newConfigKeyClassScanningSerializers("keys", "config", true)) + .serializersPostAddDefaults().build()); + y.addTypeWithAnnotations("s1", S1.class) + .addTypeWithAnnotations("s2", S2.class) + .reading("{ type: s2, ks: { k1: foo } }").writing(new S2(MutableMap.of("ks", new S1(MutableMap.of("k1", "foo"))))) + .doReadWriteAssertingJsonMatch(); + } + + @YomlConstructorConfigMap(value="keys", writeAsKey="extraConfig") + static class S3 extends S1 { + S3(Map keys) { super(keys); } + static ConfigKey K2 = new MockConfigKey(String.class, "k2"); + static ConfigKey KS1 = new MockConfigKey(S1.class, "ks1"); + static ConfigKey KS3 = new MockConfigKey(S3.class, "ks3"); + } + + @Test + public void testReadWriteAnnotation() { + YomlTestFixture.newInstance() + .addTypeWithAnnotations("s3", S3.class) + .reading("{ type: s3, ks3: { k1: foo } }").writing(new S3(MutableMap.of("ks3", new S3(MutableMap.of("k1", "foo"))))) + .doReadWriteAssertingJsonMatch(); + } + + @Test + public void testReadWriteAnnotationTypeInfoNeeded() { + YomlTestFixture.newInstance() + .addTypeWithAnnotations("s1", S1.class) + .addTypeWithAnnotations("s3", S3.class) + .reading("{ type: s3, ks1: { type: s3, k1: foo } }").writing(new S3(MutableMap.of("ks1", new S3(MutableMap.of("k1", "foo"))))) + .doReadWriteAssertingJsonMatch(); + } + + @Test + public void testReadWriteExtraField() { + YomlTestFixture.newInstance() + .addTypeWithAnnotations("s3", S3.class) + .reading("{ type: s3, k1: foo, extraConfig: { k0: { type: string, value: bar } } }").writing(new S3(MutableMap.of("k1", "foo", "k0", "bar"))) + .doReadWriteAssertingJsonMatch(); + } + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ExplicitFieldTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ExplicitFieldTests.java index 307db527da..3a5c780535 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ExplicitFieldTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ExplicitFieldTests.java @@ -26,7 +26,7 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.serializers.AllFieldsExplicit; -import org.apache.brooklyn.util.yoml.serializers.ExplicitField; +import org.apache.brooklyn.util.yoml.serializers.ExplicitFieldSerializer; import org.apache.brooklyn.util.yoml.tests.YomlBasicTests.Shape; import org.apache.brooklyn.util.yoml.tests.YomlBasicTests.ShapeWithSize; import org.testng.Assert; @@ -36,7 +36,7 @@ public class ExplicitFieldTests { public static YomlSerializer explicitFieldSerializer(String yaml) { - return (YomlSerializer) YomlTestFixture.newInstance().read("{ fields: "+yaml+" }", "java:"+ExplicitField.class.getName()).lastReadResult; + return (YomlSerializer) YomlTestFixture.newInstance().read("{ fields: "+yaml+" }", "java:"+ExplicitFieldSerializer.class.getName()).lastReadResult; } protected static YomlTestFixture simpleExplicitFieldFixture() { diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigTests.java deleted file mode 100644 index 0132b4cda6..0000000000 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigTests.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.brooklyn.util.yoml.tests; - -import java.util.Collection; -import java.util.Map; -import java.util.Set; - -import org.apache.brooklyn.config.ConfigInheritance; -import org.apache.brooklyn.config.ConfigKey; -import org.apache.brooklyn.test.Asserts; -import org.apache.brooklyn.util.collections.Jsonya; -import org.apache.brooklyn.util.collections.MutableList; -import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.yoml.YomlSerializer; -import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testng.annotations.Test; - -import com.google.common.base.Predicate; -import com.google.common.reflect.TypeToken; - -/** Tests that the default serializers can read/write types and fields. - *

- * And shows how to use them at a low level. - */ -public class YomlConfigTests { - - @SuppressWarnings("unused") - private static final Logger log = LoggerFactory.getLogger(YomlConfigTests.class); - - static class MockConfigKey implements ConfigKey { - String name; - Class type; - T defaultValue; - - public MockConfigKey(Class type, String name) { - this.name = name; - this.type = type; - } - - @Override public String getDescription() { return null; } - @Override public String getName() { return name; } - @Override public Collection getNameParts() { return MutableList.of(name); } - @Override public TypeToken getTypeToken() { return TypeToken.of(type); } - @Override public Class getType() { return type; } - - @Override public String getTypeName() { throw new UnsupportedOperationException(); } - @Override public T getDefaultValue() { return defaultValue; } - @Override public boolean hasDefaultValue() { return defaultValue!=null; } - @Override public boolean isReconfigurable() { return false; } - @Override public ConfigInheritance getTypeInheritance() { return null; } - @Override public ConfigInheritance getParentInheritance() { return null; } - @Override public ConfigInheritance getInheritance() { return null; } - @Override public Predicate getConstraint() { return null; } - @Override public boolean isValueValid(T value) { return true; } - } - - static class S1 { - Map keys = MutableMap.of(); - static ConfigKey K1 = new MockConfigKey(String.class, "k1"); - S1(Map keys) { this.keys.putAll(keys); } - } - static class S2 { - ConfigKey K2; - } - - @Test - public void testRead() { - YomlTestFixture y = YomlTestFixture.newInstance(); - - Set serializers = YomlAnnotations.findSerializerAnnotations(S1.class, "keys", "config"); - y.tr.put("s1", S1.class, serializers); - // TODO autodetect above, then do this -// y.addType("s1", S1.class); - - y.read("{ type: s1, k1: foo }", null); - - Asserts.assertInstanceOf(y.lastReadResult, S1.class); - Asserts.assertEquals(((S1)y.lastReadResult).keys.get("k1"), "foo"); - } - - @Test - public void testWrite() { - YomlTestFixture y = YomlTestFixture.newInstance(); - - Set serializers = YomlAnnotations.findSerializerAnnotations(S1.class, "keys", "config"); - y.tr.put("s1", S1.class, serializers); - // TODO autodetect above, then do this -// y.addType("s1", S1.class); - - S1 s1 = new S1(MutableMap.of("k1", "foo")); - y.write(s1); - YomlTestFixture.assertEqualsIgnoringQuotes(Jsonya.newInstance().add(y.lastWriteResult).toString(), "{ type: s1, k1: foo }", "wrong serialization"); - } - - -} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java index 59b699b0fc..664489dc5c 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java @@ -28,14 +28,24 @@ import org.apache.brooklyn.util.yoml.Yoml; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations; +import org.apache.brooklyn.util.yoml.internal.YomlConfig; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; import org.testng.Assert; public class YomlTestFixture { public static YomlTestFixture newInstance() { return new YomlTestFixture(); } + public static YomlTestFixture newInstance(YomlConfig config) { return new YomlTestFixture(config); } - MockYomlTypeRegistry tr = new MockYomlTypeRegistry(); - Yoml y = Yoml.newInstance(tr); + final MockYomlTypeRegistry tr = new MockYomlTypeRegistry(); + final Yoml y; + + public YomlTestFixture() { + this(YomlConfig.Builder.builder().serializersPostAddDefaults().build()); + } + public YomlTestFixture(YomlConfig config) { + y = Yoml.newInstance(YomlConfig.Builder.builder(config).typeRegistry(tr).build()); + } Object writeObject; String writeObjectExpectedType; @@ -112,12 +122,24 @@ static void assertEqualsIgnoringQuotes(Object s1, Object s2, String message) { public YomlTestFixture addTypeWithAnnotations(Class type) { return addTypeWithAnnotations(null, type); } - public YomlTestFixture addTypeWithAnnotations(String name, Class type) { - Set serializers = YomlAnnotations.findSerializerAnnotations(type, null, null); - for (String n: YomlAnnotations.findTypeNamesFromAnnotations(type, name, false)) { + public YomlTestFixture addTypeWithAnnotations(String optionalName, Class type) { + Set serializers = YomlAnnotations.findSerializerAnnotations(type); + for (String n: YomlAnnotations.findTypeNamesFromAnnotations(type, optionalName, false)) { tr.put(n, type, serializers); } return this; } + public YomlTestFixture addTypeWithAnnotationsAndConfig(String optionalName, Class type, + Map configFieldsToKeys) { + Set serializers = YomlAnnotations.findSerializerAnnotations(type); + for (Map.Entry entry: configFieldsToKeys.entrySet()) { + serializers.addAll( InstantiateTypeFromRegistryUsingConfigMap.newConfigKeyClassScanningSerializers( + entry.getKey(), entry.getValue(), true) ); + } + for (String n: YomlAnnotations.findTypeNamesFromAnnotations(type, optionalName, false)) { + tr.put(n, type, serializers); + } + return this; + } } From 106203ff3f833721c18fa080f83e8e861854cd57 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 12 Sep 2016 17:51:39 +0100 Subject: [PATCH 36/77] brooklyn yoml supports ConfigBag as well as map constructors and tweak transformer so tests pass --- .../camp/yoml/BrooklynYomlAnnotations.java | 46 +++++++ .../camp/yoml/BrooklynYomlTypeRegistry.java | 30 +++-- .../camp/yoml/YomlConstructorConfigBag.java | 47 ++++++++ .../camp/yoml/YomlTypePlanTransformer.java | 26 +++- ...antiateTypeFromRegistryUsingConfigBag.java | 77 ++++++++++++ .../camp/yoml/BrooklynYomlTestFixture.java | 40 +++++++ .../camp/yoml/YomlTypeRegistryTest.java | 43 +++++++ .../typereg/AbstractTypePlanTransformer.java | 1 + .../yoml/annotations/YomlAnnotations.java | 28 +++-- .../yoml/serializers/AllFieldsExplicit.java | 2 +- ...antiateTypeFromRegistryUsingConfigMap.java | 112 ++++++++++-------- .../util/yoml/tests/ConfigKeyTests.java | 2 +- .../util/yoml/tests/YomlTestFixture.java | 13 +- 13 files changed, 390 insertions(+), 77 deletions(-) create mode 100644 camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java create mode 100644 camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConstructorConfigBag.java create mode 100644 camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java new file mode 100644 index 0000000000..00e7a8d05e --- /dev/null +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.yoml; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +import org.apache.brooklyn.camp.yoml.serializers.InstantiateTypeFromRegistryUsingConfigBag; +import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations; + +public class BrooklynYomlAnnotations extends YomlAnnotations { + + public Collection findConfigBagSerializers(Class t) { + YomlConstructorConfigBag ann = t.getAnnotation(YomlConstructorConfigBag.class); + if (ann==null) return Collections.emptyList(); + return new InstantiateTypeFromRegistryUsingConfigBag.Factory().newConfigKeySerializersForType( + t, + ann.value(), ann.writeAsKey()!=null ? ann.writeAsKey() : ann.value(), + ann.validateAheadOfTime(), ann.requireStaticKeys()); + } + + @Override + protected void collectSerializerAnnotationsAtClass(Set result, Class type) { + result.addAll(findConfigBagSerializers(type)); + super.collectSerializerAnnotationsAtClass(result, type); + } + +} diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java index ac3e9dd0e9..a5a6990a0e 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java @@ -1,5 +1,6 @@ /* * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file @@ -15,7 +16,8 @@ * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. - */package org.apache.brooklyn.camp.yoml; + */ +package org.apache.brooklyn.camp.yoml; import java.util.Arrays; import java.util.Collection; @@ -48,7 +50,7 @@ import org.apache.brooklyn.util.yoml.Yoml; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.YomlTypeRegistry; -import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; import org.apache.brooklyn.util.yoml.internal.YomlUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -136,6 +138,8 @@ public Maybe newInstanceMaybe(String typeName, Yoml yoml) { } public Maybe newInstanceMaybe(String typeName, Yoml yoml, RegisteredTypeLoadingContext context) { + // yoml may be null, for java type lookups, but we could potentially get rid of that call path + RegisteredType typeR = registry().get(typeName, context); if (typeR!=null) { @@ -153,6 +157,8 @@ public Maybe newInstanceMaybe(String typeName, Yoml yoml, RegisteredType if (nextContext==null) { // fall through to path below; we have a circular reference, so need to load java instead } else { + if (yoml!=null && yoml.getConfig().getConstructionInstruction()!=null) + nextContext = RegisteredTypeLoadingContexts.builder(nextContext).constructorInstruction(yoml.getConfig().getConstructionInstruction()).build(); return Maybe.of(registry().create(typeR, nextContext, null)); } } @@ -174,7 +180,7 @@ public Maybe newInstanceMaybe(String typeName, Yoml yoml, RegisteredType } } try { - return Maybe.of((Object)t.get().newInstance()); + return ConstructionInstruction.Factory.newDefault(t.get(), yoml==null ? null : yoml.getConfig().getConstructionInstruction()).create(); } catch (Exception e) { return Maybe.absent("Error instantiating type "+typeName+": "+Exceptions.collapseText(e)); } @@ -366,10 +372,14 @@ protected void collectSerializers(Object type, Collection result supers.addAll(((RegisteredType) type).getSuperTypes()); } } else if (type instanceof Class) { - // TODO result.addAll( ... ); based on annotations on the java class - // then do the following if the evaluation above was not recursive -// supers.add(((Class) type).getSuperclass()); -// supers.addAll(Arrays.asList(((Class) type).getInterfaces())); + String name = getTypeNameOfClass((Class)type); + if (name.startsWith("java:")) { + // need to loop through superclasses unless it is a registered type + // TODO result.addAll( ... ); based on annotations on the java class + // then do the following if the evaluation above was not recursive +// supers.add(((Class) type).getSuperclass()); +// supers.addAll(Arrays.asList(((Class) type).getInterfaces())); + } } else { throw new IllegalStateException("Illegal supertype entry "+type+", visiting "+typesVisited); } @@ -400,8 +410,10 @@ public static RegisteredType newYomlRegisteredType(RegisteredTypeKind kind, /** null symbolic name means to take it from annotations or the class name */ public static RegisteredType newYomlRegisteredType(RegisteredTypeKind kind, @Nullable String symbolicName, String version, Class clazz) { - Set names = YomlAnnotations.findTypeNamesFromAnnotations(clazz, symbolicName, false); - Set serializers = YomlAnnotations.findSerializerAnnotations(clazz); + Set names = new BrooklynYomlAnnotations().findTypeNamesFromAnnotations(clazz, symbolicName, false); + + Set serializers = new BrooklynYomlAnnotations().findSerializerAnnotations(clazz, true); + RegisteredType type = BrooklynYomlTypeRegistry.newYomlRegisteredType(kind, // symbolicName, version, names.iterator().next(), version, diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConstructorConfigBag.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConstructorConfigBag.java new file mode 100644 index 0000000000..bbccf2ee77 --- /dev/null +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConstructorConfigBag.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.yoml; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.apache.brooklyn.util.yoml.annotations.YomlConstructorConfigMap; + +/** + * Indicates that a class should be yoml-serialized using a one-arg constructor taking a map or bag of config. + * Similar to {@link YomlConstructorConfigMap} but accepting config-bag constructors + * and defaulting to `brooklyn.config` as the key for unknown config. + */ +@Retention(RUNTIME) +@Target({ TYPE }) +public @interface YomlConstructorConfigBag { + /** YOML needs to know which field contains the config at serialization time. */ + String value(); + /** By default here reads/writes unrecognised key values against `brooklyn.config`. */ + String writeAsKey() default "brooklyn.config"; + + /** Validate that a suitable field and constructor exist, failing fast if not */ + boolean validateAheadOfTime() default true; + + /** Skip if there are no declared config keys (default false) */ + boolean requireStaticKeys() default false; +} diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java index 87e229e7e3..0a9aedbb15 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java @@ -20,8 +20,10 @@ import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; +import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslInterpreter; @@ -31,6 +33,7 @@ import org.apache.brooklyn.core.typereg.AbstractFormatSpecificTypeImplementationPlan; import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer; import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.core.typereg.UnsupportedTypePlanException; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.core.task.ValueResolver; import org.apache.brooklyn.util.exceptions.Exceptions; @@ -39,10 +42,12 @@ import org.apache.brooklyn.util.yoml.Yoml; import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.internal.YomlConfig; import org.yaml.snakeyaml.Yaml; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; /** * Makes it possible for Brooklyn to resolve YOML items, @@ -91,12 +96,19 @@ public YomlTypePlanTransformer() { super(FORMAT, "YOML Brooklyn syntax", "Standard YOML adapters for Apache Brooklyn including OASIS CAMP"); } + private final static Set IGNORE_SINGLE_KEYS = ImmutableSet.of("name", "version"); + @Override protected double scoreForNullFormat(Object planData, RegisteredType type, RegisteredTypeLoadingContext context) { int score = 0; Maybe> plan = RegisteredTypes.getAsYamlMap(planData); if (plan.isPresent()) { - score += 1; + if (plan.get().size()>1 || (plan.get().size()==1 && !IGNORE_SINGLE_KEYS.contains(plan.get().keySet().iterator().next()))) { + // weed out obvious bad plans -- in part so that tests pass + // TODO in future we should give a tiny score to anything else (once we want to enable this as a popular auto-detetction target) +// score += 1; + // but for now we require at least one recognised keyword + } if (plan.get().containsKey("type")) score += 5; if (plan.get().containsKey("services")) score += 2; } @@ -113,14 +125,13 @@ protected double scoreForNonmatchingNonnullFormat(String planFormat, Object plan @Override protected AbstractBrooklynObjectSpec createSpec(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception { // TODO - return null; + throw new UnsupportedTypePlanException("YOML doesn't yet support specs"); } @Override protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception { BrooklynYomlTypeRegistry tr = new BrooklynYomlTypeRegistry(mgmt, context); - Yoml y = Yoml.newInstance(tr); - y.getConfig().coercer = new ValueResolver.ResolvingTypeCoercer(); + Yoml y = Yoml.newInstance(newYomlConfig(mgmt, tr).build()); // TODO could cache the parse, could cache the instantiation instructions Object data = type.getPlan().getPlanData(); @@ -170,6 +181,13 @@ protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext co return y.readFromYamlObject(parsedInput, expectedSuperTypeName); } + + static YomlConfig.Builder newYomlConfig(ManagementContext mgmt, BrooklynYomlTypeRegistry typeRegistry) { + return YomlConfig.Builder.builder().typeRegistry(typeRegistry). + serializersPostAddDefaults(). + // TODO any custom serializers? + coercer(new ValueResolver.ResolvingTypeCoercer()); + } @Override public double scoreForTypeDefinition(String formatCode, Object catalogData) { diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java new file mode 100644 index 0000000000..6847216014 --- /dev/null +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.yoml.serializers; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.Map; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; + +public class InstantiateTypeFromRegistryUsingConfigBag extends InstantiateTypeFromRegistryUsingConfigMap { + + + public static class Factory extends InstantiateTypeFromRegistryUsingConfigMap.Factory { + protected InstantiateTypeFromRegistryUsingConfigBag newInstance() { + return new InstantiateTypeFromRegistryUsingConfigBag(); + } + } + + protected Maybe findConstructorMaybe(Class type) { + Maybe c = findConfigBagConstructor(type); + if (c.isPresent()) return c; + Maybe c2 = Reflections.findConstructorMaybe(type, Map.class); + if (c2.isPresent()) return c2; + + return c; + } + protected Maybe findConfigBagConstructor(Class type) { + return Reflections.findConstructorMaybe(type, ConfigBag.class); + } + protected Maybe findFieldMaybe(Class type) { + Maybe f = Reflections.findFieldMaybe(type, fieldNameForConfigInJavaIfPreset); + if (f.isPresent() && !(Map.class.isAssignableFrom(f.get().getType()) || ConfigBag.class.isAssignableFrom(f.get().getType()))) + f = Maybe.absent(); + return f; + } + + @Override + protected Map getRawConfigMap(Field f, Object obj) throws IllegalAccessException { + if (ConfigBag.class.isAssignableFrom(f.getType())) { + return ((ConfigBag)f.get(obj)).getAllConfig(); + } + return super.getRawConfigMap(f, obj); + } + + @Override + protected ConstructionInstruction newConstructor(Class type, Map fieldsFromReadToConstructJava, ConstructionInstruction optionalOuter) { + Maybe constructor = findConfigBagConstructor(type); + if (constructor.isPresent() && ConfigBag.class.isAssignableFrom( (((Constructor)constructor.get()).getParameterTypes()[0]) )) { + return ConstructionInstruction.Factory.newUsingConstructorWithArgs(type, MutableList.of( + ConfigBag.newInstance(fieldsFromReadToConstructJava)), optionalOuter); + } + return super.newConstructor(type, fieldsFromReadToConstructJava, optionalOuter); + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java new file mode 100644 index 0000000000..102dd69f98 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.yoml; + +import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations; +import org.apache.brooklyn.util.yoml.internal.YomlConfig; +import org.apache.brooklyn.util.yoml.tests.YomlTestFixture; + +public class BrooklynYomlTestFixture extends YomlTestFixture { + + public static YomlTestFixture newInstance() { return new BrooklynYomlTestFixture(); } + public static YomlTestFixture newInstance(YomlConfig config) { return new BrooklynYomlTestFixture(config); } + + public BrooklynYomlTestFixture() {} + public BrooklynYomlTestFixture(YomlConfig config) { + super(config); + } + + @Override + protected YomlAnnotations annotationsProvider() { + return new BrooklynYomlAnnotations(); + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java index e98c7d6c8e..5dbe4f9dce 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java @@ -19,14 +19,18 @@ package org.apache.brooklyn.camp.yoml; import java.util.Arrays; +import java.util.Map; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; import org.apache.brooklyn.api.typereg.RegisteredType; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport; import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; import org.apache.brooklyn.core.typereg.JavaClassNameTypePlanTransformer.JavaClassNameTypeImplementationPlan; import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.javalang.JavaClassNames; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; @@ -148,4 +152,43 @@ public void testInstantiateAnnotatedYoml() { Assert.assertEquals( ((ItemAn)x).name, "bob" ); } + + @YomlConstructorConfigBag(value="config", writeAsKey="extraConfig") + static class ConfigurableExampleFromBag { + ConfigBag config = ConfigBag.newInstance(); + ConfigurableExampleFromBag(ConfigBag bag) { config.putAll(bag); } + static ConfigKey S = ConfigKeys.newStringConfigKey("s"); + static ConfigKey CB = ConfigKeys.newConfigKey(ConfigurableExampleFromBag.class, "bag"); + @Override + public boolean equals(Object obj) { + return (getClass().equals(obj.getClass())) && config.getAllConfig().equals(((ConfigurableExampleFromBag)obj).config.getAllConfig()); + } + @Override + public String toString() { + return super.toString()+"["+config+"]"; + } + } + + @Test + public void testReadWriteAnnotation() { + BrooklynYomlTestFixture.newInstance() + .addTypeWithAnnotations("bag-example", ConfigurableExampleFromBag.class) + .reading("{ type: bag-example, s: foo }").writing( + new ConfigurableExampleFromBag(ConfigBag.newInstance().configure(ConfigurableExampleFromBag.S, "foo"))) + .doReadWriteAssertingJsonMatch(); + } + + static class ConfigurableExampleFromMap extends ConfigurableExampleFromBag { + ConfigurableExampleFromMap(Map bag) { super(ConfigBag.newInstance(bag)); } + } + + @Test + public void testReadWriteAnnotationMapConstructorInherited() { + BrooklynYomlTestFixture.newInstance() + .addTypeWithAnnotations("bag-example", ConfigurableExampleFromMap.class) + .reading("{ type: bag-example, s: foo }").writing( + new ConfigurableExampleFromMap(ConfigBag.newInstance().configure(ConfigurableExampleFromBag.S, "foo").getAllConfig())) + .doReadWriteAssertingJsonMatch(); + } + } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java b/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java index 5ba36b3951..760413cc0b 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/AbstractTypePlanTransformer.java @@ -103,6 +103,7 @@ public Object create(final RegisteredType type, final RegisteredTypeLoadingConte @Override protected Object visitSpec() { try { AbstractBrooklynObjectSpec result = createSpec(type, context); + if (result==null) throw new UnsupportedTypePlanException("Transformer returned null for "+type); // see notes on catalogItemIdIfNotNull result.catalogItemIdIfNotNull(type.getId()); return result; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java index 56494f3031..2b02ea2b82 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java @@ -36,7 +36,7 @@ public class YomlAnnotations { - public static Set findTypeNamesFromAnnotations(Class type, String optionalDefaultPreferredTypeName, boolean includeJavaTypeNameEvenIfOthers) { + public Set findTypeNamesFromAnnotations(Class type, String optionalDefaultPreferredTypeName, boolean includeJavaTypeNameEvenIfOthers) { MutableSet names = MutableSet.of(); Alias overallAlias = type.getAnnotation(Alias.class); @@ -56,7 +56,7 @@ public static Set findTypeNamesFromAnnotations(Class type, String opt return names; } - public static Collection findExplicitFieldSerializers(Class t, boolean requireAnnotation) { + public Collection findExplicitFieldSerializers(Class t, boolean requireAnnotation) { List result = MutableList.of(); Map fields = YomlUtils.getAllNonTransientNonStaticFields(t, null); for (Map.Entry f: fields.entrySet()) { @@ -66,19 +66,30 @@ public static Collection findExplicitFieldSerializers(C return result; } - public static Collection findConfigMapSerializers(Class t) { + public Collection findConfigMapSerializers(Class t) { YomlConstructorConfigMap ann = t.getAnnotation(YomlConstructorConfigMap.class); if (ann==null) return Collections.emptyList(); - return InstantiateTypeFromRegistryUsingConfigMap.newConfigKeySerializersForType( + return new InstantiateTypeFromRegistryUsingConfigMap.Factory().newConfigKeySerializersForType( t, ann.value(), ann.writeAsKey()!=null ? ann.writeAsKey() : ann.value(), ann.validateAheadOfTime(), ann.requireStaticKeys()); } - public static Set findSerializerAnnotations(Class type) { - + /** Adds the default set of serializer annotations */ + public Set findSerializerAnnotations(Class type, boolean recurseUpIfEmpty) { Set result = MutableSet.of(); - + if (type==null) return result; + + collectSerializerAnnotationsAtClass(result, type); + boolean canRecurse = result.isEmpty(); + + if (recurseUpIfEmpty && canRecurse) { + result.addAll(findSerializerAnnotations(type.getSuperclass(), recurseUpIfEmpty)); + } + return result; + } + + protected void collectSerializerAnnotationsAtClass(Set result, Class type) { // if it takes a config map result.addAll(findConfigMapSerializers(type)); @@ -87,8 +98,7 @@ public static Set findSerializerAnnotations(Class type) { result.addAll(findExplicitFieldSerializers(type, allFields==null)); // (so far the above is the only type of serializer we pick up from annotations) - - return result; + // subclasses can extend } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java index f4831a9bf5..25ada04016 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java @@ -46,7 +46,7 @@ protected void run() { blackboard.put(DoneAllFieldsExplicit.class.getName(), new DoneAllFieldsExplicit()); SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers( - YomlAnnotations.findExplicitFieldSerializers(getJavaObject().getClass(), false)); + new YomlAnnotations().findExplicitFieldSerializers(getJavaObject().getClass(), false)); context.phaseRestart(); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java index 9e41fe3a83..da8cb1f9c0 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java @@ -42,55 +42,68 @@ public class InstantiateTypeFromRegistryUsingConfigMap extends InstantiateTypeFr public static final String PHASE_INSTANTIATE_TYPE_DEFERRED = "handling-type-deferred-after-config"; - String keyNameForConfigWhenSerialized = null; - String fieldNameForConfigInJavaIfPreset = null; + protected String keyNameForConfigWhenSerialized = null; + protected String fieldNameForConfigInJavaIfPreset = null; boolean staticKeysRequired; // don't currently fully support inferring setup from annotations; we need the field above. // easily could automate with a YomlConfigMap annotation - but for now make it explicit // (for now this field can be used to load explicit config keys, if the field name is supplied) boolean inferByScanning = false; - - /** creates a set of serializers handling config for any type, with the given field/key combination; - * the given field will be checked at serialization time to determine whether this is applicable */ - public static Set newConfigKeyClassScanningSerializers( - String fieldNameForConfigInJava, String keyNameForConfigWhenSerialized, boolean requireStaticKeys) { - - return newConfigKeySerializersForType(null, - fieldNameForConfigInJava, keyNameForConfigWhenSerialized, - false, requireStaticKeys); - } - /** creates a set of serializers handling config for the given type, for use in a type-specific serialization, - * permitting multiple field/key combos; if the given field is not found, the pair is excluded here */ - public static Set newConfigKeySerializersForType( - Class type, - String fieldNameForConfigInJava, String keyNameForConfigWhenSerialized, - boolean validateAheadOfTime, boolean requireStaticKeys) { + public static class Factory { - MutableSet result = MutableSet.of(); - if (fieldNameForConfigInJava==null) return result; - InstantiateTypeFromRegistryUsingConfigMap instantiator = new InstantiateTypeFromRegistryUsingConfigMap(); - instantiator.fieldNameForConfigInJavaIfPreset = fieldNameForConfigInJava; - if (validateAheadOfTime) { - instantiator.findFieldMaybe(type).get(); - instantiator.findConstructorMaybe(type).get(); + /** creates a set of serializers handling config for any type, with the given field/key combination; + * the given field will be checked at serialization time to determine whether this is applicable */ + public Set newConfigKeyClassScanningSerializers( + String fieldNameForConfigInJava, String keyNameForConfigWhenSerialized, boolean requireStaticKeys) { + + return findSerializers(null, + fieldNameForConfigInJava, keyNameForConfigWhenSerialized, + false, requireStaticKeys); } - instantiator.keyNameForConfigWhenSerialized = keyNameForConfigWhenSerialized; - instantiator.staticKeysRequired = false; - if (type!=null) { - instantiator.inferByScanning = false; - Map keys = ExplicitConfigKeySerializer.findExplicitConfigKeySerializers(keyNameForConfigWhenSerialized, type); - result.addAll(keys.values()); - } else { - instantiator.inferByScanning = true; + /** creates a set of serializers handling config for the given type, for use in a type-specific serialization, + * permitting multiple field/key combos; if the given field is not found, the pair is excluded here */ + public Set newConfigKeySerializersForType( + Class type, + String fieldNameForConfigInJava, String keyNameForConfigWhenSerialized, + boolean validateAheadOfTime, boolean requireStaticKeys) { + return findSerializers(type, fieldNameForConfigInJava, keyNameForConfigWhenSerialized, validateAheadOfTime, requireStaticKeys); } - result.add(new ConfigInMapUnderConfigSerializer(keyNameForConfigWhenSerialized)); - result.add(instantiator); - - return result; + protected Set findSerializers( + Class type, + String fieldNameForConfigInJava, String keyNameForConfigWhenSerialized, + boolean validateAheadOfTime, boolean requireStaticKeys) { + MutableSet result = MutableSet.of(); + if (fieldNameForConfigInJava==null) return result; + InstantiateTypeFromRegistryUsingConfigMap instantiator = newInstance(); + instantiator.fieldNameForConfigInJavaIfPreset = fieldNameForConfigInJava; + if (validateAheadOfTime) { + instantiator.findFieldMaybe(type).get(); + instantiator.findConstructorMaybe(type).get(); + } + instantiator.keyNameForConfigWhenSerialized = keyNameForConfigWhenSerialized; + instantiator.staticKeysRequired = false; + + if (type!=null) { + instantiator.inferByScanning = false; + Map keys = ExplicitConfigKeySerializer.findExplicitConfigKeySerializers(keyNameForConfigWhenSerialized, type); + result.addAll(keys.values()); + } else { + instantiator.inferByScanning = true; + } + + result.add(new ConfigInMapUnderConfigSerializer(keyNameForConfigWhenSerialized)); + result.add(instantiator); + + return result; + } + + protected InstantiateTypeFromRegistryUsingConfigMap newInstance() { + return new InstantiateTypeFromRegistryUsingConfigMap(); + } } protected InstantiateTypeFromRegistryUsingConfigMap() {} @@ -148,9 +161,7 @@ protected void readFinallyCreate() { Preconditions.checkNotNull(keyNameForConfigWhenSerialized); YomlConfig newConfig = YomlConfig.Builder.builder(config).constructionInstruction( - ConstructionInstruction.Factory.newUsingConstructorWithArgs( - type, MutableList.of(fib.fieldsFromReadToConstructJava), config.getConstructionInstruction())) - .build(); + newConstructor(type, fib.fieldsFromReadToConstructJava, config.getConstructionInstruction())).build(); Maybe resultM = config.getTypeRegistry().newInstanceMaybe(fib.typeNameFromReadToConstructJavaLater, Yoml.newInstance(newConfig)); @@ -174,14 +185,11 @@ protected boolean canDoWrite() { protected boolean hasValidConfigFieldSoWeCanWriteConfigMap() { if (fieldNameForConfigInJavaIfPreset!=null) { - // check that the given field exists and is usable - Maybe f = Reflections.findFieldMaybe(getJavaObject().getClass(), fieldNameForConfigInJavaIfPreset); - if (f.isAbsent()) return false; - if (!Map.class.isAssignableFrom( f.get().getType() )) return false; + return findFieldMaybe(getJavaObject().getClass()).isPresent(); } - // support autodetect here (will fail later if not discoverable; could discover here) - return true; + // if supporting autodetect of the field, do the test here + return false; } @Override @@ -205,8 +213,7 @@ protected void writingPopulateBlackboard() { JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); Field f = Reflections.findFieldMaybe(getJavaObject().getClass(), fieldNameForConfigInJavaIfPreset).get(); f.setAccessible(true); - @SuppressWarnings("unchecked") - Map configMap = (Map)f.get(getJavaObject()); + Map configMap = getRawConfigMap(f, getJavaObject()); if (configMap!=null) { fib.configToWriteFromJava = MutableMap.copyOf(configMap); } @@ -230,6 +237,11 @@ protected void writingInsertPhases() { } + @SuppressWarnings("unchecked") + protected Map getRawConfigMap(Field f, Object obj) throws IllegalAccessException { + return (Map)f.get(obj); + } + /** configurable if it has a map constructor and at least one public static config key */ protected boolean isConfigurable(Class type) { if (type==null) return false; @@ -249,4 +261,8 @@ protected Maybe findConstructorMaybe(Class type) { return Reflections.findConstructorMaybe(type, Map.class); } + protected ConstructionInstruction newConstructor(Class type, Map fieldsFromReadToConstructJava, ConstructionInstruction optionalOuter) { + return ConstructionInstruction.Factory.newUsingConstructorWithArgs(type, MutableList.of(fieldsFromReadToConstructJava), optionalOuter); + } + } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConfigKeyTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConfigKeyTests.java index b6fbbad4fa..52ac34aadf 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConfigKeyTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConfigKeyTests.java @@ -146,7 +146,7 @@ public void testReadWriteNested() { @Test public void testReadWriteNestedGlobalConfigKeySupport() { YomlTestFixture y = YomlTestFixture.newInstance(YomlConfig.Builder.builder() - .serializersPostAdd(InstantiateTypeFromRegistryUsingConfigMap.newConfigKeyClassScanningSerializers("keys", "config", true)) + .serializersPostAdd(new InstantiateTypeFromRegistryUsingConfigMap.Factory().newConfigKeyClassScanningSerializers("keys", "config", true)) .serializersPostAddDefaults().build()); y.addTypeWithAnnotations("s1", S1.class) .addTypeWithAnnotations("s2", S2.class) diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java index 664489dc5c..b6a6d46d57 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java @@ -123,23 +123,26 @@ public YomlTestFixture addTypeWithAnnotations(Class type) { return addTypeWithAnnotations(null, type); } public YomlTestFixture addTypeWithAnnotations(String optionalName, Class type) { - Set serializers = YomlAnnotations.findSerializerAnnotations(type); - for (String n: YomlAnnotations.findTypeNamesFromAnnotations(type, optionalName, false)) { + Set serializers = annotationsProvider().findSerializerAnnotations(type, true); + for (String n: new YomlAnnotations().findTypeNamesFromAnnotations(type, optionalName, false)) { tr.put(n, type, serializers); } return this; } public YomlTestFixture addTypeWithAnnotationsAndConfig(String optionalName, Class type, Map configFieldsToKeys) { - Set serializers = YomlAnnotations.findSerializerAnnotations(type); + Set serializers = annotationsProvider().findSerializerAnnotations(type, true); for (Map.Entry entry: configFieldsToKeys.entrySet()) { - serializers.addAll( InstantiateTypeFromRegistryUsingConfigMap.newConfigKeyClassScanningSerializers( + serializers.addAll( new InstantiateTypeFromRegistryUsingConfigMap.Factory().newConfigKeyClassScanningSerializers( entry.getKey(), entry.getValue(), true) ); } - for (String n: YomlAnnotations.findTypeNamesFromAnnotations(type, optionalName, false)) { + for (String n: new YomlAnnotations().findTypeNamesFromAnnotations(type, optionalName, false)) { tr.put(n, type, serializers); } return this; } + protected YomlAnnotations annotationsProvider() { + return new YomlAnnotations(); + } } From 6c1522699325a5fe36114a90bdc3dbfe7e8ef4fa Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 13 Sep 2016 11:52:05 +0100 Subject: [PATCH 37/77] add/improve convert-map, and major renames (from "ExplicitField" to "TopLevelField" etc) --- .../camp/yoml/BrooklynYomlAnnotations.java | 6 +- ...Bag.java => YomlConfigBagConstructor.java} | 6 +- ...antiateTypeFromRegistryUsingConfigBag.java | 3 +- .../yoml/BrooklynDslInYomlStringPlanTest.java | 4 +- .../yoml/ObjectYomlInBrooklynDslTest.java | 4 +- .../camp/yoml/YomlTypeRegistryTest.java | 6 +- .../yoml/annotations/DefaultKeyValue.java | 39 ++++++ ...pLevel.java => YomlAllFieldsTopLevel.java} | 2 +- .../yoml/annotations/YomlAnnotations.java | 34 +++-- ...gMap.java => YomlConfigMapConsructor.java} | 2 +- .../yoml/annotations/YomlSingletonMap.java | 63 +++++++++ ...AtTopLevel.java => YomlTopLevelField.java} | 4 +- ...dsExplicit.java => AllFieldsTopLevel.java} | 14 +- .../ConfigInMapUnderConfigSerializer.java | 2 +- .../yoml/serializers/ConvertSingletonMap.java | 96 +++++++++++--- .../InstantiateTypeFromRegistry.java | 3 + ...antiateTypeFromRegistryUsingConfigMap.java | 10 +- .../yoml/serializers/InstantiateTypeList.java | 28 ++-- ....java => TopLevelConfigKeySerializer.java} | 18 +-- ...izer.java => TopLevelFieldSerializer.java} | 80 +++++------ ...ard.java => TopLevelFieldsBlackboard.java} | 20 +-- .../org/apache/brooklyn/util/yoml/sketch.md | 119 +++++++++-------- .../yoml/tests/ConvertSingletonMapTests.java | 53 +++++++- ...ests.java => TopLevelConfigKeysTests.java} | 8 +- ...eldTests.java => TopLevelFieldsTests.java} | 124 +++++++++--------- .../util/yoml/tests/YomlAnnotationTests.java | 24 ++-- ...apListTests.java => YomlMapListTests.java} | 12 +- 27 files changed, 505 insertions(+), 279 deletions(-) rename camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/{YomlConstructorConfigBag.java => YomlConfigBagConstructor.java} (89%) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/DefaultKeyValue.java rename utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/{YomlAllFieldsAtTopLevel.java => YomlAllFieldsTopLevel.java} (96%) rename utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/{YomlConstructorConfigMap.java => YomlConfigMapConsructor.java} (97%) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlSingletonMap.java rename utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/{YomlFieldAtTopLevel.java => YomlTopLevelField.java} (90%) rename utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/{AllFieldsExplicit.java => AllFieldsTopLevel.java} (78%) rename utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/{ExplicitConfigKeySerializer.java => TopLevelConfigKeySerializer.java} (87%) rename utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/{ExplicitFieldSerializer.java => TopLevelFieldSerializer.java} (80%) rename utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/{ExplicitFieldsBlackboard.java => TopLevelFieldsBlackboard.java} (92%) rename utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/{ConfigKeyTests.java => TopLevelConfigKeysTests.java} (96%) rename utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/{ExplicitFieldTests.java => TopLevelFieldsTests.java} (74%) rename utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/{MapListTests.java => YomlMapListTests.java} (97%) diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java index 00e7a8d05e..82f746b0f2 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java @@ -28,8 +28,8 @@ public class BrooklynYomlAnnotations extends YomlAnnotations { - public Collection findConfigBagSerializers(Class t) { - YomlConstructorConfigBag ann = t.getAnnotation(YomlConstructorConfigBag.class); + public Collection findConfigBagConstructorSerializers(Class t) { + YomlConfigBagConstructor ann = t.getAnnotation(YomlConfigBagConstructor.class); if (ann==null) return Collections.emptyList(); return new InstantiateTypeFromRegistryUsingConfigBag.Factory().newConfigKeySerializersForType( t, @@ -39,7 +39,7 @@ public Collection findConfigBagSerializers(Class t) { @Override protected void collectSerializerAnnotationsAtClass(Set result, Class type) { - result.addAll(findConfigBagSerializers(type)); + result.addAll(findConfigBagConstructorSerializers(type)); super.collectSerializerAnnotationsAtClass(result, type); } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConstructorConfigBag.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConfigBagConstructor.java similarity index 89% rename from camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConstructorConfigBag.java rename to camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConfigBagConstructor.java index bbccf2ee77..9d17cbdd1a 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConstructorConfigBag.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConfigBagConstructor.java @@ -24,16 +24,16 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; -import org.apache.brooklyn.util.yoml.annotations.YomlConstructorConfigMap; +import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConsructor; /** * Indicates that a class should be yoml-serialized using a one-arg constructor taking a map or bag of config. - * Similar to {@link YomlConstructorConfigMap} but accepting config-bag constructors + * Similar to {@link YomlConfigMapConsructor} but accepting config-bag constructors * and defaulting to `brooklyn.config` as the key for unknown config. */ @Retention(RUNTIME) @Target({ TYPE }) -public @interface YomlConstructorConfigBag { +public @interface YomlConfigBagConstructor { /** YOML needs to know which field contains the config at serialization time. */ String value(); /** By default here reads/writes unrecognised key values against `brooklyn.config`. */ diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java index 6847216014..b6dccfbd8e 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java @@ -26,12 +26,13 @@ import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; +@Alias("config-bag-constructor") public class InstantiateTypeFromRegistryUsingConfigBag extends InstantiateTypeFromRegistryUsingConfigMap { - public static class Factory extends InstantiateTypeFromRegistryUsingConfigMap.Factory { protected InstantiateTypeFromRegistryUsingConfigBag newInstance() { return new InstantiateTypeFromRegistryUsingConfigBag(); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java index 92e0d1e0d4..cb96ee7597 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java @@ -25,7 +25,7 @@ import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; -import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; @@ -50,7 +50,7 @@ private void add(RegisteredType type, boolean canForce) { registry().addToLocalUnpersistedTypeRegistry(type, canForce); } - @YomlAllFieldsAtTopLevel + @YomlAllFieldsTopLevel public static class ItemA { String name; @Override public String toString() { return super.toString()+"[name="+name+"]"; } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java index 11c0ca290a..ea998cdb9c 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java @@ -27,7 +27,7 @@ import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.javalang.JavaClassNames; -import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; @@ -51,7 +51,7 @@ private void add(RegisteredType type, boolean canForce) { registry().addToLocalUnpersistedTypeRegistry(type, canForce); } - @YomlAllFieldsAtTopLevel + @YomlAllFieldsTopLevel public static class ItemA { String name; /* required for 'object.fields' */ public void setName(String name) { this.name = name; } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java index 5dbe4f9dce..855a4dd341 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java @@ -33,7 +33,7 @@ import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.javalang.JavaClassNames; import org.apache.brooklyn.util.yoml.annotations.Alias; -import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; import org.testng.Assert; import org.testng.annotations.Test; @@ -135,7 +135,7 @@ public void testYomlTypeMissingGiveGoodErrorNested() { } } - @YomlAllFieldsAtTopLevel + @YomlAllFieldsTopLevel @Alias("item-annotated") public static class ItemAn { final static RegisteredType YOML = BrooklynYomlTypeRegistry.newYomlRegisteredType(RegisteredTypeKind.BEAN, @@ -153,7 +153,7 @@ public void testInstantiateAnnotatedYoml() { } - @YomlConstructorConfigBag(value="config", writeAsKey="extraConfig") + @YomlConfigBagConstructor(value="config", writeAsKey="extraConfig") static class ConfigurableExampleFromBag { ConfigBag config = ConfigBag.newInstance(); ConfigurableExampleFromBag(ConfigBag bag) { config.putAll(bag); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/DefaultKeyValue.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/DefaultKeyValue.java new file mode 100644 index 0000000000..0e90aa44b1 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/DefaultKeyValue.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.annotations; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target({ TYPE }) +/** Indicates that default key-value pair should be supplied, e.g. for YomlSingletonMap */ +public @interface DefaultKeyValue { + + String key(); + String val(); + + /** Whether the value should be treated as a string, set directly as the value (the default), + * or whether it should be parsed as YAML and the resulting JSON map/list/primitive used as the value. */ + boolean valNeedsParsing() default false; + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAllFieldsAtTopLevel.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAllFieldsTopLevel.java similarity index 96% rename from utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAllFieldsAtTopLevel.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAllFieldsTopLevel.java index 274ec25dd1..1bf6468b59 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAllFieldsAtTopLevel.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAllFieldsTopLevel.java @@ -28,5 +28,5 @@ @Target({ TYPE }) /** Indicates that all fields should be available at the top-level when reading yoml, * ie none require to be inside a fields block. */ -public @interface YomlAllFieldsAtTopLevel { +public @interface YomlAllFieldsTopLevel { } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java index 2b02ea2b82..b924f31142 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java @@ -31,7 +31,8 @@ import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.internal.YomlUtils; -import org.apache.brooklyn.util.yoml.serializers.ExplicitFieldSerializer; +import org.apache.brooklyn.util.yoml.serializers.ConvertSingletonMap; +import org.apache.brooklyn.util.yoml.serializers.TopLevelFieldSerializer; import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; public class YomlAnnotations { @@ -56,18 +57,18 @@ public Set findTypeNamesFromAnnotations(Class type, String optionalDe return names; } - public Collection findExplicitFieldSerializers(Class t, boolean requireAnnotation) { - List result = MutableList.of(); + public Collection findTopLevelFieldSerializers(Class t, boolean requireAnnotation) { + List result = MutableList.of(); Map fields = YomlUtils.getAllNonTransientNonStaticFields(t, null); for (Map.Entry f: fields.entrySet()) { - if (!requireAnnotation || f.getValue().isAnnotationPresent(YomlFieldAtTopLevel.class)) - result.add(new ExplicitFieldSerializer(f.getKey(), f.getValue())); + if (!requireAnnotation || f.getValue().isAnnotationPresent(YomlTopLevelField.class)) + result.add(new TopLevelFieldSerializer(f.getKey(), f.getValue())); } return result; } - public Collection findConfigMapSerializers(Class t) { - YomlConstructorConfigMap ann = t.getAnnotation(YomlConstructorConfigMap.class); + public Collection findConfigMapConstructorSerializers(Class t) { + YomlConfigMapConsructor ann = t.getAnnotation(YomlConfigMapConsructor.class); if (ann==null) return Collections.emptyList(); return new InstantiateTypeFromRegistryUsingConfigMap.Factory().newConfigKeySerializersForType( t, @@ -75,6 +76,12 @@ public Collection findConfigMapSerializers(Class t) { ann.validateAheadOfTime(), ann.requireStaticKeys()); } + public Collection findSingletonMapSerializers(Class t) { + YomlSingletonMap ann = t.getAnnotation(YomlSingletonMap.class); + if (ann==null) return Collections.emptyList(); + return MutableList.of((YomlSerializer) new ConvertSingletonMap(ann)); + } + /** Adds the default set of serializer annotations */ public Set findSerializerAnnotations(Class type, boolean recurseUpIfEmpty) { Set result = MutableSet.of(); @@ -90,15 +97,14 @@ public Set findSerializerAnnotations(Class type, boolean recu } protected void collectSerializerAnnotationsAtClass(Set result, Class type) { - // if it takes a config map - result.addAll(findConfigMapSerializers(type)); + result.addAll(findSingletonMapSerializers(type)); + + result.addAll(findConfigMapConstructorSerializers(type)); - // explicit fields - YomlAllFieldsAtTopLevel allFields = type.getAnnotation(YomlAllFieldsAtTopLevel.class); - result.addAll(findExplicitFieldSerializers(type, allFields==null)); + YomlAllFieldsTopLevel allFields = type.getAnnotation(YomlAllFieldsTopLevel.class); + result.addAll(findTopLevelFieldSerializers(type, allFields==null)); - // (so far the above is the only type of serializer we pick up from annotations) // subclasses can extend } - + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConstructorConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConfigMapConsructor.java similarity index 97% rename from utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConstructorConfigMap.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConfigMapConsructor.java index e86cbf6898..40de6bd553 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConstructorConfigMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConfigMapConsructor.java @@ -32,7 +32,7 @@ */ @Retention(RUNTIME) @Target({ TYPE }) -public @interface YomlConstructorConfigMap { +public @interface YomlConfigMapConsructor { /** YOML needs to know which field contains the config at serialization time. */ String value(); /** By default YOML reads/writes unrecognised key values against a key with the same name as {@link #value()}. diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlSingletonMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlSingletonMap.java new file mode 100644 index 0000000000..e31971cc70 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlSingletonMap.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.annotations; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.apache.brooklyn.util.yoml.serializers.ConvertSingletonMap; + +/** + * Indicates that a class can be yoml-serialized as a map with a single key, + * where the single-key map is converted to a bigger map such that + * the value of that single key is set under as one key ({@link #keyForKey()}), + * and the value either merged (if a map and {@link #keyForMapValue()} is blank) + * or placed under a different key ({@link #keyForAnyValue()} and other value keys). + *

+ * Default values (.key and .value) are intended for + * use by other serializers. + *

+ * See {@link ConvertSingletonMap}. + */ +@Retention(RUNTIME) +@Target({ TYPE }) +public @interface YomlSingletonMap { + /** The single key is taken as a value against the key name given here. */ + String keyForKey() default ConvertSingletonMap.DEFAULT_KEY_FOR_KEY; + + /** If value is a primitive or string, place under a key, + * the name of which is given here. */ + String keyForPrimitiveValue() default ""; + /** If value is a list, place under a key, + * the name of which is given here. */ + String keyForListValue() default ""; + /** If value is a map, place under a key, + * the name of which is given here. */ + String keyForMapValue() default ""; + /** Any value (including a map) is placed in a different key, + * the name of which is given here, + * but after more specific types are applied. */ + String keyForAnyValue() default ConvertSingletonMap.DEFAULT_KEY_FOR_VALUE; + + DefaultKeyValue[] defaultValues() default {}; + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFieldAtTopLevel.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlTopLevelField.java similarity index 90% rename from utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFieldAtTopLevel.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlTopLevelField.java index 940a882a74..558103c74c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFieldAtTopLevel.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlTopLevelField.java @@ -27,9 +27,9 @@ @Retention(RUNTIME) @Target({ FIELD }) /** Indicates that this field should be settable at the top-level when reading yoml */ -public @interface YomlFieldAtTopLevel { +public @interface YomlTopLevelField { - // could allow other configuration for the fields in ExplicitField + // could allow configuration for the TopLevelField representing the field this annotates // (that constructor looks at this annotation) } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsTopLevel.java similarity index 78% rename from utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsTopLevel.java index 25ada04016..a9ebd52ea7 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsExplicit.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/AllFieldsTopLevel.java @@ -22,16 +22,16 @@ import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; -/* Adds ExplicitField instances for all fields declared on the type */ -@Alias("all-fields-explicit") -public class AllFieldsExplicit extends YomlSerializerComposition { +/** Adds {@link TopLevelFieldSerializer} instances for all fields declared on the type */ +@Alias("all-fields-top-level") +public class AllFieldsTopLevel extends YomlSerializerComposition { protected YomlSerializerWorker newWorker() { return new Worker(); } /** marker on blackboard indicating that we have run */ - static class DoneAllFieldsExplicit {} + static class DoneAllFieldsTopLevel {} public class Worker extends YomlSerializerWorker { @@ -40,13 +40,13 @@ public class Worker extends YomlSerializerWorker { protected void run() { if (!hasJavaObject()) return; - if (blackboard.containsKey(DoneAllFieldsExplicit.class.getName())) return; + if (blackboard.containsKey(DoneAllFieldsTopLevel.class.getName())) return; // mark done - blackboard.put(DoneAllFieldsExplicit.class.getName(), new DoneAllFieldsExplicit()); + blackboard.put(DoneAllFieldsTopLevel.class.getName(), new DoneAllFieldsTopLevel()); SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers( - new YomlAnnotations().findExplicitFieldSerializers(getJavaObject().getClass(), false)); + new YomlAnnotations().findTopLevelFieldSerializers(getJavaObject().getClass(), false)); context.phaseRestart(); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java index c940be8f93..6fc57b31c6 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java @@ -95,7 +95,7 @@ protected Map writePrepareGeneralMap() { } protected String getType(String key, Object value) { - ExplicitFieldsBlackboard efb = ExplicitFieldsBlackboard.get(blackboard, getKeyNameForMapOfGeneralValues()); + TopLevelFieldsBlackboard efb = TopLevelFieldsBlackboard.get(blackboard, getKeyNameForMapOfGeneralValues()); Class type = efb.getDeclaredType(key); String optionalType = null; if (type!=null && (value==null || type.isInstance(value))) optionalType = config.getTypeRegistry().getTypeNameOfClass(type); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java index 421f9eccc9..28c608d4d1 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java @@ -21,24 +21,48 @@ import java.util.Map; import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yaml.Yamls; import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.annotations.Alias; -import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; +import org.apache.brooklyn.util.yoml.annotations.DefaultKeyValue; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlSingletonMap; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; import org.apache.brooklyn.util.yoml.internal.YomlUtils; +import com.google.common.collect.Iterables; + /* * key-for-key: type * key-for-primitive-value: type || key-for-any-value: ... || key-for-list-value: || key-for-map-value * || merge-with-map-value - * defaults: { type: explicit-field } + * defaults: { type: top-level-field } */ -@YomlAllFieldsAtTopLevel +@YomlAllFieldsTopLevel @Alias("convert-singleton-map") public class ConvertSingletonMap extends YomlSerializerComposition { public ConvertSingletonMap() { } + public ConvertSingletonMap(YomlSingletonMap ann) { + this(ann.keyForKey(), ann.keyForAnyValue(), ann.keyForPrimitiveValue(), ann.keyForListValue(), ann.keyForMapValue(), + null, extractDefaultMap(ann.defaultValues())); + } + + private static Map extractDefaultMap(DefaultKeyValue[] defaultValues) { + if (defaultValues==null || defaultValues.length==0) return null; + MutableMap result = MutableMap.of(); + for (DefaultKeyValue d: defaultValues) { + Object v = d.val(); + if (d.valNeedsParsing()) { + v = Iterables.getOnlyElement( Yamls.parseAll(d.val()) ); + } + result.put(d.key(), v); + } + return result; + } + public ConvertSingletonMap(String keyForKey, String keyForAnyValue, String keyForPrimitiveValue, String keyForListValue, String keyForMapValue, Boolean mergeWithMapValue, Map defaults) { super(); @@ -62,7 +86,13 @@ protected YomlSerializerWorker newWorker() { String keyForAnyValue = DEFAULT_KEY_FOR_VALUE; String keyForPrimitiveValue; String keyForListValue; + /** if the value against the single key is itself a map, treat it as a value for a key wit this name */ String keyForMapValue; + /** if the value against the single key is itself a map, should we put the {@link #keyForKey} as another entry in the map + * to get the result; the default (if null) and usual behaviour is to do so but not if {@link #keyForMapValue} is set (because we'd use that), and not if there would be a collision + * (ie {@link #keyForKey} is already present) and we have a value for {@link #keyForAnyValue} (so we can use that); + * however we can set true/false to say always merge (which will ignore {@link #keyForMapValue}) or never merge + * (which will prevent this serializer from applying unless {@link #keyForMapValue} or {@link #keyForAnyValue} is set) */ Boolean mergeWithMapValue; Map defaults; @@ -90,18 +120,21 @@ public void read() { newYamlMap.put(keyForKey, key); - if (isJsonPrimitiveObject(value) && keyForPrimitiveValue!=null) { + if (isJsonPrimitiveObject(value) && Strings.isNonBlank(keyForPrimitiveValue)) { newYamlMap.put(keyForPrimitiveValue, value); } else if (value instanceof Map) { - boolean merge; - if (mergeWithMapValue==null) merge = ((Map)value).containsKey(keyForKey) || keyForMapValue==null; - else merge = mergeWithMapValue; + boolean merge = isForMerging(value); if (merge) { newYamlMap.putAll((Map)value); } else { - newYamlMap.put(keyForMapValue != null ? keyForMapValue : keyForAnyValue, value); + String keyForThisMap = Strings.isNonBlank(keyForMapValue) ? keyForMapValue : keyForAnyValue; + if (Strings.isBlank(keyForThisMap)) { + // we can't apply + return; + } + newYamlMap.put(keyForThisMap, value); } - } else if (value instanceof Iterable && keyForListValue!=null) { + } else if (value instanceof Iterable && Strings.isNonBlank(keyForListValue)) { newYamlMap.put(keyForListValue, value); } else { newYamlMap.put(keyForAnyValue, value); @@ -113,6 +146,22 @@ public void read() { context.phaseRestart(); } + protected boolean isForMerging(Object value) { + boolean merge; + if (mergeWithMapValue==null) { + // default merge logic (if null): + // * merge if there is no key-for-map-value AND + // * either + // * it's safe, ie there is no collision at the key-for-key key, OR + // * we have to, ie there is no key-for-any-value (default is overridden) + merge = Strings.isBlank(keyForMapValue) && + ( (!((Map)value).containsKey(keyForKey)) || (Strings.isBlank(keyForAnyValue)) ); + } else { + merge = mergeWithMapValue; + } + return merge; + } + String OUR_PHASE = "manipulate-convert-singleton"; public void write() { @@ -149,43 +198,50 @@ public void write() { Object newValue = null; if (yamlMap.size()==1) { + // if after removing the keyForKey and defaults there is just one entry, see if we can abbreviate further + // eg using keyForPrimitiveValue + // generally we can do this if the remaining key equals the explicit key for that category, + // or if there is no key for that category but the any-value key is set and matches the remaining key + // and merging is not disallowed Object remainingKey = yamlMap.keySet().iterator().next(); if (remainingKey!=null) { Object remainingObject = yamlMap.values().iterator().next(); if (remainingObject instanceof Map) { - // NB can only merge to map if merge false or merge null and map key specified + // cannot promote if merge is true if (!Boolean.TRUE.equals(mergeWithMapValue)) { if (remainingKey.equals(keyForMapValue)) { newValue = remainingObject; - } else if (keyForMapValue==null && remainingKey.equals(keyForAnyValue) && Boolean.FALSE.equals(mergeWithMapValue)) { + } else if (Strings.isBlank(keyForMapValue) && remainingKey.equals(keyForAnyValue) && Boolean.FALSE.equals(mergeWithMapValue)) { newValue = remainingObject; } } } else if (remainingObject instanceof Iterable) { if (remainingKey.equals(keyForListValue)) { newValue = remainingObject; - } else if (keyForListValue==null && remainingKey.equals(keyForAnyValue) && Boolean.FALSE.equals(mergeWithMapValue)) { + } else if (Strings.isBlank(keyForListValue) && remainingKey.equals(keyForAnyValue) && Boolean.FALSE.equals(mergeWithMapValue)) { newValue = remainingObject; } } else if (isJsonPrimitiveObject(remainingObject)) { if (remainingKey.equals(keyForPrimitiveValue)) { newValue = remainingObject; - } else if (keyForPrimitiveValue==null && remainingKey.equals(keyForAnyValue) && Boolean.FALSE.equals(mergeWithMapValue)) { + } else if (Strings.isBlank(keyForPrimitiveValue) && remainingKey.equals(keyForAnyValue) && Boolean.FALSE.equals(mergeWithMapValue)) { newValue = remainingObject; } } } } - if (newValue==null && !Boolean.FALSE.equals(mergeWithMapValue)) { - if (keyForMapValue==null && keyForAnyValue==null) { - // if keyFor{Map,Any}Value was supplied it will steal what we want to merge, - // so only apply if those are both null - newValue = yamlMap; - } + // if we couldn't simplify above, we might still be able to proceed, if: + // * merging is forced; OR + // * merging isn't disallowed, and + // * there is no keyForMapValue, and + // * either there is no keyForAnyValue or it wouldn't cause a collision + // (if keyFor{Map,Any}Value is in effect it will steal what we want to merge) + if (newValue==null && isForMerging(yamlMap)) { + newValue = yamlMap; } - if (newValue==null) return; // doesn't apply + if (newValue==null) return; // this serializer was cancelled, it doesn't apply context.setYamlObject(MutableMap.of(newKey, newValue)); context.phaseRestart(); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java index df444738ce..3604d5b1dc 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java @@ -69,6 +69,9 @@ protected boolean readType(String type) { RuntimeException exc = null; Maybe> jt = config.getTypeRegistry().getJavaTypeMaybe(type); + if (context.seenPhase(YomlContext.StandardPhases.MANIPULATING_TO_LIST)) { + message += ": Failed to manipulate it to be a collection, and error instatiating directly"; + } if (jt.isAbsent()) { exc = ((Maybe.Absent)jt).getException(); } else { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java index da8cb1f9c0..66e791c203 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java @@ -31,12 +31,14 @@ import org.apache.brooklyn.util.yoml.Yoml; import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; import org.apache.brooklyn.util.yoml.internal.YomlConfig; import com.google.common.base.Preconditions; +@Alias("config-map-constructor") /** Special instantiator for when the class's constructor takes a Map of config */ public class InstantiateTypeFromRegistryUsingConfigMap extends InstantiateTypeFromRegistry { @@ -89,7 +91,7 @@ protected Set findSerializers( if (type!=null) { instantiator.inferByScanning = false; - Map keys = ExplicitConfigKeySerializer.findExplicitConfigKeySerializers(keyNameForConfigWhenSerialized, type); + Map keys = TopLevelConfigKeySerializer.findConfigKeySerializers(keyNameForConfigWhenSerialized, type); result.addAll(keys.values()); } else { instantiator.inferByScanning = true; @@ -145,7 +147,7 @@ protected boolean readType(String type) { protected void addExtraTypeSerializers(Class clazz) { if (inferByScanning) { SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers( - ExplicitConfigKeySerializer.findExplicitConfigKeySerializers(keyNameForConfigWhenSerialized, clazz).values()); + TopLevelConfigKeySerializer.findConfigKeySerializers(keyNameForConfigWhenSerialized, clazz).values()); } } @@ -232,7 +234,7 @@ protected void writingPopulateBlackboard() { protected void writingInsertPhases() { super.writingInsertPhases(); // for configs, we need to do this to get type info (and preferred aliases) - context.phaseInsert(ExplicitFieldSerializer.Worker.PREPARING_EXPLICIT_FIELDS); + context.phaseInsert(TopLevelFieldSerializer.Worker.PREPARING_TOP_LEVEL_FIELDS); } } @@ -247,7 +249,7 @@ protected boolean isConfigurable(Class type) { if (type==null) return false; if (findConstructorMaybe(type).isAbsent()) return false; if (findFieldMaybe(type).isAbsent()) return false; - if (staticKeysRequired && ExplicitConfigKeySerializer.findConfigKeys(type).isEmpty()) return false; + if (staticKeysRequired && TopLevelConfigKeySerializer.findConfigKeys(type).isEmpty()) return false; return true; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java index ce94ae38e0..78b318cb36 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java @@ -113,11 +113,11 @@ public void read() { if (expectedJavaType!=null && !Iterable.class.isAssignableFrom(expectedJavaType)) { // expecting something other than a collection if (!(yo instanceof Iterable)) { - // and not given a collection -- just exit + // and not given a collection -- we do nothing return; } else { // but we have a collection - // spawn manipulate-convert-from-list phase + // spawn manipulate-from-list phase if (!context.seenPhase(YomlContext.StandardPhases.MANIPULATING_FROM_LIST)) { context.phaseInsert(YomlContext.StandardPhases.MANIPULATING_FROM_LIST, YomlContext.StandardPhases.HANDLING_TYPE); context.phaseAdvance(); @@ -145,19 +145,19 @@ public void read() { expectedJavaType = oldExpectedType; if (javaType==null || value==null || !Collection.class.isAssignableFrom(javaType) || !Iterable.class.isInstance(value)) { - // only apply below if it's a list type and a list value + // don't let this run, try something else + } else { + // looks like a list in a type-value map + Object jo = newInstance(expectedJavaType, type); + if (jo==null) return; + + context.setJavaObject(jo); + + readIterableInto((Collection)jo, (Iterable)value); + context.phaseAdvance(); + removeTypeAndValueKeys(); return; } - // looks like a list in a type-value map - Object jo = newInstance(expectedJavaType, type); - if (jo==null) return; - - context.setJavaObject(jo); - - readIterableInto((Collection)jo, (Iterable)value); - context.phaseAdvance(); - removeTypeAndValueKeys(); - return; } if (expectedJavaType!=null) { // collection definitely expected but not received @@ -167,7 +167,7 @@ public void read() { } return; } - // otherwise standard InstantiateType will do it + // otherwise standard InstantiateType will try it return; } else { // given a collection, when expecting a collection or no expectation -- read as list diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitConfigKeySerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java similarity index 87% rename from utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitConfigKeySerializer.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java index 467702f6ed..fb4d44b2b6 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitConfigKeySerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java @@ -32,13 +32,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ExplicitConfigKeySerializer extends ExplicitFieldSerializer { +public class TopLevelConfigKeySerializer extends TopLevelFieldSerializer { - private static final Logger log = LoggerFactory.getLogger(ExplicitConfigKeySerializer.class); + private static final Logger log = LoggerFactory.getLogger(TopLevelConfigKeySerializer.class); final String keyNameForConfigWhenSerialized; - public ExplicitConfigKeySerializer(String keyNameForConfigWhenSerialized, ConfigKey configKey, Field optionalFieldForAnnotations) { + public TopLevelConfigKeySerializer(String keyNameForConfigWhenSerialized, ConfigKey configKey, Field optionalFieldForAnnotations) { super(configKey.getName(), optionalFieldForAnnotations); this.keyNameForConfigWhenSerialized = keyNameForConfigWhenSerialized; this.configKey = configKey; @@ -86,7 +86,7 @@ public static Set> findConfigKeys(Class clazz) { } /** only useful in conjuction with {@link InstantiateTypeFromRegistryUsingConfigMap} static serializer factory methods */ - public static Map findExplicitConfigKeySerializers(String keyNameForConfigWhenSerialized, Class clazz) { + public static Map findConfigKeySerializers(String keyNameForConfigWhenSerialized, Class clazz) { MutableMap result = MutableMap.of(); for (Field f: YomlUtils.getAllNonTransientStaticFields(clazz).values()) { @@ -101,7 +101,7 @@ public static Map findExplicitConfigKeySerializers(String if (ck==null) continue; if (result.containsKey(ck.getName())) continue; - result.put(ck.getName(), new ExplicitConfigKeySerializer(keyNameForConfigWhenSerialized, ck, f)); + result.put(ck.getName(), new TopLevelConfigKeySerializer(keyNameForConfigWhenSerialized, ck, f)); } catch (Exception e) { Exceptions.propagateIfFatal(e); @@ -117,15 +117,15 @@ protected YomlSerializerWorker newWorker() { return new Worker(); } - public class Worker extends ExplicitFieldSerializer.Worker { + public class Worker extends TopLevelFieldSerializer.Worker { protected boolean canDoRead() { return !hasJavaObject() && context.willDoPhase(InstantiateTypeFromRegistryUsingConfigMap.PHASE_INSTANTIATE_TYPE_DEFERRED); } @Override - protected void prepareExplicitFields() { - super.prepareExplicitFields(); - getExplicitFieldsBlackboard().setDeclaredTypeIfUnset(fieldName, configKey.getType()); + protected void prepareTopLevelFields() { + super.prepareTopLevelFields(); + getTopLevelFieldsBlackboard().setDeclaredTypeIfUnset(fieldName, configKey.getType()); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java similarity index 80% rename from utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldSerializer.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java index a6dd890a83..02eaf368db 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java @@ -33,7 +33,7 @@ import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.YomlContext.StandardPhases; import org.apache.brooklyn.util.yoml.annotations.Alias; -import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,18 +45,18 @@ *

* On write, after FieldsInMapUnderFields sets the `fields` map, * look for the field name, and rewrite under the preferred alias at the root. */ -@YomlAllFieldsAtTopLevel -@Alias("explicit-field") -public class ExplicitFieldSerializer extends YomlSerializerComposition { +@YomlAllFieldsTopLevel +@Alias("top-level-field") +public class TopLevelFieldSerializer extends YomlSerializerComposition { - private static final Logger log = LoggerFactory.getLogger(ExplicitFieldSerializer.class); + private static final Logger log = LoggerFactory.getLogger(TopLevelFieldSerializer.class); - public ExplicitFieldSerializer() {} - public ExplicitFieldSerializer(Field f) { + public TopLevelFieldSerializer() {} + public TopLevelFieldSerializer(Field f) { this(f.getName(), f); } /** preferred constructor for dealing with shadowed fields using superclass.field naming convention */ - public ExplicitFieldSerializer(String name, Field f) { + public TopLevelFieldSerializer(String name, Field f) { fieldName = keyName = name; Alias alias = f.getAnnotation(Alias.class); @@ -92,7 +92,7 @@ protected YomlSerializerWorker newWorker() { /** aliases to recognise at root in yaml when reading, in addition to {@link #keyName} and normally {@link #fieldName} */ protected List aliases; - /** by default when multiple explicit-field serializers are supplied for the same {@link #fieldName}, all aliases are accepted; + /** by default when multiple top-level-field serializers are supplied for the same {@link #fieldName}, all aliases are accepted; * set this false to restrict to those in the first such serializer */ protected Boolean aliasesInherited; /** by default aliases are taken case-insensitive, with mangling supported, @@ -117,58 +117,58 @@ protected String getKeyNameForMapOfGeneralValues() { public class Worker extends YomlSerializerWorker { - final static String PREPARING_EXPLICIT_FIELDS = "preparing-explicit-fields"; + final static String PREPARING_TOP_LEVEL_FIELDS = "preparing-top-level-fields"; protected String getPreferredKeyName() { - String result = getExplicitFieldsBlackboard().getKeyName(fieldName); + String result = getTopLevelFieldsBlackboard().getKeyName(fieldName); if (result!=null) return result; return fieldName; } - protected ExplicitFieldsBlackboard getExplicitFieldsBlackboard() { - return ExplicitFieldsBlackboard.get(blackboard, getKeyNameForMapOfGeneralValues()); + protected TopLevelFieldsBlackboard getTopLevelFieldsBlackboard() { + return TopLevelFieldsBlackboard.get(blackboard, getKeyNameForMapOfGeneralValues()); } protected Iterable getKeyNameAndAliases() { MutableSet keyNameAndAliases = MutableSet.of(); keyNameAndAliases.addIfNotNull(getPreferredKeyName()); - if (!getExplicitFieldsBlackboard().isAliasesStrict(fieldName)) { + if (!getTopLevelFieldsBlackboard().isAliasesStrict(fieldName)) { keyNameAndAliases.addIfNotNull(fieldName); } - keyNameAndAliases.addAll(getExplicitFieldsBlackboard().getAliases(fieldName)); + keyNameAndAliases.addAll(getTopLevelFieldsBlackboard().getAliases(fieldName)); return keyNameAndAliases; } protected boolean readyForMainEvent() { if (!context.seenPhase(YomlContext.StandardPhases.HANDLING_TYPE)) return false; if (context.willDoPhase(YomlContext.StandardPhases.HANDLING_TYPE)) return false; - if (!context.seenPhase(PREPARING_EXPLICIT_FIELDS)) { + if (!context.seenPhase(PREPARING_TOP_LEVEL_FIELDS)) { if (context.isPhase(YomlContext.StandardPhases.MANIPULATING)) { // interrupt the manipulating phase to do a preparing phase - context.phaseInsert(PREPARING_EXPLICIT_FIELDS, StandardPhases.MANIPULATING); + context.phaseInsert(PREPARING_TOP_LEVEL_FIELDS, StandardPhases.MANIPULATING); context.phaseAdvance(); return false; } } - if (context.isPhase(PREPARING_EXPLICIT_FIELDS)) { - prepareExplicitFields(); + if (context.isPhase(PREPARING_TOP_LEVEL_FIELDS)) { + prepareTopLevelFields(); return false; } - if (getExplicitFieldsBlackboard().isFieldDone(fieldName)) return false; + if (getTopLevelFieldsBlackboard().isFieldDone(fieldName)) return false; if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return false; return true; } - protected void prepareExplicitFields() { - // do the pre-main pass to determine what is required for explicit fields and what the default is - getExplicitFieldsBlackboard().setKeyNameIfUnset(fieldName, keyName); - getExplicitFieldsBlackboard().addAliasIfNotDisinherited(fieldName, alias); - getExplicitFieldsBlackboard().addAliasesIfNotDisinherited(fieldName, aliases); - getExplicitFieldsBlackboard().setAliasesInheritedIfUnset(fieldName, aliasesInherited); - getExplicitFieldsBlackboard().setAliasesStrictIfUnset(fieldName, aliasesStrict); - getExplicitFieldsBlackboard().setConstraintIfUnset(fieldName, constraint); - if (getExplicitFieldsBlackboard().getDefault(fieldName).isAbsent() && defaultValue!=null) { - getExplicitFieldsBlackboard().setUseDefaultFrom(fieldName, ExplicitFieldSerializer.this, defaultValue); + protected void prepareTopLevelFields() { + // do the pre-main pass to determine what is required for top-level fields and what the default is + getTopLevelFieldsBlackboard().setKeyNameIfUnset(fieldName, keyName); + getTopLevelFieldsBlackboard().addAliasIfNotDisinherited(fieldName, alias); + getTopLevelFieldsBlackboard().addAliasesIfNotDisinherited(fieldName, aliases); + getTopLevelFieldsBlackboard().setAliasesInheritedIfUnset(fieldName, aliasesInherited); + getTopLevelFieldsBlackboard().setAliasesStrictIfUnset(fieldName, aliasesStrict); + getTopLevelFieldsBlackboard().setConstraintIfUnset(fieldName, constraint); + if (getTopLevelFieldsBlackboard().getDefault(fieldName).isAbsent() && defaultValue!=null) { + getTopLevelFieldsBlackboard().setUseDefaultFrom(fieldName, TopLevelFieldSerializer.this, defaultValue); } // TODO combine aliases, other items } @@ -191,13 +191,13 @@ public void read() { int keysMatched = 0; for (String aliasO: getKeyNameAndAliases()) { - Set aliasMangles = getExplicitFieldsBlackboard().isAliasesStrict(fieldName) ? + Set aliasMangles = getTopLevelFieldsBlackboard().isAliasesStrict(fieldName) ? Collections.singleton(aliasO) : findAllKeyManglesYamlKeys(aliasO); for (String alias: aliasMangles) { Maybe value = peekFromYamlKeysOnBlackboard(alias, Object.class); if (value.isAbsent()) continue; if (log.isTraceEnabled()) { - log.trace(ExplicitFieldSerializer.this+": found "+alias+" for "+fieldName); + log.trace(TopLevelFieldSerializer.this+": found "+alias+" for "+fieldName); } boolean fieldAlreadyKnown = fields.containsKey(fieldName); if (value.isPresent() && fieldAlreadyKnown) { @@ -215,7 +215,7 @@ public void read() { } if (keysMatched==0) { // set a default if there is one - Maybe value = getExplicitFieldsBlackboard().getDefault(fieldName); + Maybe value = getTopLevelFieldsBlackboard().getDefault(fieldName); if (value.isPresentAndNonNull()) { fields.put(fieldName, value.get()); keysMatched++; @@ -223,7 +223,7 @@ public void read() { } if (keysMatched>0) { // repeat the preparing phase if we set any keys, so that remapping can apply - getExplicitFieldsBlackboard().setFieldDone(fieldName); + getTopLevelFieldsBlackboard().setFieldDone(fieldName); context.phaseInsert(StandardPhases.MANIPULATING); } } @@ -240,15 +240,15 @@ public void write() { */ if (fields==null) return; - Maybe dv = getExplicitFieldsBlackboard().getDefault(fieldName); + Maybe dv = getTopLevelFieldsBlackboard().getDefault(fieldName); Maybe valueToSet; if (!fields.containsKey(fieldName)) { // field not present, so omit (if field is not required and no default, or if default value is present and null) // else write an explicit null - if ((dv.isPresent() && dv.isNull()) || (getExplicitFieldsBlackboard().getConstraint(fieldName).orNull()!=FieldConstraint.REQUIRED && dv.isAbsent())) { + if ((dv.isPresent() && dv.isNull()) || (getTopLevelFieldsBlackboard().getConstraint(fieldName).orNull()!=FieldConstraint.REQUIRED && dv.isAbsent())) { // if default is null, or if not required and no default, we can suppress - getExplicitFieldsBlackboard().setFieldDone(fieldName); + getTopLevelFieldsBlackboard().setFieldDone(fieldName); return; } // default is non-null or field is required, so write the explicit null @@ -258,13 +258,13 @@ public void write() { valueToSet = Maybe.of(fields.remove(fieldName)); if (dv.isPresent() && Objects.equal(dv.get(), valueToSet.get())) { // suppress if it equals the default - getExplicitFieldsBlackboard().setFieldDone(fieldName); + getTopLevelFieldsBlackboard().setFieldDone(fieldName); valueToSet = Maybe.absent(); } } if (valueToSet.isPresent()) { - getExplicitFieldsBlackboard().setFieldDone(fieldName); + getTopLevelFieldsBlackboard().setFieldDone(fieldName); Object oldValue = getYamlMap().put(getPreferredKeyName(), valueToSet.get()); if (oldValue!=null && !oldValue.equals(valueToSet.get())) { throw new IllegalStateException("Conflicting values for `"+getPreferredKeyName()+"`"); @@ -281,6 +281,6 @@ public void write() { @Override public String toString() { - return "explicit-field["+fieldName+"->"+keyName+":"+alias+"/"+aliases+"]"; + return "top-level-field["+fieldName+"->"+keyName+":"+alias+"/"+aliases+"]"; } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldsBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java similarity index 92% rename from utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldsBlackboard.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java index 4a3d190706..2632d17dbb 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ExplicitFieldsBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java @@ -32,19 +32,19 @@ import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.YomlRequirement; import org.apache.brooklyn.util.yoml.YomlSerializer; -import org.apache.brooklyn.util.yoml.serializers.ExplicitFieldSerializer.FieldConstraint; +import org.apache.brooklyn.util.yoml.serializers.TopLevelFieldSerializer.FieldConstraint; -public class ExplicitFieldsBlackboard implements YomlRequirement { +public class TopLevelFieldsBlackboard implements YomlRequirement { - public static final String KEY = ExplicitFieldsBlackboard.class.getCanonicalName(); + public static final String KEY = TopLevelFieldsBlackboard.class.getCanonicalName(); - public static ExplicitFieldsBlackboard get(Map blackboard, String mode) { + public static TopLevelFieldsBlackboard get(Map blackboard, String mode) { Object v = blackboard.get(KEY+":"+mode); if (v==null) { - v = new ExplicitFieldsBlackboard(); + v = new TopLevelFieldsBlackboard(); blackboard.put(KEY+":"+mode, v); } - return (ExplicitFieldsBlackboard) v; + return (TopLevelFieldsBlackboard) v; } private final Map keyNames = MutableMap.of(); @@ -129,12 +129,12 @@ public void setFieldDone(String fieldName) { fieldsDone.add(fieldName); } - public void setUseDefaultFrom(String fieldName, YomlSerializer explicitField, Object defaultValue) { - defaultValueForFieldComesFromSerializer.put(fieldName, explicitField); + public void setUseDefaultFrom(String fieldName, YomlSerializer topLevelField, Object defaultValue) { + defaultValueForFieldComesFromSerializer.put(fieldName, topLevelField); defaultValueOfField.put(fieldName, defaultValue); } - public boolean shouldUseDefaultFrom(String fieldName, YomlSerializer explicitField) { - return explicitField.equals(defaultValueForFieldComesFromSerializer.get(fieldName)); + public boolean shouldUseDefaultFrom(String fieldName, YomlSerializer topLevelField) { + return topLevelField.equals(defaultValueForFieldComesFromSerializer.get(fieldName)); } public Maybe getDefault(String fieldName) { if (!defaultValueOfField.containsKey(fieldName)) return Maybe.absent("no default"); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md index 9fd30a51c0..e8d11a9fdf 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md @@ -186,13 +186,13 @@ Although this would be more sensible: ### Allowing fields at root Type definitions also support specifying additional "serializers", the workers which provide -alternate syntaxes. One common one is the `explicit-field` serializer, allowing fields at +alternate syntaxes. One common one is the `top-level-field` serializer, allowing fields at the root of an instance definition. With this type defined: - id: ez-square type: square serialization: - - type: explicit-field + - type: top-level-field field-name: color You could skip the `fields` item altogether and write: @@ -209,9 +209,9 @@ These are inherited, so we'd probably prefer to have these type definitions: definition: type: java:org.acme.Shape # where `class Shape { String name; String color; }` serialization: - - type: explicit-field + - type: top-level-field field-name: name - - type: explicit-field + - type: top-level-field field-name: color - id: square definition: @@ -224,16 +224,16 @@ These are inherited, so we'd probably prefer to have these type definitions: Serialization takes a list of serializer types. These are applied in order, both for serialization and deserialization, and re-run from the beginning if any are applied. -`explicit-field` says to look at the root as well as in the 'fields' block. It has one required +`top-level-field` says to look at the root as well as in the 'fields' block. It has one required parameter, field-name, and several optional ones, so a sample usage might look like: ``` - - type: explicit-field + - type: top-level-field field-name: color key-name: color # this is used in yaml aliases: [ colour ] # things to accept in yaml as synonyms for key-name; `alias` also accepted aliases-strict: false # if true, means only exact matches on key-name and aliases are accepted, otherwise a set of mangles are applied - aliases-inherited: true # if false, means only take aliases from the first explicit-field serializer for this field-name, otherwise any can be used + aliases-inherited: true # if false, means only take aliases from the first top-level-field serializer for this field-name, otherwise any can be used # TODO items below here are still WIP/planned field-type: string # inferred from java field, but you can constrain further to yaml types constraint: required # currently just supports 'required' (and 'null' not allowed) or blank for none (default), but reserved for future use @@ -263,17 +263,17 @@ and the serializations defined for that type specify: * If it is a map of size exactly one, it is converted to a map as done with `convert-map-to-list` above * If the key `.key` is present and `type` is not defined, that key is renamed to `type` (ignored if `type` is already present) * If the item is a primitive V, it is converted to `{ .value: V }` -* If it is a map with no `type` defined, `type: explicit-field` is added +* If it is a map with no `type` defined, `type: top-level-field` is added This allows the serialization rules defined on the specific type to kick in to handle `.key` or `.value` entries -introduced but not removed. In the case of `explicit-field` (the default type, as shown in the rules above), +introduced but not removed. In the case of `top-level-field` (the default type, as shown in the rules above), this will rename either such key `.value` to `field-name` (and give an error if `field-name` is already present). Thus we can write: serialization: - # explicit fields + # top-level fields color: { alias: colour, description: "The color of the shape", constraint: required } name @@ -298,7 +298,7 @@ This can have some surprising side-effects in occasional edge cases; consider: color: {} # or serialization: - color: explicit-field + color: top-level-field # BAD: this would try to load a type called 'field-name' serialization: @@ -308,14 +308,14 @@ This can have some surprising side-effects in occasional edge cases; consider: - field-name: color alias: colour - # BAD: this ultimately takes "explicit-field" as the "field-name", giving a conflict + # BAD: this ultimately takes "top-level-field" as the "field-name", giving a conflict serialization: - explicit-field: { field-name: color } + top-level-field: { field-name: color } # GOOD options (in addition to those in previous section, but assuming you wanted to say the type explicitly) serialization: - - explicit-field: { field-name: color } + - top-level-field: { field-name: color } # or - - explicit-field: color + - top-level-field: color ``` It does the right thing in most cases, and it serves to illustrate the flexibility of this approach. @@ -328,7 +328,7 @@ Of course if you have any doubt, simply use the long-winded syntax and avoid any ``` serialization: - - type: explicit-field + - type: top-level-field field-name: color ``` @@ -386,7 +386,7 @@ Where the java object is a list, this can correspond to YAML in many ways. New serializations we introduce include `convert-map-to-map-list` (which allows a map value to be supplied), `apply-defaults-in-list` (which ensures a set of keys are present in every entry, using default values wherever the key is absent), -`convert-single-key-maps-in-list` (which gives special behaviour if the list consists +`convert-singleton-maps-in-list` (which gives special behaviour if the list consists entirely of single-key-maps, useful where a map would normally be supplied but there might be key collisions), `if-string-in-list` (which applies `if-string` to every element in the list), and `convert-map-to-singleton-list` (which puts a map into @@ -408,27 +408,27 @@ schema: field-type: list serialization: - # transforms `- color` to `- { explicit-field: color }` which will be interpreted again + # transforms `- color` to `- { top-level-field: color }` which will be interpreted again - type: if-string-in-list - set-key: explicit-field + set-key: top-level-field # alternative implementation of above (more explicit, not relying on `apply-defaults-in-list`) - # transforms `- color` to `- { type: explicit-field, field-name: color }` + # transforms `- color` to `- { type: top-level-field, field-name: color }` - type: if-string-in-list set-key: field-name default: - type: explicit-field + type: top-level-field # describes how a yaml map can correspond to a list # in this example `k: { type: x }` in a map (not a list) # becomes an entry `{ field-name: k, type: x}` in a list # (and same for shorthand `k: x`; however if just `k` is supplied it - # takes a default type `explicit-field`) + # takes a default type `top-level-field`) - type: convert-map-to-map-list key-for-key: field-name key-for-string-value: type # note, only applies if x non-blank default: - type: explicit-field # note: needed to prevent collision with `convert-single-key-in-list` + type: top-level-field # note: needed to prevent collision with `convert-single-key-in-list` # if yaml is a list containing all maps swith a single key, treat the key specially # transforms `- x: k` or `- x: { field-name: k }` to `- { type: x, field-name: k }` @@ -440,10 +440,10 @@ schema: # applies any listed unset "default keys" to the given default values, # for every map entry in a list - # here this essentially makes `explicit-field` the default type + # here this essentially makes `top-level-field` the default type - type: apply-defaults-in-list default: - type: explicit-field + type: top-level-field ``` ### Accepting maps, including generics @@ -490,19 +490,40 @@ Note that if interfacing with the existing defaults you wil need to understand t in detail; see implementation notes below. -## Even more further behaviours (not part of MVP) +## Behaviors which are **not** supported (yet) +* multiple references to the same object +* include/exclude if null/empty/default +* controlling deep merge behaviour (currently collisions at keys are not merged) * preventing fields from being set -* type overloading, if string, if number, if map, if list... inferring type, or setting diff fields +* more type overloading, conditionals on patterns and types, setting multiple fields from multiple words * super-types and abstract types (underlying java of `supertypes` must be assignable from underying java of `type`) -* merging ... deep? field-based? -* setting in java class with annotation -* if-list, if-map, if-key-present, etc * fields fetched by getters, written by setters -* include/exclude if null/empty/default +* passing arguments to constructors (besides the single maps) + + +## Serializers reference + +We currently support the following manual serializers, in addition to many which are lower-level built-ins. +These can be set as annotations on the java class, or as serializers parsed and noted in the config, +either on a global or a per-class basis in the registry. + +* `top-level-field` (`@YomlFieldAtTopLevel`) + * means that a field is accepted at the top level (it does not need to be in a `field` block) + * TODO rename this `top-level-field` + +* `all-fields-top-level` (`@YomlAllFieldsAtTopLevel`) + * applies the above to all fields +* `convert-singleton-map` (`@YomlSingletonMap`) + * reads/writes an item as a single-key map where a field value is the key + * particularly useful when working with a list of items to allow a concise multi-entry map syntax -## Implementation Notes +* `config-map-constructor` (`@YomlConfigMapConstructor`) + * indicates that config key static fields should be scanned and passed in a map to the constructor + + +## Implementation notes We have a `Converter` which runs through phases, running through all `Serializer` instances on each phase. Each `Serializer` exposes methods to `read`, `write`, and `document`, and the appropriate method is invoked @@ -532,13 +553,13 @@ Afterwards, a completion check runs across all blackboard items to enable the mo to be shown. + + ### TODO -* annotations (basic is done, but various "if" situations) +* list/map conversion, and tidy up notes above +* rename-key * complex syntax, type as key, etc -* config/data keys - -* defining serializers and linking to brooklyn * infinite loop detection: in serialize loop * handle references, solve infinite loop detection in self-referential writes, with `.reference: ../../OBJ` @@ -549,9 +570,9 @@ to be shown. -## Real World Use Cases +## Old notes -### An Init.d-style entity/effector language +### Draft Use Case: An Init.d-style entity/effector language ``` - id: print-all @@ -571,20 +592,10 @@ to be shown. effector: launch parameters: ... - -- type: entity - fields: - - key: effectors - yamlType: list - yamlGenericType: effector - serializer: - - type: write-list-field - field: effectors -- type: effector ``` - +### Random thoughts (ignore) First, if the `serialization` field (which expects a list) is given a map, the `convert-map-to-list` serializer converts each pair in that map to a list entry as follows: @@ -595,13 +606,13 @@ the `convert-map-to-list` serializer converts each pair in that map to a l key-for-primitive-value: type, || key-for-any-value: ... || key-for-list-value: || key-for-map-value || merge-with-map-value apply-to-singleton-maps: true - defaults: { type: explicit-field } - # explicit-field sets key-for-list-value as aliases + defaults: { type: top-level-field } + # top-level-field sets key-for-list-value as aliases # on serializer convert-singleton-map: { key-for-key: type key-for-primitive-value: type, || key-for-any-value: ... || key-for-list-value: || key-for-map-value || merge-with-map-value - defaults: { type: explicit-field } + defaults: { type: top-level-field } Next, each entry in the list is interpreted as a `serialization` instance, and the serializations defined for that type specify: @@ -616,8 +627,8 @@ and the serializations defined for that type specify: primitive-to-kv-pair primitive-to-kv-pair: { key: .value || value: foo } -* If it is a map with no `type` defined, `type: explicit-field` is added - defaults: { type: explicit-field } +* If it is a map with no `type` defined, `type: top-level-field` is added + defaults: { type: top-level-field } # serializer declares convert-singleton-map ( merge-with-map-value ) NOTES diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java index 5ac851967f..14327a14c8 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java @@ -24,14 +24,16 @@ import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.yoml.serializers.AllFieldsExplicit; +import org.apache.brooklyn.util.yoml.annotations.DefaultKeyValue; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlSingletonMap; +import org.apache.brooklyn.util.yoml.serializers.AllFieldsTopLevel; import org.apache.brooklyn.util.yoml.serializers.ConvertSingletonMap; import org.apache.brooklyn.util.yoml.tests.YomlBasicTests.ShapeWithSize; import org.testng.annotations.Test; import com.google.common.base.Objects; -/** Tests that explicit fields can be set at the outer level in yaml. */ public class ConvertSingletonMapTests { static class ShapeWithTags extends ShapeWithSize { @@ -59,12 +61,12 @@ public boolean equals(Object xo) { */ YomlTestFixture y = YomlTestFixture.newInstance(). addType("shape", ShapeWithTags.class, MutableList.of( - new AllFieldsExplicit(), + new AllFieldsTopLevel(), new ConvertSingletonMap("name", null, "color", "tags", null, null, MutableMap.of("size", 0)))); YomlTestFixture y2 = YomlTestFixture.newInstance(). addType("shape", ShapeWithTags.class, MutableList.of( - new AllFieldsExplicit(), + new AllFieldsTopLevel(), new ConvertSingletonMap("name", "size", "color", "tags", "metadata", null, MutableMap.of("size", 42)))); @@ -98,5 +100,48 @@ public boolean equals(Object xo) { y.write(new ShapeWithTags().name("red-square").color("red"), null) .assertResult("{ type: shape, color: red, name: red-square, size: 0 }"); } + + YomlTestFixture y3 = YomlTestFixture.newInstance(). + addTypeWithAnnotations("shape", ShapeAnn.class); + + @YomlAllFieldsTopLevel + @YomlSingletonMap(keyForKey="name", keyForListValue="tags", keyForPrimitiveValue="color", +// keyForAnyValue="", + defaultValues={@DefaultKeyValue(key="size", val="0", valNeedsParsing=true)}) + static class ShapeAnn extends ShapeWithTags {} + + @Test public void testAnnPrimitiveValue() { + y3.reading("{ red-square: red }", "shape").writing(new ShapeAnn().name("red-square").color("red"), "shape") + .doReadWriteAssertingJsonMatch(); + } + + @Test public void testAnnListValue() { + y3.reading("{ good-square: [ good ] }", "shape").writing(new ShapeAnn().tags("good").name("good-square"), "shape") + .doReadWriteAssertingJsonMatch(); + } + + @Test public void testAnnMapValueMerge() { + y3.reading("{ merge-square: { size: 12 } }", "shape").writing(new ShapeAnn().size(12).name("merge-square"), "shape") + .doReadWriteAssertingJsonMatch(); + } + @Test public void testAnnNothingExtra() { + y3.reading("{ merge-square: { } }", "shape").writing(new ShapeAnn().name("merge-square"), "shape") + .doReadWriteAssertingJsonMatch(); + } + + @Test public void testAnnList() { + y3.reading("[ { one: { size: 1 } }, { two: { size: 2 } } ]", "list").writing( + MutableList.of(new ShapeAnn().name("one").size(1), new ShapeAnn().name("two").size(2)), "list") + .doReadWriteAssertingJsonMatch(); + } + + // TODO + @Test(enabled=false) public void testAnnListCompressed() { + y3.reading("{ one: { size: 1 }, two: { size: 2 } }", "list").writing( + MutableList.of(new ShapeAnn().name("one").size(1), new ShapeAnn().name("two").size(2)), "list") + .doReadWriteAssertingJsonMatch(); + } + + } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConfigKeyTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java similarity index 96% rename from utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConfigKeyTests.java rename to utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java index 52ac34aadf..f50cfb3c25 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConfigKeyTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java @@ -27,7 +27,7 @@ import org.apache.brooklyn.util.collections.Jsonya; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.yoml.annotations.YomlConstructorConfigMap; +import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConsructor; import org.apache.brooklyn.util.yoml.internal.YomlConfig; import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; import org.slf4j.Logger; @@ -41,10 +41,10 @@ *

* And shows how to use them at a low level. */ -public class ConfigKeyTests { +public class TopLevelConfigKeysTests { @SuppressWarnings("unused") - private static final Logger log = LoggerFactory.getLogger(ConfigKeyTests.class); + private static final Logger log = LoggerFactory.getLogger(TopLevelConfigKeysTests.class); static class MockConfigKey implements ConfigKey { String name; @@ -154,7 +154,7 @@ public void testReadWriteNestedGlobalConfigKeySupport() { .doReadWriteAssertingJsonMatch(); } - @YomlConstructorConfigMap(value="keys", writeAsKey="extraConfig") + @YomlConfigMapConsructor(value="keys", writeAsKey="extraConfig") static class S3 extends S1 { S3(Map keys) { super(keys); } static ConfigKey K2 = new MockConfigKey(String.class, "k2"); diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ExplicitFieldTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelFieldsTests.java similarity index 74% rename from utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ExplicitFieldTests.java rename to utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelFieldsTests.java index 3a5c780535..905b938703 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ExplicitFieldTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelFieldsTests.java @@ -25,23 +25,23 @@ import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.yoml.YomlSerializer; -import org.apache.brooklyn.util.yoml.serializers.AllFieldsExplicit; -import org.apache.brooklyn.util.yoml.serializers.ExplicitFieldSerializer; +import org.apache.brooklyn.util.yoml.serializers.AllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.serializers.TopLevelFieldSerializer; import org.apache.brooklyn.util.yoml.tests.YomlBasicTests.Shape; import org.apache.brooklyn.util.yoml.tests.YomlBasicTests.ShapeWithSize; import org.testng.Assert; import org.testng.annotations.Test; -/** Tests that explicit fields can be set at the outer level in yaml. */ -public class ExplicitFieldTests { +/** Tests that top-level fields can be set at the outer level in yaml. */ +public class TopLevelFieldsTests { - public static YomlSerializer explicitFieldSerializer(String yaml) { - return (YomlSerializer) YomlTestFixture.newInstance().read("{ fields: "+yaml+" }", "java:"+ExplicitFieldSerializer.class.getName()).lastReadResult; + public static YomlSerializer topLevelFieldSerializer(String yaml) { + return (YomlSerializer) YomlTestFixture.newInstance().read("{ fields: "+yaml+" }", "java:"+TopLevelFieldSerializer.class.getName()).lastReadResult; } - protected static YomlTestFixture simpleExplicitFieldFixture() { + protected static YomlTestFixture simpletopLevelFieldFixture() { return YomlTestFixture.newInstance(). - addType("shape", Shape.class, MutableList.of(explicitFieldSerializer("{ fieldName: name }"))); + addType("shape", Shape.class, MutableList.of(topLevelFieldSerializer("{ fieldName: name }"))); } static String SIMPLE_IN_WITHOUT_TYPE = "{ name: diamond, fields: { color: black } }"; @@ -51,27 +51,27 @@ protected static YomlTestFixture simpleExplicitFieldFixture() { static Shape SIMPLE_OUT_NAME_ONLY = new Shape().name("diamond"); @Test - public void testReadExplicitField() { - simpleExplicitFieldFixture(). + public void testReadTopLevelField() { + simpletopLevelFieldFixture(). read( SIMPLE_IN_WITHOUT_TYPE, "shape" ). assertResult( SIMPLE_OUT ); } @Test - public void testWriteExplicitField() { - simpleExplicitFieldFixture(). + public void testWriteTopLevelField() { + simpletopLevelFieldFixture(). write( SIMPLE_OUT, "shape" ). assertResult( SIMPLE_IN_WITHOUT_TYPE ); } @Test - public void testReadExplicitFieldNameOnly() { - simpleExplicitFieldFixture(). + public void testReadTopLevelFieldNameOnly() { + simpletopLevelFieldFixture(). read( SIMPLE_IN_NAME_ONLY_WITHOUT_TYPE, "shape" ). assertResult( SIMPLE_OUT_NAME_ONLY ); } @Test - public void testWriteExplicitFieldNameOnly() { - simpleExplicitFieldFixture(). + public void testWriteTopLevelFieldNameOnly() { + simpletopLevelFieldFixture(). write( SIMPLE_OUT_NAME_ONLY, "shape" ). assertResult( SIMPLE_IN_NAME_ONLY_WITHOUT_TYPE ); } @@ -79,25 +79,25 @@ public void testWriteExplicitFieldNameOnly() { static String SIMPLE_IN_WITH_TYPE = "{ type: shape, name: diamond, fields: { color: black } }"; @Test - public void testReadExplicitFieldNoExpectedType() { - simpleExplicitFieldFixture(). + public void testReadTopLevelFieldNoExpectedType() { + simpletopLevelFieldFixture(). read( SIMPLE_IN_WITH_TYPE, null ). assertResult( SIMPLE_OUT); } @Test - public void testWriteExplicitFieldNoExpectedType() { - simpleExplicitFieldFixture(). + public void testWriteTopLevelFieldNoExpectedType() { + simpletopLevelFieldFixture(). write( SIMPLE_OUT, null ). assertResult( SIMPLE_IN_WITH_TYPE ); } - protected static YomlTestFixture commonExplicitFieldFixtureKeyNameAlias() { - return commonExplicitFieldFixtureKeyNameAlias(""); + protected static YomlTestFixture commonTopLevelFieldFixtureKeyNameAlias() { + return commonTopLevelFieldFixtureKeyNameAlias(""); } - protected static YomlTestFixture commonExplicitFieldFixtureKeyNameAlias(String extra) { + protected static YomlTestFixture commonTopLevelFieldFixtureKeyNameAlias(String extra) { return YomlTestFixture.newInstance(). addType("shape", Shape.class, MutableList.of( - explicitFieldSerializer("{ fieldName: name, keyName: shape-name, alias: my-name"+extra+" }"))); + topLevelFieldSerializer("{ fieldName: name, keyName: shape-name, alias: my-name"+extra+" }"))); } static String COMMON_IN_KEY_NAME = "{ shape-name: diamond, fields: { color: black } }"; @@ -110,7 +110,7 @@ protected static YomlTestFixture commonExplicitFieldFixtureKeyNameAlias(String e @Test public void testCommonKeyName() { - commonExplicitFieldFixtureKeyNameAlias(). + commonTopLevelFieldFixtureKeyNameAlias(). reading( COMMON_IN_KEY_NAME, "shape" ). writing( COMMON_OUT, "shape" ). doReadWriteAssertingJsonMatch(); @@ -118,14 +118,14 @@ public void testCommonKeyName() { @Test public void testCommonAlias() { - commonExplicitFieldFixtureKeyNameAlias(). + commonTopLevelFieldFixtureKeyNameAlias(). read( COMMON_IN_ALIAS, "shape" ).assertResult(COMMON_OUT). write( COMMON_OUT, "shape" ).assertResult(COMMON_IN_KEY_NAME); } @Test public void testCommonDefault() { - commonExplicitFieldFixtureKeyNameAlias(", defaultValue: { type: string, value: bob }"). + commonTopLevelFieldFixtureKeyNameAlias(", defaultValue: { type: string, value: bob }"). reading( COMMON_IN_DEFAULT, "shape" ). writing( COMMON_OUT_DEFAULT, "shape" ). doReadWriteAssertingJsonMatch(); @@ -133,7 +133,7 @@ public void testCommonDefault() { @Test public void testNameNotRequired() { - commonExplicitFieldFixtureKeyNameAlias(). + commonTopLevelFieldFixtureKeyNameAlias(). reading( COMMON_IN_NO_NAME, "shape" ). writing( COMMON_OUT_NO_NAME, "shape" ). doReadWriteAssertingJsonMatch(); @@ -142,7 +142,7 @@ public void testNameNotRequired() { @Test public void testNameRequired() { try { - YomlTestFixture x = commonExplicitFieldFixtureKeyNameAlias(", constraint: required") + YomlTestFixture x = commonTopLevelFieldFixtureKeyNameAlias(", constraint: required") .read( COMMON_IN_NO_NAME, "shape" ); Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); } catch (Exception e) { @@ -153,7 +153,7 @@ public void testNameRequired() { @Test public void testAliasConflictNiceError() { try { - YomlTestFixture x = commonExplicitFieldFixtureKeyNameAlias().read( + YomlTestFixture x = commonTopLevelFieldFixtureKeyNameAlias().read( "{ my-name: name-from-alias, shape-name: name-from-key }", "shape" ); Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); } catch (Exception e) { @@ -161,20 +161,20 @@ public void testAliasConflictNiceError() { } } - protected static YomlTestFixture extended0ExplicitFieldFixture(List extras) { - return commonExplicitFieldFixtureKeyNameAlias(", defaultValue: { type: string, value: bob }"). + protected static YomlTestFixture extended0TopLevelFieldFixture(List extras) { + return commonTopLevelFieldFixtureKeyNameAlias(", defaultValue: { type: string, value: bob }"). addType("shape-with-size", "{ type: \"java:"+ShapeWithSize.class.getName()+"\", interfaceTypes: [ shape ] }", - MutableList.copyOf(extras).append(explicitFieldSerializer("{ fieldName: size, alias: shape-size }")) ); + MutableList.copyOf(extras).append(topLevelFieldSerializer("{ fieldName: size, alias: shape-size }")) ); } - protected static YomlTestFixture extended1ExplicitFieldFixture() { - return extended0ExplicitFieldFixture( MutableList.of( - explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name }")) ); + protected static YomlTestFixture extended1TopLevelFieldFixture() { + return extended0TopLevelFieldFixture( MutableList.of( + topLevelFieldSerializer("{ fieldName: name, keyName: shape-w-size-name }")) ); } @Test - public void testExplicitFieldSerializersAreCollected() { - YomlTestFixture ytc = extended1ExplicitFieldFixture(); + public void testTopLevelFieldSerializersAreCollected() { + YomlTestFixture ytc = extended1TopLevelFieldFixture(); Set serializers = MutableSet.of(); ytc.tr.collectSerializers("shape-with-size", serializers, MutableSet.of()); Assert.assertEquals(serializers.size(), 3, "Wrong serializers: "+serializers); @@ -185,7 +185,7 @@ public void testExplicitFieldSerializersAreCollected() { @Test public void testExtendedKeyNameIsUsed() { - extended1ExplicitFieldFixture(). + extended1TopLevelFieldFixture(). reading( EXTENDED_IN_1, null ). writing( EXTENDED_OUT_1, "shape"). doReadWriteAssertingJsonMatch(); @@ -194,7 +194,7 @@ public void testExtendedKeyNameIsUsed() { @Test public void testInheritedAliasIsUsed() { String json = "{ type: shape-with-size, my-name: diamond, size: 2, fields: { color: black } }"; - extended1ExplicitFieldFixture(). + extended1TopLevelFieldFixture(). read( json, null ).assertResult( EXTENDED_OUT_1 ). write( EXTENDED_OUT_1, "shape-w-size" ).assertResult(EXTENDED_IN_1); } @@ -204,7 +204,7 @@ public void testInheritedAliasIsUsed() { @Test public void testOverriddenKeyNameNotUsed() { try { - YomlTestFixture x = extended1ExplicitFieldFixture().read(EXTENDED_IN_ORIGINAL_KEYNAME, null); + YomlTestFixture x = extended1TopLevelFieldFixture().read(EXTENDED_IN_ORIGINAL_KEYNAME, null); Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); } catch (Exception e) { Asserts.expectedFailureContains(e, "shape-name", "diamond"); @@ -215,8 +215,8 @@ public void testOverriddenKeyNameNotUsed() { @Test public void testInheritedKeyNameIsUsed() { - extended0ExplicitFieldFixture( MutableList.of( - explicitFieldSerializer(EXTENDED_TYPEDEF_NEW_ALIAS)) ) + extended0TopLevelFieldFixture( MutableList.of( + topLevelFieldSerializer(EXTENDED_TYPEDEF_NEW_ALIAS)) ) .read(EXTENDED_IN_ORIGINAL_KEYNAME, null).assertResult(EXTENDED_OUT_1) .write(EXTENDED_OUT_1).assertResult(EXTENDED_IN_ORIGINAL_KEYNAME); } @@ -224,8 +224,8 @@ public void testInheritedKeyNameIsUsed() { @Test public void testOverriddenAliasIsRecognised() { String json = "{ type: shape-with-size, new-name: diamond, size: 2, fields: { color: black } }"; - extended0ExplicitFieldFixture( MutableList.of( - explicitFieldSerializer(EXTENDED_TYPEDEF_NEW_ALIAS)) ) + extended0TopLevelFieldFixture( MutableList.of( + topLevelFieldSerializer(EXTENDED_TYPEDEF_NEW_ALIAS)) ) .read( json, null ).assertResult( EXTENDED_OUT_1 ) .write( EXTENDED_OUT_1, "shape-w-size" ).assertResult(EXTENDED_IN_ORIGINAL_KEYNAME); } @@ -236,8 +236,8 @@ public void testOverriddenAliasIsRecognised() { @Test public void testInheritedKeyNameIsUsedWithNewDefault() { String json = "{ size: 2, fields: { color: black } }"; - extended0ExplicitFieldFixture( MutableList.of( - explicitFieldSerializer(EXTENDED_TYPEDEF_NEW_DEFAULT)) ) + extended0TopLevelFieldFixture( MutableList.of( + topLevelFieldSerializer(EXTENDED_TYPEDEF_NEW_DEFAULT)) ) .write(EXTENDED_OUT_NEW_DEFAULT, "shape-with-size").assertResult(json) .read(json, "shape-with-size").assertResult(EXTENDED_OUT_NEW_DEFAULT); } @@ -247,8 +247,8 @@ public void testInheritedAliasIsNotUsedIfRestricted() { // same as testInheritedAliasIsUsed -- except fails because we say aliases-inherited: false String json = "{ type: shape-with-size, my-name: diamond, size: 2, fields: { color: black } }"; try { - YomlTestFixture x = extended0ExplicitFieldFixture( MutableList.of( - explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name, aliasesInherited: false }")) ) + YomlTestFixture x = extended0TopLevelFieldFixture( MutableList.of( + topLevelFieldSerializer("{ fieldName: name, keyName: shape-w-size-name, aliasesInherited: false }")) ) .read( json, null ); Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); } catch (Exception e) { @@ -259,8 +259,8 @@ public void testInheritedAliasIsNotUsedIfRestricted() { @Test public void testFieldNameAsAlias() { String json = "{ type: shape-with-size, name: diamond, size: 2, fields: { color: black } }"; - extended0ExplicitFieldFixture( MutableList.of( - explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name }")) ) + extended0TopLevelFieldFixture( MutableList.of( + topLevelFieldSerializer("{ fieldName: name, keyName: shape-w-size-name }")) ) .read( json, null ).assertResult( EXTENDED_OUT_1 ) .write( EXTENDED_OUT_1 ).assertResult( EXTENDED_IN_1 ); } @@ -269,8 +269,8 @@ public void testFieldNameAsAlias() { public void testFieldNameAsAliasExcludedWhenStrict() { String json = "{ type: shape-with-size, name: diamond, size: 2, fields: { color: black } }"; try { - YomlTestFixture x = extended0ExplicitFieldFixture( MutableList.of( - explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name, aliasesStrict: true }")) ) + YomlTestFixture x = extended0TopLevelFieldFixture( MutableList.of( + topLevelFieldSerializer("{ fieldName: name, keyName: shape-w-size-name, aliasesStrict: true }")) ) .read( json, null ); Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); } catch (Exception e) { @@ -282,8 +282,8 @@ public void testFieldNameAsAliasExcludedWhenStrict() { @Test public void testFieldNameMangled() { - extended0ExplicitFieldFixture( MutableList.of( - explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name }")) ) + extended0TopLevelFieldFixture( MutableList.of( + topLevelFieldSerializer("{ fieldName: name, keyName: shape-w-size-name }")) ) .read( EXTENDED_IN_1_MANGLED, null ).assertResult( EXTENDED_OUT_1 ) .write( EXTENDED_OUT_1 ).assertResult( EXTENDED_IN_1 ); } @@ -291,8 +291,8 @@ public void testFieldNameMangled() { @Test public void testFieldNameManglesExcludedWhenStrict() { try { - YomlTestFixture x = extended0ExplicitFieldFixture( MutableList.of( - explicitFieldSerializer("{ fieldName: name, keyName: shape-w-size-name, aliasesStrict: true }")) ) + YomlTestFixture x = extended0TopLevelFieldFixture( MutableList.of( + topLevelFieldSerializer("{ fieldName: name, keyName: shape-w-size-name, aliasesStrict: true }")) ) .read( EXTENDED_IN_1_MANGLED, null ); Asserts.shouldHaveFailedPreviously("Returned "+x.lastReadResult+" when should have thrown"); } catch (Exception e) { @@ -300,13 +300,13 @@ public void testFieldNameManglesExcludedWhenStrict() { } } - static String SIMPLE_IN_ALL_FIELDS_EXPLICIT = "{ color: black, name: diamond }"; - @Test public void testAllFieldsExplicit() { + static String SIMPLE_IN_ALL_FIELDS_TOP_LEVEL = "{ color: black, name: diamond }"; + @Test public void testAllFieldsTopLevel() { YomlTestFixture y = YomlTestFixture.newInstance(). - addType("shape", Shape.class, MutableList.of(new AllFieldsExplicit())); + addType("shape", Shape.class, MutableList.of(new AllFieldsTopLevel())); - y.read( SIMPLE_IN_ALL_FIELDS_EXPLICIT, "shape" ).assertResult( SIMPLE_OUT ). - write( SIMPLE_OUT, "shape" ).assertResult( SIMPLE_IN_ALL_FIELDS_EXPLICIT ); + y.read( SIMPLE_IN_ALL_FIELDS_TOP_LEVEL, "shape" ).assertResult( SIMPLE_OUT ). + write( SIMPLE_OUT, "shape" ).assertResult( SIMPLE_IN_ALL_FIELDS_TOP_LEVEL ); } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlAnnotationTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlAnnotationTests.java index 377a7e8b14..ad3d179fb3 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlAnnotationTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlAnnotationTests.java @@ -19,8 +19,8 @@ package org.apache.brooklyn.util.yoml.tests; import org.apache.brooklyn.util.yoml.annotations.Alias; -import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsAtTopLevel; -import org.apache.brooklyn.util.yoml.annotations.YomlFieldAtTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlTopLevelField; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.Test; @@ -36,12 +36,12 @@ public class YomlAnnotationTests { @SuppressWarnings("unused") private static final Logger log = LoggerFactory.getLogger(YomlAnnotationTests.class); - static class ExplicitFieldsAtTopLevelExamples { + static class TopLevelFieldsExample { @Alias("shape") static class Shape { - @YomlFieldAtTopLevel + @YomlTopLevelField String name; - @YomlFieldAtTopLevel + @YomlTopLevelField @Alias(value={"kolor","couleur"}, preferred="colour") String color; @@ -65,19 +65,19 @@ public String toString() { @Test public void testYomlFieldsAtTopLevel() { - ExplicitFieldsAtTopLevelExamples.Shape shape = new ExplicitFieldsAtTopLevelExamples.Shape().name("nifty_shape").color("blue"); + TopLevelFieldsExample.Shape shape = new TopLevelFieldsExample.Shape().name("nifty_shape").color("blue"); YomlTestFixture.newInstance(). - addTypeWithAnnotations(ExplicitFieldsAtTopLevelExamples.Shape.class). + addTypeWithAnnotations(TopLevelFieldsExample.Shape.class). read("{ name: nifty_shape, couleur: blue }", "shape").assertResult(shape). write(shape).assertResult("{ type: shape, colour: blue, name: nifty_shape }"); } - static class ExplicitFieldsAllExamples { - @YomlAllFieldsAtTopLevel + static class AllFieldsTopLevelExample { + @YomlAllFieldsTopLevel @Alias("shape") static class Shape { String name; - @YomlFieldAtTopLevel + @YomlTopLevelField @Alias(value={"kolor","couleur"}, preferred="colour") String color; @@ -101,9 +101,9 @@ public String toString() { @Test public void testYomlAllFields() { - ExplicitFieldsAllExamples.Shape shape = new ExplicitFieldsAllExamples.Shape().name("nifty_shape").color("blue"); + AllFieldsTopLevelExample.Shape shape = new AllFieldsTopLevelExample.Shape().name("nifty_shape").color("blue"); YomlTestFixture.newInstance(). - addTypeWithAnnotations(ExplicitFieldsAllExamples.Shape.class). + addTypeWithAnnotations(AllFieldsTopLevelExample.Shape.class). read("{ name: nifty_shape, couleur: blue }", "shape").assertResult(shape). write(shape).assertResult("{ type: shape, colour: blue, name: nifty_shape }"); } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MapListTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlMapListTests.java similarity index 97% rename from utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MapListTests.java rename to utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlMapListTests.java index 92a9774da6..3326a00f5e 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MapListTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlMapListTests.java @@ -29,13 +29,13 @@ import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; -import org.apache.brooklyn.util.yoml.serializers.AllFieldsExplicit; +import org.apache.brooklyn.util.yoml.serializers.AllFieldsTopLevel; import org.apache.brooklyn.util.yoml.tests.YomlBasicTests.Shape; import org.testng.Assert; import org.testng.annotations.Test; -/** Tests that explicit fields can be set at the outer level in yaml. */ -public class MapListTests { +/** Tests basic map/list parsing */ +public class YomlMapListTests { YomlTestFixture y = YomlTestFixture.newInstance(); @@ -156,7 +156,7 @@ public String toString() { } @Test public void testGenericList() { - y.tr.put("gf", TestingGenericsOnFields.class, MutableList.of(new AllFieldsExplicit())); + y.tr.put("gf", TestingGenericsOnFields.class, MutableList.of(new AllFieldsTopLevel())); TestingGenericsOnFields gf; gf = new TestingGenericsOnFields(); @@ -164,7 +164,7 @@ public String toString() { y.reading("{ list: [ UP ] }", "gf").writing(gf, "gf").doReadWriteAssertingJsonMatch(); } @Test public void testGenericMap() { - y.tr.put("gf", TestingGenericsOnFields.class, MutableList.of(new AllFieldsExplicit())); + y.tr.put("gf", TestingGenericsOnFields.class, MutableList.of(new AllFieldsTopLevel())); TestingGenericsOnFields gf; String json; gf = new TestingGenericsOnFields(); @@ -178,7 +178,7 @@ public String toString() { } @Test public void testGenericListSet() { - y.tr.put("gf", TestingGenericsOnFields.class, MutableList.of(new AllFieldsExplicit())); + y.tr.put("gf", TestingGenericsOnFields.class, MutableList.of(new AllFieldsTopLevel())); TestingGenericsOnFields gf; String json; gf = new TestingGenericsOnFields(); From 1c5bbb454c396d725cf611ebe24ca61c2a36f04b Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 22 Sep 2016 12:47:39 +0100 Subject: [PATCH 38/77] repair MockConfigKey interface in light of config inheritance changes --- .../brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java index f50cfb3c25..bb7cf37840 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java @@ -22,6 +22,7 @@ import java.util.Map; import org.apache.brooklyn.config.ConfigInheritance; +import org.apache.brooklyn.config.ConfigInheritance.ConfigInheritanceContext; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.Jsonya; @@ -71,6 +72,8 @@ public MockConfigKey(Class type, String name) { @Override public ConfigInheritance getInheritance() { return null; } @Override public Predicate getConstraint() { return null; } @Override public boolean isValueValid(T value) { return true; } + @Override public ConfigInheritance getInheritanceByContext(ConfigInheritanceContext context) { return null; } + @Override public Map getInheritanceByContext() { return MutableMap.of(); } } static class S1 { From 7110e9b7a26c8780b90ddfa0cd288b9a31f21061 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 13 Sep 2016 14:29:03 +0100 Subject: [PATCH 39/77] update sketch for next set of serializers --- .../org/apache/brooklyn/util/yoml/sketch.md | 357 +++++++++--------- 1 file changed, 183 insertions(+), 174 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md index e8d11a9fdf..2833ebe961 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md @@ -239,97 +239,8 @@ parameter, field-name, and several optional ones, so a sample usage might look l constraint: required # currently just supports 'required' (and 'null' not allowed) or blank for none (default), but reserved for future use description: The color of the shape # text (markdown) serialization: # optional additional serialization instructions for this field - - if-string: # (defined below) - set-key: field-name -``` - - -### On overloading (really can skip!) - -At the heart of this YAML serialization is the idea of heavily overloading to permit the most -natural way of writing in different situations. We go a bit overboard in 'serialization' to illustrate -below the different strategies. (Feel free to ignore, if you're comfortable with the simple examples.) - -First, if the `serialization` field (which expects a list) is given a map, -the `convert-map-to-list` serializer converts each pair in that map to a list entry as follows: - -* if V is a non-empty map, then the corresponding list entry is the map V with `{ .key: K }` added -* otherwise, the corresponding list entry is `{ .key: K, .value: V }` - -Next, each entry in the list is interpreted as a `serialization` instance, -and the serializations defined for that type specify: - -* If the key `.value` is present and `type` is not defined, that key is renamed to `type` (ignored if `type` is already present) -* If it is a map of size exactly one, it is converted to a map as done with `convert-map-to-list` above -* If the key `.key` is present and `type` is not defined, that key is renamed to `type` (ignored if `type` is already present) -* If the item is a primitive V, it is converted to `{ .value: V }` -* If it is a map with no `type` defined, `type: top-level-field` is added - - -This allows the serialization rules defined on the specific type to kick in to handle `.key` or `.value` entries -introduced but not removed. In the case of `top-level-field` (the default type, as shown in the rules above), -this will rename either such key `.value` to `field-name` (and give an error if `field-name` is already present). - -Thus we can write: - - serialization: - # top-level fields - color: { alias: colour, description: "The color of the shape", constraint: required } - name - -Or - - serialization: - - field-name: color - alias: colour - - name - -This can have some surprising side-effects in occasional edge cases; consider: - -``` - # BAD: this would try to load a type called 'color' - serialization: - - color: {} - # GOOD options - serialization: - - color - # or - serialization: - color: {} - # or - serialization: - color: top-level-field - - # BAD: this would try to load a type called 'field-name' - serialization: - - field-name: color - # GOOD options are those in the previous block or to add another field - serialization: - - field-name: color - alias: colour - - # BAD: this ultimately takes "top-level-field" as the "field-name", giving a conflict - serialization: - top-level-field: { field-name: color } - # GOOD options (in addition to those in previous section, but assuming you wanted to say the type explicitly) - serialization: - - top-level-field: { field-name: color } - # or - - top-level-field: color -``` - -It does the right thing in most cases, and it serves to illustrate the flexibility of this approach. -Of course in most cases it's probably a bad idea to do this much overloading! -However the descriptions here will normally be taken from java annotations and not written by hand, -so emphasis is on making type definitions easy-to-read (which overloading does nicely), and -instance definitions both easy-to-read and -write, rather than type definitions easy-to-write. - -Of course if you have any doubt, simply use the long-winded syntax and avoid any convenience syntax: - -``` - serialization: - - type: top-level-field - field-name: color + - convert-primitive-to-map: # (defined below) + key: field-name ``` @@ -383,69 +294,168 @@ method. These are detected and applied as one of the default strategies (below). ### Accepting lists, including generics Where the java object is a list, this can correspond to YAML in many ways. -New serializations we introduce include `convert-map-to-map-list` (which allows -a map value to be supplied), `apply-defaults-in-list` (which ensures a set of keys -are present in every entry, using default values wherever the key is absent), -`convert-singleton-maps-in-list` (which gives special behaviour if the list consists -entirely of single-key-maps, useful where a map would normally be supplied but there -might be key collisions), `if-string-in-list` (which applies `if-string` to every -element in the list), and `convert-map-to-singleton-list` (which puts a map into -a list). +The simplest is where the YAML is a list, in which case each item is parsed, +including serializers for the generic type of the list if available. + +Because lists are common in object representation, and because the context +might have additional knowledge about how to intepret them, a list field +(or any context where a list is expected) can declare additional serializers. +This can result in much nicer YOML representations. + +Serializers available for this include: + +* `convert-singleton-map` which can specify how to convert a map to a list, + by taking each pair and treating it as a list of f() where + f maps the K and V into a new map, e.g. embedding K with a default key +* `default-map-values` which ensures a set of keys are present in every entry, + using default values wherever the key is absent +* `convert-singleton-maps-in-list` which gives special behaviour if a list consists + entirely of single-key-maps, useful where a user might want to supply a concise map + syntax but that map would have several keys the same +* `convert-primitive-to-map` which converts a primitive to a map If no special list serialization is supplied for when expecting a type of `list`, the YAML must be a list and the serialization rules for `x` are then applied. If no generic type is available for a list and no serialization is specified, an explicit type is required on all entries. -Serializations that apply to lists or map entries are applied to each entry, and if -any apply the serialization is then continued from the beginning. +Serializations that apply to lists are applied to each entry, and if any apply the +serialization is then continued from the beginning (unless otherwise noted). + -As a complex example, the `serialization` list we described above has the following formal -schema: +#### Complex list serializers (skip on first read!) + +At the heart of this YAML serialization is the idea of heavily overloading to permit the most +natural way of writing in different situations. We go a bit overboard in some of the `serialization` +examples to illustrate the different strategies and some of the subtleties. (Feel free to ignore +until and unless you need to know details of complex strategies.) + +As a complex example, to define serializations for shape, the basic syntax is as follows: + + serialization: + - type: top-level-field + field-name: color + alias: colour + description: "The color of the shape" + - type: top-level-field + field-name: name + - type: convert-primitive-to-map + key: color + +However we can also support these simplifications: + + serialization: + - field-name: color + alias: colour + - name + - convert-primitive-to-map: color + + serialization: + name: {} + color: { alias: colour, description: "The color of the shape", constraint: required } + colour: { type: convert-primitive-to-map } + +This works because we've defined the following sets of rules for serializing serializations: ``` - field-name: serialization - field-type: list + field-type: list serialization: - # transforms `- color` to `- { top-level-field: color }` which will be interpreted again - - type: if-string-in-list - set-key: top-level-field + # given `- name` rewrite as `- { top-level-field: name }`, which will then be further rewritten + - type: convert-primitive-to-map + key: top-level-field - # alternative implementation of above (more explicit, not relying on `apply-defaults-in-list`) - # transforms `- color` to `- { type: top-level-field, field-name: color }` - - type: if-string-in-list - set-key: field-name - default: + # alternative implementation of above (more explicit, not relying on singleton map conversion) + # e.g. transforms `- name` to `- { type: top-level-field, field-name: name }` + - type: convert-primitive-to-map + key: field-name + defaults: type: top-level-field + # if yaml is a list containing maps with a single key, treat the key specially + # transforms `- x: k` or `- x: { .value: k }` to `- { type: x, .value: k }` + # (use this one with care as it can be confusing, but useful where type is the only thing + # always required; it's recommended (although not done in this example) to use alongside + # a rule `convert-primitive-to-map` with `key: type`.) + - type: convert-singleton-maps-in-list + key-for-key: type # NB skipped if the value is a map containing this key + # if the value is a map, they will merge + # otherwise the value is set as `.value` for conversion later + # describes how a yaml map can correspond to a list # in this example `k: { type: x }` in a map (not a list) # becomes an entry `{ field-name: k, type: x}` in a list - # (and same for shorthand `k: x`; however if just `k` is supplied it + # (and same for shorthand `k: x`; however if just `k: {}` is supplied it # takes a default type `top-level-field`) - - type: convert-map-to-map-list - key-for-key: field-name + - type: convert-singleton-map + key-for-key: .value # as above, will be converted later key-for-string-value: type # note, only applies if x non-blank - default: - type: top-level-field # note: needed to prevent collision with `convert-single-key-in-list` - - # if yaml is a list containing all maps swith a single key, treat the key specially - # transforms `- x: k` or `- x: { field-name: k }` to `- { type: x, field-name: k }` - # (use this one with care as it can be confusing, but useful where type is the only thing - # always required! typically only use in conjunction with `if-string-in-list` where `set-key: type`.) - - type: convert-single-key-maps-in-list - key-for-key: type # NB fails if this key is present in the value which is a map - key-for-string-value: field-name + defaults: + type: top-level-field # note: this is needed to prevent collision with rule above - # applies any listed unset "default keys" to the given default values, - # for every map entry in a list + # applies any listed unset default keys to the given default values, + # either on a map, or if a list then for every map entry in the list; # here this essentially makes `top-level-field` the default type - - type: apply-defaults-in-list - default: + - type: default-map-values + defaults: type: top-level-field ``` +We also rely on `top-level-field` having a rule `rename-default-value: field-name` +and `convert-primitive-to-map` having a rule `rename-default-value: key` +to convert the `.value` key appropriately for those types. + +This can have some surprising side-effects in edge cases; consider: + +``` + # BAD: this would try to load a type called 'color' + serialization: + - color: {} + # GOOD options + serialization: + - color + # or + serialization: + color: {} + # or + serialization: + color: top-level-field + + # BAD: this would try to load a type called 'field-name' + serialization: + - field-name: color + # GOOD options are those in the previous block or to add another field + serialization: + - field-name: color + alias: colour + + # BAD: this ultimately takes "top-level-field" as the "field-name", giving a conflict + serialization: + top-level-field: { field-name: color } + # GOOD options (in addition to those in previous section, but assuming you wanted to say the type explicitly) + serialization: + - top-level-field: { field-name: color } + # or + - top-level-field: color +``` + +In most cases it's probably a bad idea to do this much overloading! +But here it does the right thing in most cases, and it serves to illustrate the flexibility of this approach. + +The serializer definitions will normally be taken from java annotations and not written by hand, +so emphasis should be on making type definitions easy-to-read (which overloading does nicely), and +instance definitions both easy-to-read and -write, rather than type definitions easy-to-write. + +Of course if you have any doubt, simply use the long-winded syntax: + +``` + serialization: + - type: top-level-field + field-name: color +``` + + ### Accepting maps, including generics In some cases the underlying type will be a java Map. The lowest level way of representing a map is @@ -510,7 +520,6 @@ either on a global or a per-class basis in the registry. * `top-level-field` (`@YomlFieldAtTopLevel`) * means that a field is accepted at the top level (it does not need to be in a `field` block) - * TODO rename this `top-level-field` * `all-fields-top-level` (`@YomlAllFieldsAtTopLevel`) * applies the above to all fields @@ -518,22 +527,39 @@ either on a global or a per-class basis in the registry. * `convert-singleton-map` (`@YomlSingletonMap`) * reads/writes an item as a single-key map where a field value is the key * particularly useful when working with a list of items to allow a concise multi-entry map syntax + * defaults `.key` and `.value` facilitate working with `rename-...` serializers * `config-map-constructor` (`@YomlConfigMapConstructor`) * indicates that config key static fields should be scanned and passed in a map to the constructor +# TODO13 test the above on their own, and then again in a list +# TODO13 bail out on collision rather than attempt to use "any value" in singleton map +# TODO13 convert-primitive-to-map +# TODO13 convert-singleton-maps-in-list +# TODO13 convert-singleton-map applies to list +# TODO13 list infers from type +# TODO13 default-map-values + + ## Implementation notes -We have a `Converter` which runs through phases, running through all `Serializer` instances on each phase. -Each `Serializer` exposes methods to `read`, `write`, and `document`, and the appropriate method is invoked -depending on what the `Converter` is doing. +The `Yoml` entry point starts by invoking the `YamlConverter` which holds the input and +output objects and instructions in a `YomlContext`, and runs through phases, applying +`Serializer` instances on each phase. + +Each `Serializer` exposes methods to `read`, `write`, and `document`, with the appropriate method +invoked depending on what the `Converter` is doing. A `Serializer` will typically check the phase +and do nothing if it isn't appropriate; or if appropriate, they can: -A `Serializer` can detect the phase and bail out if it isn't appropriate; -or they can end the current phase, and/or insert one or more phases to follow the current phase. -In addition, they use a shared blackboard to store local information and communicate state. -These are the mechanisms by which serializers do the right things in the right order, -whilst allowing them to be extended. +* modify the objects in the context +* change the phases (restarting, ending, and/or inserting new ones to follow the current phase) +* add new serializers (e.g. once the type has been discovered, it may bring new serializers) + +In addition, they can use a shared blackboard to store local information and communicate state. +This loosely coupled mechanism gives a lot of flexibility for serializers do the right things in +the right order whilst allowing them to be extended, but care does need to be taken. +(The use of special phases and blackboards makes it easier to control what is done when.) The general phases are: @@ -549,8 +575,8 @@ The general phases are: * `handling-fields` (collect the fields to write from the java object) * `manipulating` (custom serializers again, now with the type set and other serializers loaded) -Afterwards, a completion check runs across all blackboard items to enable the most appropriate error -to be shown. +Afterwards, a completion check runs across all blackboard items to confirm everything has been used +and to enable the most appropriate error to be returned to the user if there are any problems. @@ -595,46 +621,29 @@ to be shown. ``` -### Random thoughts (ignore) +### Alternate serialization approach (ignore) -First, if the `serialization` field (which expects a list) is given a map, -the `convert-map-to-list` serializer converts each pair in that map to a list entry as follows: +(relying on sequencing and lots of defaults) -* if V is a non-empty map, then the corresponding list entry is the map V with `{ field-name: K }` added -* otherwise, the corresponding list entry is `{ field-name: K, type: V }` - convert-map-to-list: { key-for-key: field-name - key-for-primitive-value: type, || key-for-any-value: ... || key-for-list-value: || key-for-map-value - || merge-with-map-value - apply-to-singleton-maps: true - defaults: { type: top-level-field } - # top-level-field sets key-for-list-value as aliases -# on serializer - convert-singleton-map: { key-for-key: type - key-for-primitive-value: type, || key-for-any-value: ... || key-for-list-value: || key-for-map-value - || merge-with-map-value - defaults: { type: top-level-field } +If the `serialization` field (which expects a list) is given a map, the `convert-singleton-map` +serializer converts each pair in that map to a list entry as follows: +* if V is a map, then the corresponding list entry is the map V with `{ .key: K }` added +* otherwise, the corresponding list entry is `{ .key: K, .value: V }` + Next, each entry in the list is interpreted as a `serialization` instance, and the serializations defined for that type specify: -* If the key `.key` is present and `type` is not defined, that key is renamed to `type` (ignored if `type` is already present) - rename-key: { from: .key, to: type, fail-if-present: true } - rename-default-key: type (as above but .value) - rename-default-value: to - # above two serializers have special rules to need their own - -* If the item is a primitive V, it is converted to `{ .value: V }` - primitive-to-kv-pair - primitive-to-kv-pair: { key: .value || value: foo } - +* If the key `.value` is present and `type` is not defined, that key is renamed to `type` (ignored if `type` is already present) + (code `rename-default-value: type`, handling `{ color: top-level-field }`) +* If the key `.key` is present and `.value` is not defined, that key is renamed to `.value` +* If it is a map of size exactly one, it is converted to a map with `convert-singleton-map` above, and phases not restarted + (code `convert-singleton-maps-in-list`, handling `[ { top-level-field: color } ]`) +* If the key `.key` is present and `type` is not defined, that key is renamed to `type` +* If the item is a primitive V, it is converted to `{ .value: V }`, and phases not restarted * If it is a map with no `type` defined, `type: top-level-field` is added - defaults: { type: top-level-field } - # serializer declares convert-singleton-map ( merge-with-map-value ) - -NOTES -convert-map-to-list (default-key, default-value) - -* if V is a non-empty map, then the corresponding list entry is the map V with `{ : K }` added -* otherwise, the corresponding list entry is `{ : K, : V }` +This allows the serialization rules defined on the specific type to kick in to handle `.key` or `.value` entries +introduced but not removed. In the case of `top-level-field` (the default type, as shown in the rules above), +this will rename either such key `.value` to `field-name` (and give an error if `field-name` is already present). From ca6c384187052370baeef96819fe1cf5154bc701 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 13 Sep 2016 15:07:51 +0100 Subject: [PATCH 40/77] serializer to convert explicitly from primitive to map --- .../yoml/annotations/YomlFromPrimitive.java | 46 +++++++++ .../yoml/annotations/YomlSingletonMap.java | 2 +- .../util/yoml/internal/YomlUtils.java | 15 +++ .../serializers/ConvertFromPrimitive.java | 93 +++++++++++++++++++ .../yoml/serializers/ConvertSingletonMap.java | 19 +--- .../InstantiateTypeWorkerAbstract.java | 30 ------ .../YomlSerializerComposition.java | 27 ++++++ .../org/apache/brooklyn/util/yoml/sketch.md | 30 +++--- .../tests/ConvertPrimitiveToMapTests.java | 72 ++++++++++++++ .../yoml/tests/ConvertSingletonMapTests.java | 2 +- 10 files changed, 272 insertions(+), 64 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertPrimitiveToMapTests.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java new file mode 100644 index 0000000000..73d5377d29 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.annotations; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.apache.brooklyn.util.yoml.serializers.ConvertFromPrimitive; + +/** + * Indicates that a class can be yoml-serialized as a primitive + * if there is just a single key to set. + *

+ * Default value .value is intended for use by other serializers. + *

+ * See {@link ConvertFromPrimitive}. + */ +@Retention(RUNTIME) +@Target({ TYPE }) +public @interface YomlFromPrimitive { + + /** The key to insert for the given value */ + String keyToInsert() default ConvertFromPrimitive.DEFAULT_DEFAULT_KEY; + + DefaultKeyValue[] defaults() default {}; + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlSingletonMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlSingletonMap.java index e31971cc70..b8ff8b2d8b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlSingletonMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlSingletonMap.java @@ -58,6 +58,6 @@ * but after more specific types are applied. */ String keyForAnyValue() default ConvertSingletonMap.DEFAULT_KEY_FOR_VALUE; - DefaultKeyValue[] defaultValues() default {}; + DefaultKeyValue[] defaults() default {}; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java index 313706747a..79feb4b90c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java @@ -33,11 +33,14 @@ import org.apache.brooklyn.util.javalang.ReflectionPredicates; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yaml.Yamls; +import org.apache.brooklyn.util.yoml.annotations.DefaultKeyValue; import com.google.common.annotations.Beta; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; public class YomlUtils { @@ -262,4 +265,16 @@ public static int removeDefaults(Map defaults, Map extractDefaultMap(DefaultKeyValue[] defaultValues) { + if (defaultValues==null || defaultValues.length==0) return null; + MutableMap result = MutableMap.of(); + for (DefaultKeyValue d: defaultValues) { + Object v = d.val(); + if (d.valNeedsParsing()) { + v = Iterables.getOnlyElement( Yamls.parseAll(d.val()) ); + } + result.put(d.key(), v); + } + return result; + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java new file mode 100644 index 0000000000..baf0ed238f --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.serializers; + +import java.util.Map; + +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlFromPrimitive; +import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; + +@YomlAllFieldsTopLevel +@Alias("convert-from-primitive") +public class ConvertFromPrimitive extends YomlSerializerComposition { + + public final static String DEFAULT_DEFAULT_KEY = ConvertSingletonMap.DEFAULT_KEY_FOR_VALUE; + + public ConvertFromPrimitive() { } + + public ConvertFromPrimitive(YomlFromPrimitive ann) { + this(ann.keyToInsert(), YomlUtils.extractDefaultMap(ann.defaults())); + } + + public ConvertFromPrimitive(String keyToInsert, Map defaults) { + super(); + this.keyToInsert = keyToInsert; + this.defaults = defaults; + } + + protected YomlSerializerWorker newWorker() { + return new Worker(); + } + + @Alias("key") + String keyToInsert = DEFAULT_DEFAULT_KEY; + Map defaults; + + public class Worker extends YomlSerializerWorker { + public void read() { + if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return; + // runs before type instantiated + if (hasJavaObject()) return; + // only runs on primitives + if (!isJsonPrimitiveObject(getYamlObject())) return; + + Map newYamlMap = MutableMap.of(keyToInsert, getYamlObject()); + + YomlUtils.addDefaults(defaults, newYamlMap); + + context.setYamlObject(newYamlMap); + context.phaseRestart(); + } + + public void write() { + if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return; + if (!isYamlMap()) return; + // don't run if we're only added after instantiating the type (because then we couldn't read back!) + if (SerializersOnBlackboard.isAddedByTypeInstantiation(blackboard, ConvertFromPrimitive.this)) return; + + Object value = getYamlMap().get(keyToInsert); + if (value==null) return; + if (!isJsonPrimitiveObject(value)) return; + + Map yamlMap = MutableMap.copyOf(getYamlMap()); + yamlMap.remove(keyToInsert); + YomlUtils.removeDefaults(defaults, yamlMap); + if (!yamlMap.isEmpty()) return; + + context.setYamlObject(value); + context.phaseRestart(); + } + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java index 28c608d4d1..08c5f2eb49 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java @@ -22,17 +22,13 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yaml.Yamls; import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.annotations.Alias; -import org.apache.brooklyn.util.yoml.annotations.DefaultKeyValue; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; import org.apache.brooklyn.util.yoml.annotations.YomlSingletonMap; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; import org.apache.brooklyn.util.yoml.internal.YomlUtils; -import com.google.common.collect.Iterables; - /* * key-for-key: type * key-for-primitive-value: type || key-for-any-value: ... || key-for-list-value: || key-for-map-value @@ -47,20 +43,7 @@ public ConvertSingletonMap() { } public ConvertSingletonMap(YomlSingletonMap ann) { this(ann.keyForKey(), ann.keyForAnyValue(), ann.keyForPrimitiveValue(), ann.keyForListValue(), ann.keyForMapValue(), - null, extractDefaultMap(ann.defaultValues())); - } - - private static Map extractDefaultMap(DefaultKeyValue[] defaultValues) { - if (defaultValues==null || defaultValues.length==0) return null; - MutableMap result = MutableMap.of(); - for (DefaultKeyValue d: defaultValues) { - Object v = d.val(); - if (d.valNeedsParsing()) { - v = Iterables.getOnlyElement( Yamls.parseAll(d.val()) ); - } - result.put(d.key(), v); - } - return result; + null, YomlUtils.extractDefaultMap(ann.defaults())); } public ConvertSingletonMap(String keyForKey, String keyForAnyValue, String keyForPrimitiveValue, String keyForListValue, diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java index 341d313a2c..4fad145231 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java @@ -18,48 +18,18 @@ */ package org.apache.brooklyn.util.yoml.serializers; -import java.util.List; -import java.util.Map; -import java.util.Set; - import javax.annotation.Nullable; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.javalang.Boxing; import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; -import org.apache.brooklyn.util.yoml.internal.YomlUtils; import org.apache.brooklyn.util.yoml.serializers.YomlSerializerComposition.YomlSerializerWorker; public abstract class InstantiateTypeWorkerAbstract extends YomlSerializerWorker { - protected boolean isJsonPrimitiveType(Class type) { - if (type==null) return false; - if (String.class.isAssignableFrom(type)) return true; - if (Boxing.isPrimitiveOrBoxedClass(type)) return true; - return false; - } - protected boolean isJsonTypeName(String typename) { - if (isJsonMarkerType(typename)) return true; - return getSpecialKnownTypeName(typename)!=null; - } - protected boolean isJsonMarkerTypeExpected() { - return isJsonMarkerType(context.getExpectedType()); - } - protected boolean isJsonMarkerType(String typeName) { - return YomlUtils.TYPE_JSON.equals(typeName); - } - protected Class getSpecialKnownTypeName(String typename) { - if (YomlUtils.TYPE_STRING.equals(typename)) return String.class; - if (YomlUtils.TYPE_LIST.equals(typename)) return List.class; - if (YomlUtils.TYPE_SET.equals(typename)) return Set.class; - if (YomlUtils.TYPE_MAP.equals(typename)) return Map.class; - return Boxing.boxedType( Boxing.getPrimitiveType(typename).orNull() ); - } - protected boolean canDoRead() { if (!context.isPhase(YomlContext.StandardPhases.HANDLING_TYPE)) return false; if (hasJavaObject()) return false; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java index 536f7856f3..46fecc6d15 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.util.yoml.serializers; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Set; @@ -47,7 +48,33 @@ public abstract static class YomlSerializerWorker { protected YomlContextForRead readContext; protected YomlConfig config; protected Map blackboard; + + + protected boolean isJsonPrimitiveType(Class type) { + if (type==null) return false; + if (String.class.isAssignableFrom(type)) return true; + if (Boxing.isPrimitiveOrBoxedClass(type)) return true; + return false; + } + protected boolean isJsonTypeName(String typename) { + if (isJsonMarkerType(typename)) return true; + return getSpecialKnownTypeName(typename)!=null; + } + protected boolean isJsonMarkerTypeExpected() { + return isJsonMarkerType(context.getExpectedType()); + } + protected boolean isJsonMarkerType(String typeName) { + return YomlUtils.TYPE_JSON.equals(typeName); + } + protected Class getSpecialKnownTypeName(String typename) { + if (YomlUtils.TYPE_STRING.equals(typename)) return String.class; + if (YomlUtils.TYPE_LIST.equals(typename)) return List.class; + if (YomlUtils.TYPE_SET.equals(typename)) return Set.class; + if (YomlUtils.TYPE_MAP.equals(typename)) return Map.class; + return Boxing.boxedType( Boxing.getPrimitiveType(typename).orNull() ); + } + private void initRead(YomlContextForRead context, YomlConverter converter, Map blackboard) { if (this.context!=null) throw new IllegalStateException("Already initialized, for "+context); this.context = context; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md index 2833ebe961..37968a77d2 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md @@ -239,7 +239,7 @@ parameter, field-name, and several optional ones, so a sample usage might look l constraint: required # currently just supports 'required' (and 'null' not allowed) or blank for none (default), but reserved for future use description: The color of the shape # text (markdown) serialization: # optional additional serialization instructions for this field - - convert-primitive-to-map: # (defined below) + - convert-from-primitive: # (defined below) key: field-name ``` @@ -312,7 +312,7 @@ Serializers available for this include: * `convert-singleton-maps-in-list` which gives special behaviour if a list consists entirely of single-key-maps, useful where a user might want to supply a concise map syntax but that map would have several keys the same -* `convert-primitive-to-map` which converts a primitive to a map +* `convert-from-primitive` which converts a primitive to a map If no special list serialization is supplied for when expecting a type of `list`, the YAML must be a list and the serialization rules for `x` are then applied. If no @@ -339,7 +339,7 @@ As a complex example, to define serializations for shape, the basic syntax is as description: "The color of the shape" - type: top-level-field field-name: name - - type: convert-primitive-to-map + - type: convert-from-primitive key: color However we can also support these simplifications: @@ -348,12 +348,12 @@ However we can also support these simplifications: - field-name: color alias: colour - name - - convert-primitive-to-map: color + - convert-from-primitive: color serialization: name: {} color: { alias: colour, description: "The color of the shape", constraint: required } - colour: { type: convert-primitive-to-map } + colour: { type: convert-from-primitive } This works because we've defined the following sets of rules for serializing serializations: @@ -363,12 +363,12 @@ This works because we've defined the following sets of rules for serializing ser serialization: # given `- name` rewrite as `- { top-level-field: name }`, which will then be further rewritten - - type: convert-primitive-to-map + - type: convert-from-primitive key: top-level-field # alternative implementation of above (more explicit, not relying on singleton map conversion) # e.g. transforms `- name` to `- { type: top-level-field, field-name: name }` - - type: convert-primitive-to-map + - type: convert-from-primitive key: field-name defaults: type: top-level-field @@ -377,7 +377,7 @@ This works because we've defined the following sets of rules for serializing ser # transforms `- x: k` or `- x: { .value: k }` to `- { type: x, .value: k }` # (use this one with care as it can be confusing, but useful where type is the only thing # always required; it's recommended (although not done in this example) to use alongside - # a rule `convert-primitive-to-map` with `key: type`.) + # a rule `convert-from-primitive` with `key: type`.) - type: convert-singleton-maps-in-list key-for-key: type # NB skipped if the value is a map containing this key # if the value is a map, they will merge @@ -403,7 +403,7 @@ This works because we've defined the following sets of rules for serializing ser ``` We also rely on `top-level-field` having a rule `rename-default-value: field-name` -and `convert-primitive-to-map` having a rule `rename-default-value: key` +and `convert-from-primitive` having a rule `rename-default-value: key` to convert the `.value` key appropriately for those types. This can have some surprising side-effects in edge cases; consider: @@ -532,13 +532,15 @@ either on a global or a per-class basis in the registry. * `config-map-constructor` (`@YomlConfigMapConstructor`) * indicates that config key static fields should be scanned and passed in a map to the constructor -# TODO13 test the above on their own, and then again in a list +* `convert-from-primitive` (`@YomlFromPrimitive`) + * indicates that a primitive can be used for a complex object if just one non-trivial field is set + + +# TODO13 renames # TODO13 bail out on collision rather than attempt to use "any value" in singleton map -# TODO13 convert-primitive-to-map -# TODO13 convert-singleton-maps-in-list -# TODO13 convert-singleton-map applies to list -# TODO13 list infers from type # TODO13 default-map-values +# TODO13 apply and test the above in a list as well, inferring type +# TODO13 convert-singleton-maps-in-list diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertPrimitiveToMapTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertPrimitiveToMapTests.java new file mode 100644 index 0000000000..2eb06fa512 --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertPrimitiveToMapTests.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.tests; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.yoml.annotations.DefaultKeyValue; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlFromPrimitive; +import org.apache.brooklyn.util.yoml.serializers.AllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.serializers.ConvertFromPrimitive; +import org.apache.brooklyn.util.yoml.tests.YomlBasicTests.Shape; +import org.apache.brooklyn.util.yoml.tests.YomlBasicTests.ShapeWithSize; +import org.testng.annotations.Test; + +public class ConvertPrimitiveToMapTests { + + YomlTestFixture y = YomlTestFixture.newInstance(). + addType("shape", Shape.class, MutableList.of( + new ConvertFromPrimitive("name", null), + new AllFieldsTopLevel())); + + @Test + public void testPrimitive() { + y.reading("red-square", "shape").writing(new Shape().name("red-square"), "shape") + .doReadWriteAssertingJsonMatch(); + } + + + YomlTestFixture y2 = YomlTestFixture.newInstance(). + addType("shape", ShapeWithSize.class, MutableList.of( + new ConvertFromPrimitive("name", MutableMap.of("size", 0)), + new AllFieldsTopLevel())); + + @Test + public void testWithDefaults() { + y2.reading("red-square", "shape").writing(new ShapeWithSize().name("red-square").size(0), "shape") + .doReadWriteAssertingJsonMatch(); + } + + + @YomlAllFieldsTopLevel + @YomlFromPrimitive(keyToInsert="name", defaults={@DefaultKeyValue(key="size",val="0",valNeedsParsing=true)}) + static class ShapeAnn extends ShapeWithSize { + } + + YomlTestFixture y3 = YomlTestFixture.newInstance(). + addTypeWithAnnotations("shape", ShapeAnn.class); + + @Test + public void testFromAnnotation() { + y2.reading("red-square", "shape").writing(new ShapeWithSize().name("red-square").size(0), "shape") + .doReadWriteAssertingJsonMatch(); + } + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java index 14327a14c8..9fb612bb6d 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java @@ -107,7 +107,7 @@ public boolean equals(Object xo) { @YomlAllFieldsTopLevel @YomlSingletonMap(keyForKey="name", keyForListValue="tags", keyForPrimitiveValue="color", // keyForAnyValue="", - defaultValues={@DefaultKeyValue(key="size", val="0", valNeedsParsing=true)}) + defaults={@DefaultKeyValue(key="size", val="0", valNeedsParsing=true)}) static class ShapeAnn extends ShapeWithTags {} @Test public void testAnnPrimitiveValue() { From 0da7caa2227a21cb04d4f8e18dd27ca6dd7aec74 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 13 Sep 2016 15:41:44 +0100 Subject: [PATCH 41/77] support renaming of keys --- .../yoml/annotations/YomlAnnotations.java | 27 ++++- .../util/yoml/annotations/YomlRenameKey.java | 63 ++++++++++ .../util/yoml/serializers/RenameKey.java | 113 ++++++++++++++++++ .../org/apache/brooklyn/util/yoml/sketch.md | 9 +- ...ts.java => ConvertFromPrimitiveTests.java} | 4 +- .../util/yoml/tests/RenameKeyTests.java | 80 +++++++++++++ 6 files changed, 291 insertions(+), 5 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKey.java rename utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/{ConvertPrimitiveToMapTests.java => ConvertFromPrimitiveTests.java} (94%) create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/RenameKeyTests.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java index b924f31142..fb32e49cca 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java @@ -30,10 +30,16 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultKey; +import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultValue; import org.apache.brooklyn.util.yoml.internal.YomlUtils; +import org.apache.brooklyn.util.yoml.serializers.ConvertFromPrimitive; import org.apache.brooklyn.util.yoml.serializers.ConvertSingletonMap; -import org.apache.brooklyn.util.yoml.serializers.TopLevelFieldSerializer; import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; +import org.apache.brooklyn.util.yoml.serializers.RenameKey; +import org.apache.brooklyn.util.yoml.serializers.RenameKey.RenameDefaultKey; +import org.apache.brooklyn.util.yoml.serializers.RenameKey.RenameDefaultValue; +import org.apache.brooklyn.util.yoml.serializers.TopLevelFieldSerializer; public class YomlAnnotations { @@ -82,6 +88,23 @@ public Collection findSingletonMapSerializers(Class t) { return MutableList.of((YomlSerializer) new ConvertSingletonMap(ann)); } + public Collection findConvertFromPrimitiveSerializers(Class t) { + YomlFromPrimitive ann = t.getAnnotation(YomlFromPrimitive.class); + if (ann==null) return Collections.emptyList(); + return MutableList.of((YomlSerializer) new ConvertFromPrimitive(ann)); + } + + public Collection findRenameKeySerializers(Class t) { + MutableList result = MutableList.of(); + YomlRenameKey ann1 = t.getAnnotation(YomlRenameKey.class); + if (ann1!=null) result.add(new RenameKey(ann1)); + YomlRenameDefaultKey ann2 = t.getAnnotation(YomlRenameDefaultKey.class); + if (ann2!=null) result.add(new RenameDefaultKey(ann2)); + YomlRenameDefaultValue ann3 = t.getAnnotation(YomlRenameDefaultValue.class); + if (ann3!=null) result.add(new RenameDefaultValue(ann3)); + return result; + } + /** Adds the default set of serializer annotations */ public Set findSerializerAnnotations(Class type, boolean recurseUpIfEmpty) { Set result = MutableSet.of(); @@ -97,6 +120,8 @@ public Set findSerializerAnnotations(Class type, boolean recu } protected void collectSerializerAnnotationsAtClass(Set result, Class type) { + result.addAll(findConvertFromPrimitiveSerializers(type)); + result.addAll(findRenameKeySerializers(type)); result.addAll(findSingletonMapSerializers(type)); result.addAll(findConfigMapConstructorSerializers(type)); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java new file mode 100644 index 0000000000..e513e823f3 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.annotations; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.apache.brooklyn.util.yoml.serializers.RenameKey; + +/** + * Indicates that a key should be renamed when reading yaml. + *

+ * See {@link RenameKey}. + */ +@Retention(RUNTIME) +@Target({ TYPE }) +public @interface YomlRenameKey { + + /** The key name to change from when reading */ + String oldKeyName(); + + /** The key name to change to when reading */ + String newKeyName(); + + DefaultKeyValue[] defaults() default {}; + + /** As {@link YomlRenameKey} with {@link YomlRenameKey#oldKeyName()} equals to .key */ + @Retention(RUNTIME) + @Target({ TYPE }) + public @interface YomlRenameDefaultKey { + /** The key name to change to when reading */ + String value(); + DefaultKeyValue[] defaults() default {}; + } + + /** As {@link YomlRenameKey} with {@link YomlRenameKey#oldKeyName()} equals to .value */ + @Retention(RUNTIME) + @Target({ TYPE }) + public @interface YomlRenameDefaultValue { + /** The key name to change to when reading */ + String value(); + DefaultKeyValue[] defaults() default {}; + } +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKey.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKey.java new file mode 100644 index 0000000000..764e050cd5 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKey.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.serializers; + +import java.util.Map; + +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey; +import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultKey; +import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultValue; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; + +@YomlAllFieldsTopLevel +@Alias("rename-key") +public class RenameKey extends YomlSerializerComposition { + + RenameKey() { } + + public RenameKey(YomlRenameKey ann) { + this(ann.oldKeyName(), ann.newKeyName(), YomlUtils.extractDefaultMap(ann.defaults())); + } + + public RenameKey(String oldKeyName, String newKeyName, Map defaults) { + super(); + this.oldKeyName = oldKeyName; + this.newKeyName = newKeyName; + this.defaults = defaults; + } + + protected YomlSerializerWorker newWorker() { + return new Worker(); + } + + @Alias("from") + String oldKeyName; + @Alias("to") + String newKeyName; + Map defaults; + + public class Worker extends YomlSerializerWorker { + public void read() { + if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return; + // runs before type instantiated + if (hasJavaObject()) return; + if (!isYamlMap()) return; + + if (!getYamlMap().containsKey(oldKeyName)) return; + if (getYamlMap().containsKey(newKeyName)) return; + + getYamlMap().put(newKeyName, getYamlMap().remove(oldKeyName)); + YomlUtils.addDefaults(defaults, getYamlMap()); + + context.phaseRestart(); + } + + public void write() { + if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return; + if (!isYamlMap()) return; + + // reverse order + if (!getYamlMap().containsKey(newKeyName)) return; + if (getYamlMap().containsKey(oldKeyName)) return; + + getYamlMap().put(oldKeyName, getYamlMap().remove(newKeyName)); + YomlUtils.removeDefaults(defaults, getYamlMap()); + + context.phaseRestart(); + } + } + + @YomlAllFieldsTopLevel + @Alias("rename-default-key") + public static class RenameDefaultKey extends RenameKey { + public RenameDefaultKey(YomlRenameDefaultKey ann) { + this(ann.value(), YomlUtils.extractDefaultMap(ann.defaults())); + } + + public RenameDefaultKey(String newKeyName, Map defaults) { + super(".key", newKeyName, defaults); + } + } + + @YomlAllFieldsTopLevel + @Alias("rename-default-value") + public static class RenameDefaultValue extends RenameKey { + public RenameDefaultValue(YomlRenameDefaultValue ann) { + this(ann.value(), YomlUtils.extractDefaultMap(ann.defaults())); + } + + public RenameDefaultValue(String newKeyName, Map defaults) { + super(".value", newKeyName, defaults); + } + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md index 37968a77d2..c60f2c0e7f 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md @@ -535,8 +535,13 @@ either on a global or a per-class basis in the registry. * `convert-from-primitive` (`@YomlFromPrimitive`) * indicates that a primitive can be used for a complex object if just one non-trivial field is set - -# TODO13 renames +* `rename-key` (`@YomlRenameKey`) + * indicates that a key encountered during read-yaml processing should be renamed + * renamed in the reverse direction when writing + * also `rename-default-key` (`@YomlRenameDefaultKey`) and `rename-default-key` (`@YomlRenameDefaultValue`) + as conveniences for the above when renaming `.key` or `.value` respectively + (which are used in some of the other serializers) + # TODO13 bail out on collision rather than attempt to use "any value" in singleton map # TODO13 default-map-values # TODO13 apply and test the above in a list as well, inferring type diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertPrimitiveToMapTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertFromPrimitiveTests.java similarity index 94% rename from utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertPrimitiveToMapTests.java rename to utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertFromPrimitiveTests.java index 2eb06fa512..bef29daff3 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertPrimitiveToMapTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertFromPrimitiveTests.java @@ -29,7 +29,7 @@ import org.apache.brooklyn.util.yoml.tests.YomlBasicTests.ShapeWithSize; import org.testng.annotations.Test; -public class ConvertPrimitiveToMapTests { +public class ConvertFromPrimitiveTests { YomlTestFixture y = YomlTestFixture.newInstance(). addType("shape", Shape.class, MutableList.of( @@ -65,7 +65,7 @@ static class ShapeAnn extends ShapeWithSize { @Test public void testFromAnnotation() { - y2.reading("red-square", "shape").writing(new ShapeWithSize().name("red-square").size(0), "shape") + y3.reading("red-square", "shape").writing(new ShapeAnn().name("red-square").size(0), "shape") .doReadWriteAssertingJsonMatch(); } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/RenameKeyTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/RenameKeyTests.java new file mode 100644 index 0000000000..8d9b2d1b29 --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/RenameKeyTests.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.tests; + +import org.apache.brooklyn.util.yoml.annotations.DefaultKeyValue; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlFromPrimitive; +import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey; +import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultKey; +import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultValue; +import org.apache.brooklyn.util.yoml.tests.YomlBasicTests.Shape; +import org.apache.brooklyn.util.yoml.tests.YomlBasicTests.ShapeWithSize; +import org.testng.annotations.Test; + +public class RenameKeyTests { + + @YomlAllFieldsTopLevel + @YomlRenameKey(oldKeyName="name-of-tiny-shape", newKeyName="name", defaults={@DefaultKeyValue(key="size",val="0",valNeedsParsing=true)}) + static class ShapeAnn extends ShapeWithSize { + } + + YomlTestFixture y = YomlTestFixture.newInstance(). + addTypeWithAnnotations("shape", ShapeAnn.class); + + @Test + public void testFromAnnotation() { + y.reading("{ name-of-tiny-shape: red-square }", "shape") + .writing(new ShapeAnn().name("red-square").size(0), "shape") + .doReadWriteAssertingJsonMatch(); + } + + @YomlAllFieldsTopLevel + @YomlFromPrimitive + @YomlRenameDefaultValue("name") + static class ShapeAnnUsingDotValue extends Shape { + } + + YomlTestFixture y2 = YomlTestFixture.newInstance(). + addTypeWithAnnotations("shape", ShapeAnnUsingDotValue.class); + + @Test + public void testWithDotValue() { + y2.reading("red-square", "shape") + .writing(new ShapeAnnUsingDotValue().name("red-square"), "shape") + .doReadWriteAssertingJsonMatch(); + } + + + @YomlAllFieldsTopLevel + @YomlRenameDefaultKey("name") + static class ShapeAnnUsingDotKey extends Shape { + } + + YomlTestFixture y3 = YomlTestFixture.newInstance(). + addTypeWithAnnotations("shape", ShapeAnnUsingDotKey.class); + + @Test + public void testWithDotKey() { + y3.reading("{ .key: red-square }", "shape") + .writing(new ShapeAnnUsingDotKey().name("red-square"), "shape") + .doReadWriteAssertingJsonMatch(); + } + +} From 07a557691b9632843aa18f913f9efc6963847a7b Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 13 Sep 2016 15:47:13 +0100 Subject: [PATCH 42/77] bail out in singleton-map conversion --- .../yoml/serializers/ConvertSingletonMap.java | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java index 08c5f2eb49..d12222d500 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java @@ -66,6 +66,8 @@ protected YomlSerializerWorker newWorker() { public final static String DEFAULT_KEY_FOR_VALUE = ".value"; String keyForKey = DEFAULT_KEY_FOR_KEY; + /** key to use if type-specific key is not known; + * only applies to map if {@link #mergeWithMapValue} is set */ String keyForAnyValue = DEFAULT_KEY_FOR_VALUE; String keyForPrimitiveValue; String keyForListValue; @@ -106,14 +108,14 @@ public void read() { if (isJsonPrimitiveObject(value) && Strings.isNonBlank(keyForPrimitiveValue)) { newYamlMap.put(keyForPrimitiveValue, value); } else if (value instanceof Map) { - boolean merge = isForMerging(value); + Boolean merge = isForMerging(value); + if (merge==null) return; if (merge) { newYamlMap.putAll((Map)value); } else { String keyForThisMap = Strings.isNonBlank(keyForMapValue) ? keyForMapValue : keyForAnyValue; if (Strings.isBlank(keyForThisMap)) { - // we can't apply - return; + throw new IllegalStateException("Error in isForMergingLogic"); } newYamlMap.put(keyForThisMap, value); } @@ -129,20 +131,19 @@ public void read() { context.phaseRestart(); } - protected boolean isForMerging(Object value) { - boolean merge; + /** return true/false whether to merge, or null if need to bail out */ + protected Boolean isForMerging(Object value) { if (mergeWithMapValue==null) { // default merge logic (if null): // * merge if there is no key-for-map-value AND - // * either - // * it's safe, ie there is no collision at the key-for-key key, OR - // * we have to, ie there is no key-for-any-value (default is overridden) - merge = Strings.isBlank(keyForMapValue) && - ( (!((Map)value).containsKey(keyForKey)) || (Strings.isBlank(keyForAnyValue)) ); + // * it's safe, ie there is no collision at the key-for-key key + // if not safe, we bail out (collisions may be used by clients to suppress) + if (Strings.isNonBlank(keyForMapValue)) return false; + if (((Map)value).containsKey(keyForKey)) return null; + return true; } else { - merge = mergeWithMapValue; + return mergeWithMapValue; } - return merge; } String OUR_PHASE = "manipulate-convert-singleton"; @@ -219,9 +220,10 @@ public void write() { // * merging is forced; OR // * merging isn't disallowed, and // * there is no keyForMapValue, and - // * either there is no keyForAnyValue or it wouldn't cause a collision - // (if keyFor{Map,Any}Value is in effect it will steal what we want to merge) - if (newValue==null && isForMerging(yamlMap)) { + // * it wouldn't cause a collision + // (if keyForMapValue is in effect, it will steal what we want to merge; + // but keyForAnyValue will only be used if merge is set true) + if (newValue==null && Boolean.TRUE.equals(isForMerging(yamlMap))) { newValue = yamlMap; } if (newValue==null) return; // this serializer was cancelled, it doesn't apply From c0d191887ef5f5427fd0cc3f7030ba717f324b2b Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 13 Sep 2016 15:57:07 +0100 Subject: [PATCH 43/77] add default map values serializer, and fix spelling --- .../camp/yoml/YomlConfigBagConstructor.java | 4 +- .../yoml/annotations/YomlAnnotations.java | 18 +++-- ...tor.java => YomlConfigMapConstructor.java} | 2 +- .../annotations/YomlDefaultMapValues.java | 38 ++++++++++ .../util/yoml/annotations/YomlRenameKey.java | 4 +- .../DefaultMapValuesSerializer.java | 74 +++++++++++++++++++ ...enameKey.java => RenameKeySerializer.java} | 12 +-- .../org/apache/brooklyn/util/yoml/sketch.md | 3 - .../yoml/tests/DefaultMapValuesTests.java | 50 +++++++++++++ .../yoml/tests/TopLevelConfigKeysTests.java | 4 +- 10 files changed, 188 insertions(+), 21 deletions(-) rename utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/{YomlConfigMapConsructor.java => YomlConfigMapConstructor.java} (97%) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlDefaultMapValues.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/DefaultMapValuesSerializer.java rename utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/{RenameKey.java => RenameKeySerializer.java} (90%) create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/DefaultMapValuesTests.java diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConfigBagConstructor.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConfigBagConstructor.java index 9d17cbdd1a..21c9fc153c 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConfigBagConstructor.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConfigBagConstructor.java @@ -24,11 +24,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; -import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConsructor; +import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConstructor; /** * Indicates that a class should be yoml-serialized using a one-arg constructor taking a map or bag of config. - * Similar to {@link YomlConfigMapConsructor} but accepting config-bag constructors + * Similar to {@link YomlConfigMapConstructor} but accepting config-bag constructors * and defaulting to `brooklyn.config` as the key for unknown config. */ @Retention(RUNTIME) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java index fb32e49cca..000a874dcb 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java @@ -35,10 +35,11 @@ import org.apache.brooklyn.util.yoml.internal.YomlUtils; import org.apache.brooklyn.util.yoml.serializers.ConvertFromPrimitive; import org.apache.brooklyn.util.yoml.serializers.ConvertSingletonMap; +import org.apache.brooklyn.util.yoml.serializers.DefaultMapValuesSerializer; import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; -import org.apache.brooklyn.util.yoml.serializers.RenameKey; -import org.apache.brooklyn.util.yoml.serializers.RenameKey.RenameDefaultKey; -import org.apache.brooklyn.util.yoml.serializers.RenameKey.RenameDefaultValue; +import org.apache.brooklyn.util.yoml.serializers.RenameKeySerializer; +import org.apache.brooklyn.util.yoml.serializers.RenameKeySerializer.RenameDefaultKey; +import org.apache.brooklyn.util.yoml.serializers.RenameKeySerializer.RenameDefaultValue; import org.apache.brooklyn.util.yoml.serializers.TopLevelFieldSerializer; public class YomlAnnotations { @@ -74,7 +75,7 @@ public Collection findTopLevelFieldSerializers(Class } public Collection findConfigMapConstructorSerializers(Class t) { - YomlConfigMapConsructor ann = t.getAnnotation(YomlConfigMapConsructor.class); + YomlConfigMapConstructor ann = t.getAnnotation(YomlConfigMapConstructor.class); if (ann==null) return Collections.emptyList(); return new InstantiateTypeFromRegistryUsingConfigMap.Factory().newConfigKeySerializersForType( t, @@ -94,10 +95,16 @@ public Collection findConvertFromPrimitiveSerializers(Class t return MutableList.of((YomlSerializer) new ConvertFromPrimitive(ann)); } + public Collection findDefaultMapValuesSerializers(Class t) { + YomlDefaultMapValues ann = t.getAnnotation(YomlDefaultMapValues.class); + if (ann==null) return Collections.emptyList(); + return MutableList.of((YomlSerializer) new DefaultMapValuesSerializer(ann)); + } + public Collection findRenameKeySerializers(Class t) { MutableList result = MutableList.of(); YomlRenameKey ann1 = t.getAnnotation(YomlRenameKey.class); - if (ann1!=null) result.add(new RenameKey(ann1)); + if (ann1!=null) result.add(new RenameKeySerializer(ann1)); YomlRenameDefaultKey ann2 = t.getAnnotation(YomlRenameDefaultKey.class); if (ann2!=null) result.add(new RenameDefaultKey(ann2)); YomlRenameDefaultValue ann3 = t.getAnnotation(YomlRenameDefaultValue.class); @@ -123,6 +130,7 @@ protected void collectSerializerAnnotationsAtClass(Set result, C result.addAll(findConvertFromPrimitiveSerializers(type)); result.addAll(findRenameKeySerializers(type)); result.addAll(findSingletonMapSerializers(type)); + result.addAll(findDefaultMapValuesSerializers(type)); result.addAll(findConfigMapConstructorSerializers(type)); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConfigMapConsructor.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConfigMapConstructor.java similarity index 97% rename from utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConfigMapConsructor.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConfigMapConstructor.java index 40de6bd553..b92118856b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConfigMapConsructor.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConfigMapConstructor.java @@ -32,7 +32,7 @@ */ @Retention(RUNTIME) @Target({ TYPE }) -public @interface YomlConfigMapConsructor { +public @interface YomlConfigMapConstructor { /** YOML needs to know which field contains the config at serialization time. */ String value(); /** By default YOML reads/writes unrecognised key values against a key with the same name as {@link #value()}. diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlDefaultMapValues.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlDefaultMapValues.java new file mode 100644 index 0000000000..32ea0779aa --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlDefaultMapValues.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.annotations; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Indicates that default values should be placed into a map if not present. + *

+ * Presence is tested individually on keys. + */ +@Retention(RUNTIME) +@Target({ TYPE }) +public @interface YomlDefaultMapValues { + + DefaultKeyValue[] value(); + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java index e513e823f3..40da369787 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java @@ -24,12 +24,12 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; -import org.apache.brooklyn.util.yoml.serializers.RenameKey; +import org.apache.brooklyn.util.yoml.serializers.RenameKeySerializer; /** * Indicates that a key should be renamed when reading yaml. *

- * See {@link RenameKey}. + * See {@link RenameKeySerializer}. */ @Retention(RUNTIME) @Target({ TYPE }) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/DefaultMapValuesSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/DefaultMapValuesSerializer.java new file mode 100644 index 0000000000..014d6dce0f --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/DefaultMapValuesSerializer.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.serializers; + +import java.util.Map; + +import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConstructor; +import org.apache.brooklyn.util.yoml.annotations.YomlDefaultMapValues; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; + +@YomlAllFieldsTopLevel +@YomlConfigMapConstructor("defaults") +@Alias("default-map-values") +public class DefaultMapValuesSerializer extends YomlSerializerComposition { + + DefaultMapValuesSerializer() { } + + public DefaultMapValuesSerializer(YomlDefaultMapValues ann) { + this(YomlUtils.extractDefaultMap(ann.value())); + } + + public DefaultMapValuesSerializer(Map defaults) { + super(); + this.defaults = defaults; + } + + protected YomlSerializerWorker newWorker() { + return new Worker(); + } + + Map defaults; + + public class Worker extends YomlSerializerWorker { + public void read() { + if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return; + // runs before type instantiated + if (hasJavaObject()) return; + if (!isYamlMap()) return; + + if (YomlUtils.addDefaults(defaults, getYamlMap())==0) return; + + context.phaseRestart(); + } + + public void write() { + if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return; + if (!isYamlMap()) return; + + if (YomlUtils.removeDefaults(defaults, getYamlMap())==0) return; + + context.phaseRestart(); + } + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKey.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java similarity index 90% rename from utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKey.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java index 764e050cd5..926f0a26f8 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKey.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java @@ -30,15 +30,15 @@ @YomlAllFieldsTopLevel @Alias("rename-key") -public class RenameKey extends YomlSerializerComposition { +public class RenameKeySerializer extends YomlSerializerComposition { - RenameKey() { } + RenameKeySerializer() { } - public RenameKey(YomlRenameKey ann) { + public RenameKeySerializer(YomlRenameKey ann) { this(ann.oldKeyName(), ann.newKeyName(), YomlUtils.extractDefaultMap(ann.defaults())); } - public RenameKey(String oldKeyName, String newKeyName, Map defaults) { + public RenameKeySerializer(String oldKeyName, String newKeyName, Map defaults) { super(); this.oldKeyName = oldKeyName; this.newKeyName = newKeyName; @@ -88,7 +88,7 @@ public void write() { @YomlAllFieldsTopLevel @Alias("rename-default-key") - public static class RenameDefaultKey extends RenameKey { + public static class RenameDefaultKey extends RenameKeySerializer { public RenameDefaultKey(YomlRenameDefaultKey ann) { this(ann.value(), YomlUtils.extractDefaultMap(ann.defaults())); } @@ -100,7 +100,7 @@ public RenameDefaultKey(String newKeyName, Map defaults @YomlAllFieldsTopLevel @Alias("rename-default-value") - public static class RenameDefaultValue extends RenameKey { + public static class RenameDefaultValue extends RenameKeySerializer { public RenameDefaultValue(YomlRenameDefaultValue ann) { this(ann.value(), YomlUtils.extractDefaultMap(ann.defaults())); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md index c60f2c0e7f..10544e4499 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md @@ -542,13 +542,10 @@ either on a global or a per-class basis in the registry. as conveniences for the above when renaming `.key` or `.value` respectively (which are used in some of the other serializers) -# TODO13 bail out on collision rather than attempt to use "any value" in singleton map -# TODO13 default-map-values # TODO13 apply and test the above in a list as well, inferring type # TODO13 convert-singleton-maps-in-list - ## Implementation notes The `Yoml` entry point starts by invoking the `YamlConverter` which holds the input and diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/DefaultMapValuesTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/DefaultMapValuesTests.java new file mode 100644 index 0000000000..fe212c48a6 --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/DefaultMapValuesTests.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.tests; + +import org.apache.brooklyn.util.yoml.annotations.DefaultKeyValue; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlDefaultMapValues; +import org.apache.brooklyn.util.yoml.tests.YomlBasicTests.ShapeWithSize; +import org.testng.annotations.Test; + +public class DefaultMapValuesTests { + + @YomlAllFieldsTopLevel + @YomlDefaultMapValues({@DefaultKeyValue(key="size",val="0",valNeedsParsing=true), + @DefaultKeyValue(key="name",val="anonymous")}) + static class ShapeAnn extends ShapeWithSize { + } + + YomlTestFixture y = YomlTestFixture.newInstance(). + addTypeWithAnnotations("shape", ShapeAnn.class); + + @Test + public void testEntirelyFromAnnotation() { + y.reading("{ }", "shape").writing(new ShapeAnn().name("anonymous").size(0), "shape") + .doReadWriteAssertingJsonMatch(); + } + + @Test + public void testOverwritingDefault() { + y.reading("{ name: special }", "shape").writing(new ShapeAnn().name("special").size(0), "shape") + .doReadWriteAssertingJsonMatch(); + } + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java index bb7cf37840..929a321020 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java @@ -28,7 +28,7 @@ import org.apache.brooklyn.util.collections.Jsonya; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConsructor; +import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConstructor; import org.apache.brooklyn.util.yoml.internal.YomlConfig; import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; import org.slf4j.Logger; @@ -157,7 +157,7 @@ public void testReadWriteNestedGlobalConfigKeySupport() { .doReadWriteAssertingJsonMatch(); } - @YomlConfigMapConsructor(value="keys", writeAsKey="extraConfig") + @YomlConfigMapConstructor(value="keys", writeAsKey="extraConfig") static class S3 extends S1 { S3(Map keys) { super(keys); } static ConfigKey K2 = new MockConfigKey(String.class, "k2"); From 0328555d8c7290f3e6ace22727506587a5036fc9 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 13 Sep 2016 23:47:48 +0100 Subject: [PATCH 44/77] big yoml refactor, and fixes to map conversion to restrict scope more classes internal YomlConfig uses interface YomlContext carries blackboard and parent type instantiation passes serializers to the types map conversion allows different in-map and in-list behaviour --- .../camp/yoml/YomlTypePlanTransformer.java | 2 +- .../camp/yoml/BrooklynYomlTestFixture.java | 2 +- .../org/apache/brooklyn/util/yoml/Yoml.java | 7 +- .../apache/brooklyn/util/yoml/YomlConfig.java | 42 +++++ .../brooklyn/util/yoml/YomlException.java | 2 + .../brooklyn/util/yoml/YomlRequirement.java | 2 + .../brooklyn/util/yoml/YomlSerializer.java | 8 +- .../internal/SerializersOnBlackboard.java | 7 + .../{YomlConfig.java => YomlConfigs.java} | 30 ++-- .../util/yoml/{ => internal}/YomlContext.java | 21 ++- .../{ => internal}/YomlContextForRead.java | 6 +- .../{ => internal}/YomlContextForWrite.java | 6 +- .../util/yoml/internal/YomlConverter.java | 20 +-- .../util/yoml/internal/YomlUtils.java | 1 + .../ConfigInMapUnderConfigSerializer.java | 12 +- .../serializers/ConvertFromPrimitive.java | 2 +- .../yoml/serializers/ConvertSingletonMap.java | 158 ++++++++++++++++-- .../DefaultMapValuesSerializer.java | 2 +- .../serializers/FieldsInMapUnderFields.java | 12 +- .../yoml/serializers/InstantiateTypeEnum.java | 2 +- .../InstantiateTypeFromRegistry.java | 5 +- ...antiateTypeFromRegistryUsingConfigMap.java | 4 +- .../yoml/serializers/InstantiateTypeList.java | 41 +++-- .../yoml/serializers/InstantiateTypeMap.java | 16 +- .../serializers/InstantiateTypePrimitive.java | 2 +- .../InstantiateTypeWorkerAbstract.java | 8 +- .../serializers/JavaFieldsOnBlackboard.java | 2 +- .../serializers/ReadingTypeOnBlackboard.java | 6 +- .../yoml/serializers/RenameKeySerializer.java | 2 +- .../serializers/TopLevelFieldSerializer.java | 4 +- .../serializers/TopLevelFieldsBlackboard.java | 2 +- .../serializers/YamlKeysOnBlackboard.java | 2 +- .../YomlSerializerComposition.java | 32 ++-- .../org/apache/brooklyn/util/yoml/sketch.md | 48 +++--- .../yoml/tests/ConvertSingletonMapTests.java | 63 +++++-- .../yoml/tests/TopLevelConfigKeysTests.java | 2 +- .../util/yoml/tests/YomlTestFixture.java | 2 +- 37 files changed, 414 insertions(+), 171 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlConfig.java rename utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/{YomlConfig.java => YomlConfigs.java} (75%) rename utils/common/src/main/java/org/apache/brooklyn/util/yoml/{ => internal}/YomlContext.java (88%) rename utils/common/src/main/java/org/apache/brooklyn/util/yoml/{ => internal}/YomlContextForRead.java (90%) rename utils/common/src/main/java/org/apache/brooklyn/util/yoml/{ => internal}/YomlContextForWrite.java (87%) diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java index 0a9aedbb15..fed4ccfe67 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java @@ -40,9 +40,9 @@ import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.Yoml; +import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.YomlSerializer; -import org.apache.brooklyn.util.yoml.internal.YomlConfig; import org.yaml.snakeyaml.Yaml; import com.google.common.base.Preconditions; diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java index 102dd69f98..6954255276 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java @@ -18,8 +18,8 @@ */ package org.apache.brooklyn.camp.yoml; +import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations; -import org.apache.brooklyn.util.yoml.internal.YomlConfig; import org.apache.brooklyn.util.yoml.tests.YomlTestFixture; public class BrooklynYomlTestFixture extends YomlTestFixture { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java index 346bc75f7b..f0c97c22b0 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/Yoml.java @@ -18,7 +18,8 @@ */ package org.apache.brooklyn.util.yoml; -import org.apache.brooklyn.util.yoml.internal.YomlConfig; +import org.apache.brooklyn.util.yoml.internal.YomlContextForRead; +import org.apache.brooklyn.util.yoml.internal.YomlContextForWrite; import org.apache.brooklyn.util.yoml.internal.YomlConverter; @@ -47,14 +48,14 @@ public Object read(String yaml, String expectedType) { return readFromYamlObject(new org.yaml.snakeyaml.Yaml().load(yaml), expectedType); } public Object readFromYamlObject(Object yamlObject, String type) { - return new YomlConverter(config).read( new YomlContextForRead(yamlObject, "", type) ); + return new YomlConverter(config).read( new YomlContextForRead(yamlObject, "", type, null) ); } public Object write(Object java) { return write(java, null); } public Object write(Object java, String expectedType) { - return new YomlConverter(config).write( new YomlContextForWrite(java, "", expectedType) ); + return new YomlConverter(config).write( new YomlContextForWrite(java, "", expectedType, null) ); } // public T read(String yaml, Class type) { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlConfig.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlConfig.java new file mode 100644 index 0000000000..0713a4d334 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlConfig.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml; + +import java.util.List; + +import org.apache.brooklyn.util.javalang.coerce.TypeCoercer; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; +import org.apache.brooklyn.util.yoml.internal.YomlConfigs; + +public interface YomlConfig { + + public YomlTypeRegistry getTypeRegistry(); + public TypeCoercer getCoercer(); + public List getSerializersPost(); + public ConstructionInstruction getConstructionInstruction(); + + public static class Builder extends YomlConfigs.Builder { + public static Builder builder() { return new Builder(); } + public static Builder builder(YomlConfig source) { return new Builder(source); } + + protected Builder() { } + protected Builder(YomlConfig source) { super(source); } + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlException.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlException.java index 989d9c4d9a..8fb5058a4c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlException.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlException.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.util.yoml; +import org.apache.brooklyn.util.yoml.internal.YomlContext; + public class YomlException extends RuntimeException { private static final long serialVersionUID = 7825908737102292499L; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlRequirement.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlRequirement.java index 5808846901..cae4b69e2e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlRequirement.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlRequirement.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.util.yoml; +import org.apache.brooklyn.util.yoml.internal.YomlContext; + public interface YomlRequirement { void checkCompletion(YomlContext context); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlSerializer.java index 2428378269..c6392d544f 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlSerializer.java @@ -18,8 +18,8 @@ */ package org.apache.brooklyn.util.yoml; -import java.util.Map; - +import org.apache.brooklyn.util.yoml.internal.YomlContextForRead; +import org.apache.brooklyn.util.yoml.internal.YomlContextForWrite; import org.apache.brooklyn.util.yoml.internal.YomlConverter; import org.apache.brooklyn.util.yoml.serializers.YomlSerializerComposition; @@ -37,7 +37,7 @@ public interface YomlSerializer { * returning true if it did anything (and so should restart the cycle). * implementations must NOT return true indefinitely if passed the same instances! */ - public void read(YomlContextForRead context, YomlConverter converter, Map blackboard); + public void read(YomlContextForRead context, YomlConverter converter); /** * modifies java object and/or yaml object and/or blackboard as appropriate, @@ -45,7 +45,7 @@ public interface YomlSerializer { * returning true if it did anything (and so should restart the cycle). * implementations must NOT return true indefinitely if passed the same instances! */ - public void write(YomlContextForWrite context, YomlConverter converter, Map blackboard); + public void write(YomlContextForWrite context, YomlConverter converter); /** * generates human-readable schema for a type using this schema. diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java index 83cdaa3d24..502aa79518 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java @@ -47,6 +47,13 @@ public static SerializersOnBlackboard create(Map blackboard) { blackboard.put(KEY, new SerializersOnBlackboard()); return peek(blackboard); } + public static SerializersOnBlackboard getOrCreate(Map blackboard) { + SerializersOnBlackboard result = peek(blackboard); + if (result!=null) return result; + result = new SerializersOnBlackboard(); + blackboard.put(KEY, result); + return result; + } private List preSerializers = MutableList.of(); private List instantiatedTypeSerializers = MutableList.of(); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfig.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfigs.java similarity index 75% rename from utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfig.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfigs.java index 79405b6c58..0278e29fd4 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfig.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfigs.java @@ -24,6 +24,7 @@ import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.javalang.coerce.TypeCoercer; import org.apache.brooklyn.util.javalang.coerce.TypeCoercerExtensible; +import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.YomlTypeRegistry; import org.apache.brooklyn.util.yoml.serializers.FieldsInMapUnderFields; @@ -35,14 +36,9 @@ import com.google.common.collect.ImmutableList; -public interface YomlConfig { +public class YomlConfigs { - public YomlTypeRegistry getTypeRegistry(); - public TypeCoercer getCoercer(); - public List getSerializersPost(); - public ConstructionInstruction getConstructionInstruction(); - - public static class BasicYomlConfig implements YomlConfig { + private static class BasicYomlConfig implements YomlConfig { private BasicYomlConfig() {} private BasicYomlConfig(YomlConfig original) { if (original!=null) { @@ -75,20 +71,20 @@ public ConstructionInstruction getConstructionInstruction() { } } - - public static class Builder { - public static Builder builder() { return new Builder(); } - public static Builder builder(YomlConfig source) { return new Builder(source); } + public static class Builder> { final BasicYomlConfig result; protected Builder() { result = new BasicYomlConfig(); } protected Builder(YomlConfig source) { result = new BasicYomlConfig(source); } - public Builder typeRegistry(YomlTypeRegistry tr) { result.typeRegistry = tr; return this; } - public Builder coercer(TypeCoercer x) { result.coercer = x; return this; } - public Builder serializersPostReplace(List x) { result.serializersPost = x; return this; } - public Builder serializersPostAdd(Collection x) { result.serializersPost.addAll(x); return this; } - public Builder serializersPostAddDefaults() { return serializersPostAdd(getDefaultSerializers()); } - public Builder constructionInstruction(ConstructionInstruction x) { result.constructionInstruction = x; return this; } + + @SuppressWarnings("unchecked") + T thiz = (T) this; + public T typeRegistry(YomlTypeRegistry tr) { result.typeRegistry = tr; return thiz; } + public T coercer(TypeCoercer x) { result.coercer = x; return thiz; } + public T serializersPostReplace(List x) { result.serializersPost = x; return thiz; } + public T serializersPostAdd(Collection x) { result.serializersPost.addAll(x); return thiz; } + public T serializersPostAddDefaults() { return serializersPostAdd(getDefaultSerializers()); } + public T constructionInstruction(ConstructionInstruction x) { result.constructionInstruction = x; return thiz; } public YomlConfig build() { return new BasicYomlConfig(result); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContext.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContext.java similarity index 88% rename from utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContext.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContext.java index a45b0b7b58..45a75cdb84 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContext.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContext.java @@ -16,25 +16,28 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yoml; +package org.apache.brooklyn.util.yoml.internal; import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import com.google.common.base.Objects; public abstract class YomlContext { + final YomlContext parent; final String jsonPath; final String expectedType; Object javaObject; Object yamlObject; - + Map blackboard; String phaseCurrent = null; int phaseCurrentStep = -1; @@ -45,15 +48,17 @@ public static interface StandardPhases { String MANIPULATING = "manipulating"; String HANDLING_TYPE = "handling-type"; String HANDLING_FIELDS = "handling-fields"; - String MANIPULATING_FROM_LIST = "manipulating-from-list"; - String MANIPULATING_TO_LIST = "manipulating-to-list"; } - public YomlContext(String jsonPath, String expectedType) { + public YomlContext(String jsonPath, String expectedType, YomlContext parent) { this.jsonPath = jsonPath; this.expectedType = expectedType; + this.parent = parent; } + public YomlContext getParent() { + return parent; + } public String getJsonPath() { return jsonPath; } @@ -109,4 +114,10 @@ public void phasesFinished() { public String toString() { return super.toString()+"["+getJsonPath()+"]"; } + + public Map getBlackboard() { + if (blackboard==null) blackboard = MutableMap.of(); + return blackboard; + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContextForRead.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForRead.java similarity index 90% rename from utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContextForRead.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForRead.java index b43dbfc8c4..ba25316024 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContextForRead.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForRead.java @@ -16,14 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yoml; +package org.apache.brooklyn.util.yoml.internal; import org.apache.brooklyn.util.text.Strings; public class YomlContextForRead extends YomlContext { - public YomlContextForRead(Object yamlObject, String jsonPath, String expectedType) { - super(jsonPath, expectedType); + public YomlContextForRead(Object yamlObject, String jsonPath, String expectedType, YomlContext parent) { + super(jsonPath, expectedType, parent); setYamlObject(yamlObject); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContextForWrite.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForWrite.java similarity index 87% rename from utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContextForWrite.java rename to utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForWrite.java index 04f623652a..19fad8419c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlContextForWrite.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForWrite.java @@ -16,12 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.util.yoml; +package org.apache.brooklyn.util.yoml.internal; public class YomlContextForWrite extends YomlContext { - public YomlContextForWrite(Object javaObject, String jsonPath, String expectedType) { - super(jsonPath, expectedType); + public YomlContextForWrite(Object javaObject, String jsonPath, String expectedType, YomlContext parent) { + super(jsonPath, expectedType, parent); setJavaObject(javaObject); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java index 397a07042b..e2256695e3 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java @@ -20,10 +20,7 @@ import java.util.Map; -import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.yoml.YomlContext; -import org.apache.brooklyn.util.yoml.YomlContextForRead; -import org.apache.brooklyn.util.yoml.YomlContextForWrite; +import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.YomlRequirement; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.serializers.JavaFieldsOnBlackboard; @@ -62,10 +59,11 @@ public Object write(final YomlContextForWrite context) { } protected void loopOverSerializers(YomlContext context) { - Map blackboard = MutableMap.of(); + // TODO refactor further so we always pass the context + Map blackboard = context.getBlackboard(); // find the serializers known so far; store on blackboard so they could be edited - SerializersOnBlackboard serializers = SerializersOnBlackboard.create(blackboard); + SerializersOnBlackboard serializers = SerializersOnBlackboard.getOrCreate(blackboard); if (context.getExpectedType()!=null) { serializers.addExpectedTypeSerializers(config.getTypeRegistry().getSerializersForType(context.getExpectedType())); } @@ -87,11 +85,11 @@ protected void loopOverSerializers(YomlContext context) { YomlSerializer s = Iterables.get(serializers.getSerializers(), context.phaseCurrentStep()); if (context instanceof YomlContextForRead) { if (log.isTraceEnabled()) log.trace("read "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" starting ("+context.phaseCurrent()+"."+context.phaseCurrentStep()+") "); - s.read((YomlContextForRead)context, this, blackboard); + s.read((YomlContextForRead)context, this); if (log.isTraceEnabled()) log.trace("read "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" ended: "+context.getYamlObject()); } else { if (log.isTraceEnabled()) log.trace("write "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" starting ("+context.phaseCurrent()+"."+context.phaseCurrentStep()+") "); - s.write((YomlContextForWrite)context, this, blackboard); + s.write((YomlContextForWrite)context, this); if (log.isDebugEnabled()) log.debug("write "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" ended: "+context.getYamlObject()); if (log.isTraceEnabled()) log.trace("write "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" ended: "+context.getYamlObject()); @@ -100,11 +98,11 @@ protected void loopOverSerializers(YomlContext context) { } if (log.isTraceEnabled()) log.trace("YOML done looking at "+context.getJsonPath()+"/ = "+context.getJavaObject()+" <-> "+context.getYamlObject()); - checkCompletion(context, blackboard); + checkCompletion(context); } - protected void checkCompletion(YomlContext context, Map blackboard) { - for (Object bo: blackboard.values()) { + protected void checkCompletion(YomlContext context) { + for (Object bo: context.getBlackboard().values()) { if (bo instanceof YomlRequirement) { ((YomlRequirement)bo).checkCompletion(context); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java index 79feb4b90c..11b58cca73 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java @@ -34,6 +34,7 @@ import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yaml.Yamls; +import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.annotations.DefaultKeyValue; import com.google.common.annotations.Beta; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java index 6fc57b31c6..1229bd41c1 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java @@ -22,9 +22,9 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.exceptions.Exceptions; -import org.apache.brooklyn.util.yoml.YomlContext; -import org.apache.brooklyn.util.yoml.YomlContextForRead; -import org.apache.brooklyn.util.yoml.YomlContextForWrite; +import org.apache.brooklyn.util.yoml.internal.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlContextForRead; +import org.apache.brooklyn.util.yoml.internal.YomlContextForWrite; public class ConfigInMapUnderConfigSerializer extends FieldsInMapUnderFields { @@ -67,12 +67,12 @@ protected boolean setKeyValueForJavaObjectOnRead(String key, Object value) throw String optionalType = getType(key, null); Object v2; try { - v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, optionalType) ); + v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, optionalType, context) ); } catch (Exception e) { // for config we try with the optional type, but don't insist Exceptions.propagateIfFatal(e); if (optionalType!=null) optionalType = null; - v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, optionalType) ); + v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, optionalType, context) ); } fib.fieldsFromReadToConstructJava.put(key, v2); return true; @@ -86,7 +86,7 @@ protected Map writePrepareGeneralMap() { for (Map.Entry entry: fib.configToWriteFromJava.entrySet()) { // NB: won't normally have a type, the explicit config keys will take those String optionalType = getType(entry.getKey(), entry.getValue()); - Object v = converter.write(new YomlContextForWrite(entry.getValue(), context.getJsonPath()+"/"+entry.getKey(), optionalType) ); + Object v = converter.write(new YomlContextForWrite(entry.getValue(), context.getJsonPath()+"/"+entry.getKey(), optionalType, context) ); configMap.put(entry.getKey(), v); } for (String key: configMap.keySet()) fib.configToWriteFromJava.remove(key); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java index baf0ed238f..c07d4228a6 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java @@ -21,11 +21,11 @@ import java.util.Map; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; import org.apache.brooklyn.util.yoml.annotations.YomlFromPrimitive; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; +import org.apache.brooklyn.util.yoml.internal.YomlContext; import org.apache.brooklyn.util.yoml.internal.YomlUtils; @YomlAllFieldsTopLevel diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java index d12222d500..a9f8b8a186 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java @@ -18,16 +18,27 @@ */ package org.apache.brooklyn.util.yoml.serializers; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; import java.util.Map; +import java.util.Set; +import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; import org.apache.brooklyn.util.yoml.annotations.YomlSingletonMap; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; +import org.apache.brooklyn.util.yoml.internal.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlContextForRead; import org.apache.brooklyn.util.yoml.internal.YomlUtils; +import org.apache.brooklyn.util.yoml.internal.YomlUtils.GenericsParse; + +import com.google.common.collect.Iterables; /* * key-for-key: type @@ -39,21 +50,24 @@ @Alias("convert-singleton-map") public class ConvertSingletonMap extends YomlSerializerComposition { + public static enum SingletonMapMode { LIST_AS_MAP, LIST_AS_LIST, NON_LIST } + public ConvertSingletonMap() { } public ConvertSingletonMap(YomlSingletonMap ann) { this(ann.keyForKey(), ann.keyForAnyValue(), ann.keyForPrimitiveValue(), ann.keyForListValue(), ann.keyForMapValue(), - null, YomlUtils.extractDefaultMap(ann.defaults())); + null, null, YomlUtils.extractDefaultMap(ann.defaults())); } - public ConvertSingletonMap(String keyForKey, String keyForAnyValue, String keyForPrimitiveValue, String keyForListValue, - String keyForMapValue, Boolean mergeWithMapValue, Map defaults) { + public ConvertSingletonMap(String keyForKey, String keyForAnyValue, String keyForPrimitiveValue, String keyForListValue, String keyForMapValue, + Collection modes, Boolean mergeWithMapValue, Map defaults) { super(); this.keyForKey = keyForKey; this.keyForAnyValue = keyForAnyValue; this.keyForPrimitiveValue = keyForPrimitiveValue; this.keyForListValue = keyForListValue; this.keyForMapValue = keyForMapValue; + this.onlyInModes = MutableSet.copyOf(modes); this.mergeWithMapValue = mergeWithMapValue; this.defaults = defaults; } @@ -73,6 +87,10 @@ protected YomlSerializerWorker newWorker() { String keyForListValue; /** if the value against the single key is itself a map, treat it as a value for a key wit this name */ String keyForMapValue; + /** conveniences for {@link #onlyInModes} when just supplying one */ + SingletonMapMode onlyInMode = null; + /** if non-empty, restrict the modes where this serializer can run */ + Set onlyInModes = null; /** if the value against the single key is itself a map, should we put the {@link #keyForKey} as another entry in the map * to get the result; the default (if null) and usual behaviour is to do so but not if {@link #keyForMapValue} is set (because we'd use that), and not if there would be a collision * (ie {@link #keyForKey} is already present) and we have a value for {@link #keyForAnyValue} (so we can use that); @@ -80,23 +98,33 @@ protected YomlSerializerWorker newWorker() { * (which will prevent this serializer from applying unless {@link #keyForMapValue} or {@link #keyForAnyValue} is set) */ Boolean mergeWithMapValue; Map defaults; - - public static class ConvertSingletonApplied {} - + public class Worker extends YomlSerializerWorker { public void read() { - if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return; // runs before type instantiated if (hasJavaObject()) return; + if (context.isPhase(InstantiateTypeList.MANIPULATING_TO_LIST)) { + if (isYamlMap() && enterModeRead(SingletonMapMode.LIST_AS_MAP)) { + readManipulatingMapToList(); + } else if (getYamlObject() instanceof Collection && enterModeRead(SingletonMapMode.LIST_AS_LIST)) { + // this would also be done by instantiate-type-list, but it wouldn't know to + // pass this serializer through + readManipulatingInList(); + } + + return; + } + + if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return; + if (!isYamlMap()) return; if (getYamlMap().size()!=1) return; - // don't run multiple times - if (blackboard.put(ConvertSingletonMap.class.getName(), new ConvertSingletonApplied())!=null) return; - // it *is* a singleton map - Object key = getYamlMap().keySet().iterator().next(); - Object value = getYamlMap().values().iterator().next(); + if (!enterModeRead(SingletonMapMode.NON_LIST)) return; + + Object key = Iterables.getOnlyElement(getYamlMap().keySet()); + Object value = Iterables.getOnlyElement(getYamlMap().values()); // key should always be primitive if (!isJsonPrimitiveObject(key)) return; @@ -131,6 +159,81 @@ public void read() { context.phaseRestart(); } + protected boolean enterModeRead(SingletonMapMode newMode) { + SingletonMapMode currentMode = (SingletonMapMode) blackboard.get(ConvertSingletonMap.this); + if (currentMode==null) { + if (!allowInMode(newMode)) return false; + } else if (currentMode == SingletonMapMode.NON_LIST) { + // cannot transition from non-list mode others + return false; + } else { + // current mode is one of the list modes; + // only allowed to transition to non-list (in recursive call) + if (newMode == SingletonMapMode.NON_LIST) /* fine */; + else if (currentMode == SingletonMapMode.LIST_AS_MAP && newMode == SingletonMapMode.LIST_AS_LIST) ; + else { + return false; + } + } + // set the new mode + blackboard.put(ConvertSingletonMap.this, newMode); + return true; + } + + protected void readManipulatingInList() { + // go through a list, applying to each + List result = readManipulatingInList((Collection)getYamlObject(), SingletonMapMode.LIST_AS_LIST); + if (result==null) return; + + context.setYamlObject(result); + context.phaseAdvance(); + } + + protected List readManipulatingInList(Collection list, SingletonMapMode mode) { + // go through a list, applying to each + GenericsParse gp = new GenericsParse(context.getExpectedType()); + if (gp.warning!=null) { + warn(gp.warning); + return null; + } + String genericSubType = null; + if (gp.isGeneric()) { + if (gp.subTypeCount()!=1) { + // not a list + return null; + } + genericSubType = Iterables.getOnlyElement(gp.subTypes); + } + + List result = MutableList.of(); + int index = 0; + for (Object item: list) { + YomlContextForRead newContext = new YomlContextForRead(item, context.getJsonPath()+"["+index+"]", genericSubType, context); + // add this serializer and set mode in the new context + SerializersOnBlackboard.create(newContext.getBlackboard()).addExpectedTypeSerializers(MutableList.of((YomlSerializer) ConvertSingletonMap.this)); + newContext.getBlackboard().put(ConvertSingletonMap.this, mode); + + Object newItem = converter.read(newContext); + result.add( newItem ); + index++; + } + return result; + } + + protected void readManipulatingMapToList() { + // convert from a map to a list; then manipulate in list + List result = MutableList.of(); + for (Map.Entry entry: getYamlMap().entrySet()) { + result.add(MutableMap.of(entry.getKey(), entry.getValue())); + } + result = readManipulatingInList(result, SingletonMapMode.LIST_AS_MAP); + if (result==null) return; + + context.setYamlObject(result); + YamlKeysOnBlackboard.getOrCreate(blackboard, null).yamlKeysToReadToJava.clear(); + context.phaseAdvance(); + } + /** return true/false whether to merge, or null if need to bail out */ protected Boolean isForMerging(Object value) { if (mergeWithMapValue==null) { @@ -159,9 +262,22 @@ public void write() { return; } if (!context.isPhase(OUR_PHASE)) return; - + // don't run multiple times - if (blackboard.put(ConvertSingletonMap.class.getName(), new ConvertSingletonApplied())!=null) return; + if (blackboard.put(ConvertSingletonMap.this, SingletonMapMode.NON_LIST)!=null) return; + + if (!allowInMode(SingletonMapMode.NON_LIST)) { + if (!allowInMode(SingletonMapMode.LIST_AS_LIST)) { + // we can only write in one of the above two modes currently + // (list-as-map is difficult to reverse-engineer, and not necessary) + return; + } + YomlContext parent = context.getParent(); + if (parent==null || !(parent.getJavaObject() instanceof Collection)) { + // parent is not a list; disallow + return; + } + } if (!getYamlMap().containsKey(keyForKey)) return; Object newKey = getYamlMap().get(keyForKey); @@ -232,5 +348,17 @@ public void write() { context.phaseRestart(); } } + + public boolean allowInMode(SingletonMapMode currentMode) { + return getAllowedModes().contains(currentMode); + } + + protected Collection getAllowedModes() { + MutableSet modes = MutableSet.of(); + modes.addIfNotNull(onlyInMode); + modes.putAll(onlyInModes); + if (modes.isEmpty()) return Arrays.asList(SingletonMapMode.values()); + return modes; + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/DefaultMapValuesSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/DefaultMapValuesSerializer.java index 014d6dce0f..e730e6fcfc 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/DefaultMapValuesSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/DefaultMapValuesSerializer.java @@ -20,11 +20,11 @@ import java.util.Map; -import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConstructor; import org.apache.brooklyn.util.yoml.annotations.YomlDefaultMapValues; +import org.apache.brooklyn.util.yoml.internal.YomlContext; import org.apache.brooklyn.util.yoml.internal.YomlUtils; @YomlAllFieldsTopLevel diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java index d2c5ad80a9..d8a465937c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java @@ -27,11 +27,11 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; -import org.apache.brooklyn.util.yoml.YomlContext; -import org.apache.brooklyn.util.yoml.YomlContext.StandardPhases; -import org.apache.brooklyn.util.yoml.YomlContextForRead; -import org.apache.brooklyn.util.yoml.YomlContextForWrite; +import org.apache.brooklyn.util.yoml.internal.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlContextForRead; +import org.apache.brooklyn.util.yoml.internal.YomlContextForWrite; import org.apache.brooklyn.util.yoml.internal.YomlUtils; +import org.apache.brooklyn.util.yoml.internal.YomlContext.StandardPhases; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,7 +68,7 @@ protected boolean setKeyValueForJavaObjectOnRead(String key, Object value) return false; } else { String fieldType = YomlUtils.getFieldTypeName(ff, config); - Object v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, fieldType) ); + Object v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, fieldType, context) ); ff.setAccessible(true); ff.set(getJavaObject(), v2); @@ -135,7 +135,7 @@ protected Map writePrepareGeneralMap() { } else { Field ff = Reflections.findFieldMaybe(getJavaObject().getClass(), f).get(); String fieldType = YomlUtils.getFieldTypeName(ff, config); - Object v2 = converter.write(new YomlContextForWrite(v.get(), context.getJsonPath()+"/"+f, fieldType) ); + Object v2 = converter.write(new YomlContextForWrite(v.get(), context.getJsonPath()+"/"+f, fieldType, context) ); fields.put(f, v2); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java index 8f2c64dfae..4ff20a86cc 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java @@ -19,7 +19,7 @@ package org.apache.brooklyn.util.yoml.serializers; import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlContext; public class InstantiateTypeEnum extends YomlSerializerComposition { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java index 3604d5b1dc..3c73e6c286 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java @@ -23,7 +23,7 @@ import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.Yoml; -import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlContext; import org.apache.brooklyn.util.yoml.internal.YomlUtils; public class InstantiateTypeFromRegistry extends YomlSerializerComposition { @@ -69,9 +69,6 @@ protected boolean readType(String type) { RuntimeException exc = null; Maybe> jt = config.getTypeRegistry().getJavaTypeMaybe(type); - if (context.seenPhase(YomlContext.StandardPhases.MANIPULATING_TO_LIST)) { - message += ": Failed to manipulate it to be a collection, and error instatiating directly"; - } if (jt.isAbsent()) { exc = ((Maybe.Absent)jt).getException(); } else { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java index 66e791c203..596d1d750a 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java @@ -29,12 +29,12 @@ import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.yoml.Yoml; -import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; -import org.apache.brooklyn.util.yoml.internal.YomlConfig; +import org.apache.brooklyn.util.yoml.internal.YomlContext; import com.google.common.base.Preconditions; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java index 78b318cb36..43a0a173c2 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java @@ -34,10 +34,12 @@ import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.yoml.Yoml; -import org.apache.brooklyn.util.yoml.YomlContext; -import org.apache.brooklyn.util.yoml.YomlContextForRead; -import org.apache.brooklyn.util.yoml.YomlContextForWrite; +import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; +import org.apache.brooklyn.util.yoml.internal.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlContextForRead; +import org.apache.brooklyn.util.yoml.internal.YomlContextForWrite; import org.apache.brooklyn.util.yoml.internal.YomlUtils; +import org.apache.brooklyn.util.yoml.internal.YomlContext.StandardPhases; import org.apache.brooklyn.util.yoml.internal.YomlUtils.GenericsParse; import org.apache.brooklyn.util.yoml.internal.YomlUtils.JsonMarker; import org.slf4j.Logger; @@ -63,6 +65,9 @@ */ public class InstantiateTypeList extends YomlSerializerComposition { + public static final String MANIPULATING_FROM_LIST = "manipulating-from-list"; + public static final String MANIPULATING_TO_LIST = "manipulating-to-list"; + private static final Logger log = LoggerFactory.getLogger(InstantiateTypeList.class); private static final String LIST = YomlUtils.TYPE_LIST; @@ -118,8 +123,8 @@ public void read() { } else { // but we have a collection // spawn manipulate-from-list phase - if (!context.seenPhase(YomlContext.StandardPhases.MANIPULATING_FROM_LIST)) { - context.phaseInsert(YomlContext.StandardPhases.MANIPULATING_FROM_LIST, YomlContext.StandardPhases.HANDLING_TYPE); + if (!context.seenPhase(MANIPULATING_FROM_LIST)) { + context.phaseInsert(MANIPULATING_FROM_LIST, YomlContext.StandardPhases.HANDLING_TYPE); context.phaseAdvance(); } return; @@ -145,7 +150,7 @@ public void read() { expectedJavaType = oldExpectedType; if (javaType==null || value==null || !Collection.class.isAssignableFrom(javaType) || !Iterable.class.isInstance(value)) { - // don't let this run, try something else + // we don't apply, at least not yet, but may need to manipulate *to* a list } else { // looks like a list in a type-value map Object jo = newInstance(expectedJavaType, type); @@ -160,10 +165,14 @@ public void read() { } } if (expectedJavaType!=null) { - // collection definitely expected but not received - if (!context.seenPhase(YomlContext.StandardPhases.MANIPULATING_TO_LIST)) { - context.phaseInsert(YomlContext.StandardPhases.MANIPULATING_TO_LIST, YomlContext.StandardPhases.HANDLING_TYPE); + // collection definitely expected but not received, schedule manipulation phase + if (!context.seenPhase(MANIPULATING_TO_LIST)) { + // and add converters for the generic subtype + SerializersOnBlackboard.get(blackboard).addExpectedTypeSerializers( config.getTypeRegistry().getSerializersForType(genericSubType) ); + context.phaseInsert(MANIPULATING_TO_LIST, YomlContext.StandardPhases.HANDLING_TYPE); context.phaseAdvance(); + } else { + warn("Unable to manipulate input to be a list when a list is expected"); } return; } @@ -171,6 +180,16 @@ public void read() { return; } else { // given a collection, when expecting a collection or no expectation -- read as list + + if (!context.seenPhase(MANIPULATING_TO_LIST)) { + // first apply manipulations, + // and add converters for the generic subtype + SerializersOnBlackboard.get(blackboard).addExpectedTypeSerializers( config.getTypeRegistry().getSerializersForType(genericSubType) ); + context.phaseInsert(MANIPULATING_TO_LIST, StandardPhases.MANIPULATING, YomlContext.StandardPhases.HANDLING_TYPE); + context.phaseAdvance(); + return; + } + Object jo; if (hasJavaObject()) { // populating previous java object @@ -286,7 +305,7 @@ protected void readIterableInto(Collection joq, Iterable yo) { int index = 0; for (Object yi: yo) { - jo.add(converter.read( new YomlContextForRead(yi, context.getJsonPath()+"["+index+"]", genericSubType) )); + jo.add(converter.read( new YomlContextForRead(yi, context.getJsonPath()+"["+index+"]", genericSubType, context) )); index++; } @@ -388,7 +407,7 @@ public void write() { int index = 0; for (Object ji: (Iterable)getJavaObject()) { - list.add(converter.write( new YomlContextForWrite(ji, context.getJsonPath()+"["+index+"]", genericSubType) )); + list.add(converter.write( new YomlContextForWrite(ji, context.getJsonPath()+"["+index+"]", genericSubType, context) )); index++; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java index 8babcb8b90..77472913e4 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java @@ -28,8 +28,8 @@ import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.yoml.Yoml; -import org.apache.brooklyn.util.yoml.YomlContextForRead; -import org.apache.brooklyn.util.yoml.YomlContextForWrite; +import org.apache.brooklyn.util.yoml.internal.YomlContextForRead; +import org.apache.brooklyn.util.yoml.internal.YomlContextForWrite; import org.apache.brooklyn.util.yoml.internal.YomlUtils; import org.apache.brooklyn.util.yoml.internal.YomlUtils.GenericsParse; @@ -165,9 +165,9 @@ public void read() { ev1 = m.get("value"); } if (ek2==null) { - ek2 = converter.read(new YomlContextForRead(ek1, newPath+"/key", genericKeySubType)); + ek2 = converter.read(new YomlContextForRead(ek1, newPath+"/@key", genericKeySubType, context)); } - ev2 = converter.read(new YomlContextForRead(ev1, newPath+"/value", genericValueSubType)); + ev2 = converter.read(new YomlContextForRead(ev1, newPath+"/@value", genericValueSubType, context)); jom.put(ek2, ev2); } else { // must be an entry set, so invalid @@ -180,7 +180,7 @@ public void read() { } else if (value instanceof Map) { for (Map.Entry me: ((Map)value).entrySet()) { - Object v = converter.read(new YomlContextForRead(me.getValue(), context.getJsonPath()+"/"+me.getKey(), genericValueSubType)); + Object v = converter.read(new YomlContextForRead(me.getValue(), context.getJsonPath()+"/"+me.getKey(), genericValueSubType, context)); jom.put(me.getKey(), v); } @@ -294,7 +294,7 @@ public void write() { MutableMap out = MutableMap.of(); for (Map.Entry me: jo.entrySet()) { - Object v = converter.write(new YomlContextForWrite(me.getValue(), context.getJsonPath()+"/"+me.getKey(), genericValueSubType)); + Object v = converter.write(new YomlContextForWrite(me.getValue(), context.getJsonPath()+"/"+me.getKey(), genericValueSubType, context)); out.put(me.getKey(), v); } isEmpty = out.isEmpty(); @@ -304,12 +304,12 @@ public void write() { int i=0; MutableList out = MutableList.of(); for (Map.Entry me: jo.entrySet()) { - Object v = converter.write(new YomlContextForWrite(me.getValue(), context.getJsonPath()+"["+i+"]/value", genericValueSubType)); + Object v = converter.write(new YomlContextForWrite(me.getValue(), context.getJsonPath()+"["+i+"]/value", genericValueSubType, context)); if (me.getKey() instanceof String) { out.add(MutableMap.of(me.getKey(), v)); } else { - Object k = converter.write(new YomlContextForWrite(me.getKey(), context.getJsonPath()+"["+i+"]/key", genericValueSubType)); + Object k = converter.write(new YomlContextForWrite(me.getKey(), context.getJsonPath()+"["+i+"]/key", genericValueSubType, context)); out.add(MutableMap.of("key", k, "value", v)); } i++; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java index 34aeccfffe..9aa6dea015 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java @@ -20,7 +20,7 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.yoml.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlContext; import org.apache.brooklyn.util.yoml.internal.YomlUtils; public class InstantiateTypePrimitive extends YomlSerializerComposition { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java index 4fad145231..3eb23751e5 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java @@ -24,8 +24,8 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; +import org.apache.brooklyn.util.yoml.internal.YomlContext; import org.apache.brooklyn.util.yoml.serializers.YomlSerializerComposition.YomlSerializerWorker; public abstract class InstantiateTypeWorkerAbstract extends YomlSerializerWorker { @@ -123,10 +123,4 @@ protected MutableMap writingMapWithTypeAndLiteralValue(String ty return map; } - protected void warn(String message) { - ReadingTypeOnBlackboard.get(blackboard).addNote(message); - } - protected void warn(Throwable message) { - ReadingTypeOnBlackboard.get(blackboard).addNote(message); - } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/JavaFieldsOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/JavaFieldsOnBlackboard.java index c24b3cbb81..766ecccd96 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/JavaFieldsOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/JavaFieldsOnBlackboard.java @@ -22,9 +22,9 @@ import java.util.Map; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.YomlRequirement; +import org.apache.brooklyn.util.yoml.internal.YomlContext; /** Indicates that something has handled the type * (on read, creating the java object, and on write, setting the `type` field in the yaml object) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java index 11fa5dadef..2775ebb52b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java @@ -26,11 +26,11 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yoml.YomlContext; -import org.apache.brooklyn.util.yoml.YomlContextForRead; -import org.apache.brooklyn.util.yoml.YomlContextForWrite; import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.YomlRequirement; +import org.apache.brooklyn.util.yoml.internal.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlContextForRead; +import org.apache.brooklyn.util.yoml.internal.YomlContextForWrite; public class ReadingTypeOnBlackboard implements YomlRequirement { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java index 926f0a26f8..3edb54ba3c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java @@ -20,12 +20,12 @@ import java.util.Map; -import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey; import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultKey; import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultValue; +import org.apache.brooklyn.util.yoml.internal.YomlContext; import org.apache.brooklyn.util.yoml.internal.YomlUtils; @YomlAllFieldsTopLevel diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java index 02eaf368db..3a2fefd393 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java @@ -30,10 +30,10 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yoml.YomlContext; -import org.apache.brooklyn.util.yoml.YomlContext.StandardPhases; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.internal.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlContext.StandardPhases; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java index 2632d17dbb..5d25eb411e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java @@ -28,10 +28,10 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.YomlRequirement; import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.internal.YomlContext; import org.apache.brooklyn.util.yoml.serializers.TopLevelFieldSerializer.FieldConstraint; public class TopLevelFieldsBlackboard implements YomlRequirement { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java index 722a89d09e..a2bf3adbc6 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java @@ -21,9 +21,9 @@ import java.util.Map; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.yoml.YomlContext; import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.YomlRequirement; +import org.apache.brooklyn.util.yoml.internal.YomlContext; /** Keys from a YAML map that still need to be handled */ public class YamlKeysOnBlackboard implements YomlRequirement { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java index 46fecc6d15..0bf72e775d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java @@ -28,11 +28,11 @@ import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Boxing; import org.apache.brooklyn.util.text.Strings; -import org.apache.brooklyn.util.yoml.YomlContext; -import org.apache.brooklyn.util.yoml.YomlContextForRead; -import org.apache.brooklyn.util.yoml.YomlContextForWrite; +import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.YomlSerializer; -import org.apache.brooklyn.util.yoml.internal.YomlConfig; +import org.apache.brooklyn.util.yoml.internal.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlContextForRead; +import org.apache.brooklyn.util.yoml.internal.YomlContextForWrite; import org.apache.brooklyn.util.yoml.internal.YomlConverter; import org.apache.brooklyn.util.yoml.internal.YomlUtils; import org.apache.brooklyn.util.yoml.internal.YomlUtils.JsonMarker; @@ -50,6 +50,14 @@ public abstract static class YomlSerializerWorker { protected Map blackboard; + protected void warn(String message) { + ReadingTypeOnBlackboard.get(blackboard).addNote(message); + } + protected void warn(Throwable message) { + ReadingTypeOnBlackboard.get(blackboard).addNote(message); + } + + protected boolean isJsonPrimitiveType(Class type) { if (type==null) return false; if (String.class.isAssignableFrom(type)) return true; @@ -75,21 +83,21 @@ protected Class getSpecialKnownTypeName(String typename) { } - private void initRead(YomlContextForRead context, YomlConverter converter, Map blackboard) { + private void initRead(YomlContextForRead context, YomlConverter converter) { if (this.context!=null) throw new IllegalStateException("Already initialized, for "+context); this.context = context; this.readContext = context; this.converter = converter; this.config = converter.getConfig(); - this.blackboard = blackboard; + this.blackboard = context.getBlackboard(); } - private void initWrite(YomlContextForWrite context, YomlConverter converter, Map blackboard) { + private void initWrite(YomlContextForWrite context, YomlConverter converter) { if (this.context!=null) throw new IllegalStateException("Already initialized, for "+context); this.context = context; this.converter = converter; this.config = converter.getConfig(); - this.blackboard = blackboard; + this.blackboard = context.getBlackboard(); } /** If there is an expected type -- other than "Object"! -- return the java instance. Otherwise null. */ @@ -183,22 +191,22 @@ protected boolean isJsonPureObject(Object o) { } @Override - public void read(YomlContextForRead context, YomlConverter converter, Map blackboard) { + public void read(YomlContextForRead context, YomlConverter converter) { YomlSerializerWorker worker; try { worker = newWorker(); } catch (Exception e) { throw Exceptions.propagate(e); } - worker.initRead(context, converter, blackboard); + worker.initRead(context, converter); worker.read(); } @Override - public void write(YomlContextForWrite context, YomlConverter converter, Map blackboard) { + public void write(YomlContextForWrite context, YomlConverter converter) { YomlSerializerWorker worker; try { worker = newWorker(); } catch (Exception e) { throw Exceptions.propagate(e); } - worker.initWrite(context, converter, blackboard); + worker.initWrite(context, converter); worker.write(); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md index 10544e4499..8c6551c22b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md @@ -373,27 +373,29 @@ This works because we've defined the following sets of rules for serializing ser defaults: type: top-level-field - # if yaml is a list containing maps with a single key, treat the key specially - # transforms `- x: k` or `- x: { .value: k }` to `- { type: x, .value: k }` - # (use this one with care as it can be confusing, but useful where type is the only thing - # always required; it's recommended (although not done in this example) to use alongside - # a rule `convert-from-primitive` with `key: type`.) - - type: convert-singleton-maps-in-list - key-for-key: type # NB skipped if the value is a map containing this key - # if the value is a map, they will merge - # otherwise the value is set as `.value` for conversion later - - # describes how a yaml map can correspond to a list - # in this example `k: { type: x }` in a map (not a list) - # becomes an entry `{ field-name: k, type: x}` in a list + # convert-singleton-map allows a key to be promoted as a primary key + # for a more convenient representation, especially when a list is expected; + # in this example `k: { type: x }` becomes `{ field-name: k, type: x}` # (and same for shorthand `k: x`; however if just `k: {}` is supplied it - # takes a default type `top-level-field`) + # takes a default type `top-level-field`); here it is restricted to being inside + # a larger map so as not to conflict with the next rule - type: convert-singleton-map + only-in-mode: map key-for-key: .value # as above, will be converted later key-for-string-value: type # note, only applies if x non-blank - defaults: - type: top-level-field # note: this is needed to prevent collision with rule above - + + # sometimes it is convenient to have lists containing maps with a single key, + # for cases where the above rule might be wanted but the keys would conflict; + # this transforms `- x: k` or `- x: { .value: k }` to `- { type: x, .value: k }` + # (use `only-apply-in` restrictions with care; the example here shows how it + # can quickly become confusing; also note it's handy to use alongside a rule + # `convert-from-primitive` to introduce the same `key`) + - type: convert-singleton-map + only-in-mode: list + key-for-key: type # NB skipped if the value is a map containing this key + # if the value is a map, they will merge + # otherwise the value is set as `.value` for conversion later + # applies any listed unset default keys to the given default values, # either on a map, or if a list then for every map entry in the list; # here this essentially makes `top-level-field` the default type @@ -541,10 +543,10 @@ either on a global or a per-class basis in the registry. * also `rename-default-key` (`@YomlRenameDefaultKey`) and `rename-default-key` (`@YomlRenameDefaultValue`) as conveniences for the above when renaming `.key` or `.value` respectively (which are used in some of the other serializers) - -# TODO13 apply and test the above in a list as well, inferring type -# TODO13 convert-singleton-maps-in-list - + +* `default-map-values` (`@YomlDefaultMapValues`) + * allows default key-value pairs to be added on read and removed on write + ## Implementation notes @@ -587,10 +589,6 @@ and to enable the most appropriate error to be returned to the user if there are ### TODO -* list/map conversion, and tidy up notes above -* rename-key -* complex syntax, type as key, etc - * infinite loop detection: in serialize loop * handle references, solve infinite loop detection in self-referential writes, with `.reference: ../../OBJ` * best-serialization vs first-serialization diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java index 9fb612bb6d..18c48d2ee4 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; +import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.yoml.annotations.DefaultKeyValue; @@ -29,13 +30,18 @@ import org.apache.brooklyn.util.yoml.annotations.YomlSingletonMap; import org.apache.brooklyn.util.yoml.serializers.AllFieldsTopLevel; import org.apache.brooklyn.util.yoml.serializers.ConvertSingletonMap; +import org.apache.brooklyn.util.yoml.serializers.ConvertSingletonMap.SingletonMapMode; import org.apache.brooklyn.util.yoml.tests.YomlBasicTests.ShapeWithSize; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.testng.annotations.Test; import com.google.common.base.Objects; public class ConvertSingletonMapTests { + private static final Logger log = LoggerFactory.getLogger(ConvertSingletonMapTests.class); + static class ShapeWithTags extends ShapeWithSize { List tags; Map metadata; @@ -62,12 +68,12 @@ public boolean equals(Object xo) { YomlTestFixture y = YomlTestFixture.newInstance(). addType("shape", ShapeWithTags.class, MutableList.of( new AllFieldsTopLevel(), - new ConvertSingletonMap("name", null, "color", "tags", null, null, MutableMap.of("size", 0)))); + new ConvertSingletonMap("name", null, "color", "tags", null, null, null, MutableMap.of("size", 0)))); YomlTestFixture y2 = YomlTestFixture.newInstance(). addType("shape", ShapeWithTags.class, MutableList.of( new AllFieldsTopLevel(), - new ConvertSingletonMap("name", "size", "color", "tags", "metadata", null, MutableMap.of("size", 42)))); + new ConvertSingletonMap("name", "size", "color", "tags", "metadata", null, null, MutableMap.of("size", 42)))); @Test public void testPrimitiveValue() { @@ -101,15 +107,14 @@ public boolean equals(Object xo) { .assertResult("{ type: shape, color: red, name: red-square, size: 0 }"); } - YomlTestFixture y3 = YomlTestFixture.newInstance(). - addTypeWithAnnotations("shape", ShapeAnn.class); - @YomlAllFieldsTopLevel @YomlSingletonMap(keyForKey="name", keyForListValue="tags", keyForPrimitiveValue="color", -// keyForAnyValue="", defaults={@DefaultKeyValue(key="size", val="0", valNeedsParsing=true)}) static class ShapeAnn extends ShapeWithTags {} + YomlTestFixture y3 = YomlTestFixture.newInstance(). + addTypeWithAnnotations("shape", ShapeAnn.class); + @Test public void testAnnPrimitiveValue() { y3.reading("{ red-square: red }", "shape").writing(new ShapeAnn().name("red-square").color("red"), "shape") .doReadWriteAssertingJsonMatch(); @@ -136,12 +141,46 @@ static class ShapeAnn extends ShapeWithTags {} .doReadWriteAssertingJsonMatch(); } - // TODO - @Test(enabled=false) public void testAnnListCompressed() { - y3.reading("{ one: { size: 1 }, two: { size: 2 } }", "list").writing( - MutableList.of(new ShapeAnn().name("one").size(1), new ShapeAnn().name("two").size(2)), "list") - .doReadWriteAssertingJsonMatch(); + @Test public void testAnnListCompressed() { + // read list-as-map, will write out as list-as-list, and can read that back too + List obj = MutableList.of(new ShapeAnn().name("one").size(1), new ShapeAnn().name("two").size(2)); + y3.read("{ one: { size: 1 }, two: { size: 2 } }", "list").assertResult(obj); + y3.reading("[ { one: { size: 1 } }, { two: { size: 2 } } ]", "list") + .writing(obj, "list") + .doReadWriteAssertingJsonMatch(); } - + + /* perverse example where we parse differently depending whether it is a list or a map */ + YomlTestFixture y4 = YomlTestFixture.newInstance(). + addType("shape", ShapeAnn.class, MutableList.of( + new AllFieldsTopLevel(), + // in map, we take : + new ConvertSingletonMap("name", null, "color", null, null, + MutableList.of(SingletonMapMode.LIST_AS_MAP), null, MutableMap.of("size", 0)), + // in list, we take : + new ConvertSingletonMap("color", null, "name", null, null, + MutableList.of(SingletonMapMode.LIST_AS_LIST), null, MutableMap.of("size", 0)) ) + ); + + @Test public void testAnnListPerverseOrders() { + // read list-as-map, will write out as list-as-list, and can read that back too + List obj = MutableList.of(new ShapeAnn().name("blue").color("bleu")); + String listJson = "[ { bleu: blue } ]"; + + y4.read("{ blue: bleu }", "list").assertResult(obj); + y4.write(y4.lastReadResult, "list").assertResult(listJson); + y4.read(listJson, "list").assertResult(obj); + } + + @Test public void testAnnDisallowedAtRoot() { + try { + y4.read("{ blue: bleu }", "shape"); + Asserts.shouldHaveFailedPreviously("but got "+y4.lastReadResult); + } catch (Exception e) { + log.info("got expected error: "+e); + Asserts.expectedFailureContainsIgnoreCase(e, "blue", "incomplete"); + } + } + } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java index 929a321020..3f357b20ac 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java @@ -28,8 +28,8 @@ import org.apache.brooklyn.util.collections.Jsonya; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConstructor; -import org.apache.brooklyn.util.yoml.internal.YomlConfig; import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java index b6a6d46d57..9ab6d6c46d 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java @@ -26,9 +26,9 @@ import org.apache.brooklyn.util.collections.Jsonya; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.Yoml; +import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations; -import org.apache.brooklyn.util.yoml.internal.YomlConfig; import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; import org.testng.Assert; From c84643b0c8ab527d0a6dae79baaebbd491418f7e Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 13 Sep 2016 08:55:04 +0100 Subject: [PATCH 45/77] sensors/effectors wip EntityInitializers will trial the new syntax if given a map. needs convert map, then some tests. --- .../api/entity/EntityInitializer.java | 9 ++- .../typereg/RegisteredTypeLoadingContext.java | 1 + .../BrooklynEntityDecorationResolver.java | 39 +++++++-- .../spi/creation/BrooklynEntityMatcher.java | 46 +++++------ .../BrooklynYamlLocationResolver.java | 1 - .../BrooklynYamlTypeInstantiator.java | 1 - .../brooklyn/spi/creation/CampResolver.java | 1 - .../service/UrlServiceSpecResolver.java | 3 - .../camp/yoml/BrooklynYomlTypeRegistry.java | 33 +++++--- .../camp/yoml/YomlTypePlanTransformer.java | 21 +++-- .../camp/yoml/types/YomlInitializers.java | 10 ++- ...st.java => YomlTypeRegistryBasicTest.java} | 2 +- ...omlTypeRegistryEntityInitializersTest.java | 79 +++++++++++++++++++ .../brooklyn/core/effector/AddSensor.java | 2 + .../core/effector/ssh/SshCommandEffector.java | 4 + ...BrooklynClassLoadingContextSequential.java | 2 +- .../brooklyn/core/sensor/StaticSensor.java | 4 +- .../core/sensor/ssh/SshCommandSensor.java | 22 +++--- .../core/typereg/TypePlanTransformers.java | 1 - .../brooklyn/util/yoml/annotations/Alias.java | 3 + .../internal/ConstructionInstruction.java | 4 + 21 files changed, 218 insertions(+), 70 deletions(-) rename camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/{YomlTypeRegistryTest.java => YomlTypeRegistryBasicTest.java} (99%) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java diff --git a/api/src/main/java/org/apache/brooklyn/api/entity/EntityInitializer.java b/api/src/main/java/org/apache/brooklyn/api/entity/EntityInitializer.java index a9f407ab43..fd8ab234b5 100644 --- a/api/src/main/java/org/apache/brooklyn/api/entity/EntityInitializer.java +++ b/api/src/main/java/org/apache/brooklyn/api/entity/EntityInitializer.java @@ -23,19 +23,24 @@ import org.apache.brooklyn.api.objs.EntityAdjunct; import org.apache.brooklyn.api.policy.Policy; import org.apache.brooklyn.api.sensor.Feed; +import org.apache.brooklyn.util.yoml.annotations.YomlSingletonMap; /** * Instances of this class supply logic which can be used to initialize entities. * These can be added to an {@link EntitySpec} programmatically, or declared as part * of YAML recipes in a brooklyn.initializers section. - * In the case of the latter, implementing classes should define a no-arg constructor - * or a {@link Map} constructor so that YAML parameters can be supplied. + *

+ * When intended for use from YAML, implementing classes should define + * a single-argument constructor taking either a {@link Map} or a ConfigBag + * so that YAML parameters can be supplied + * (or a no-arg constructor if parameters are not supported). *

* Note that initializers are only invoked on first creation; they are not called * during a rebind. Instead, the typical pattern is that initializers will create * {@link EntityAdjunct} instances such as {@link Policy} and {@link Feed} * which will be attached during rebind. **/ +@YomlSingletonMap(keyForPrimitiveValue="type") public interface EntityInitializer { /** Applies initialization logic to a just-built entity. diff --git a/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java b/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java index c14ad57a7c..50abe051bf 100644 --- a/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java +++ b/api/src/main/java/org/apache/brooklyn/api/typereg/RegisteredTypeLoadingContext.java @@ -38,6 +38,7 @@ public interface RegisteredTypeLoadingContext { * for specs, this refers to the target type, not the spec * (eg {@link Entity} not {@link EntitySpec}). * If nothing is specified, this returns {@link Object}'s class. */ + // TODO extend to offer expected registered super type @Nonnull public Class getExpectedJavaSuperType(); /** encountered types, so that during resolution, diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java index befe8756d8..f638acde8f 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityDecorationResolver.java @@ -22,10 +22,6 @@ import java.util.Map; import java.util.Set; -import com.google.common.base.Function; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; - import org.apache.brooklyn.api.entity.EntityInitializer; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.mgmt.ManagementContext; @@ -41,9 +37,14 @@ import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.guava.Maybe; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + /** * Pattern for resolving "decorations" on service specs / entity specs, such as policies, enrichers, etc. * @@ -70,13 +71,18 @@ protected List buildListOfTheseDecorationsFromEntityAttributes(Con if (value==null) { return MutableList.of(); } if (value instanceof Iterable) { return buildListOfTheseDecorationsFromIterable((Iterable)value); + } else if (canBuildFromMap()) { + if (value instanceof Map) { + return buildListOfTheseDecorationsFromMap((Map)value); + } else { + throw new IllegalArgumentException(getDecorationKind()+" body should be map or iterable, not " + value.getClass()); + } } else { - // in future may support types other than iterables here, - // e.g. a map short form where the key is the type throw new IllegalArgumentException(getDecorationKind()+" body should be iterable, not " + value.getClass()); } } + protected Map checkIsMap(Object decorationJson) { if (!(decorationJson instanceof Map)) { throw new IllegalArgumentException(getDecorationKind()+" value must be a Map, not " + @@ -93,6 +99,11 @@ protected List

buildListOfTheseDecorationsFromIterable(Iterable value) { return decorations; } + // optional if syntax supports a map input + // (e.g. where the key is the type or the name) + protected boolean canBuildFromMap() { return false; } + protected List
buildListOfTheseDecorationsFromMap(Map value) { throw new UnsupportedOperationException(); } + protected abstract String getDecorationKind(); protected abstract Object getDecorationAttributeJsonValue(ConfigBag attrs); @@ -194,6 +205,22 @@ protected Object getDecorationAttributeJsonValue(ConfigBag attrs) { protected void addDecorationFromJsonMap(Map decorationJson, List decorations) { decorations.add(instantiator.from(decorationJson).prefix("initializer").newInstance(EntityInitializer.class)); } + + @Override + protected boolean canBuildFromMap() { + return true; + } + + @Override + protected List buildListOfTheseDecorationsFromMap(Map value) { + ManagementContext mgmt = instantiator.loader.getManagementContext(); + List result = MutableList.of(); + for (Map.Entry v: value.entrySet()) { + result.add(mgmt.getTypeRegistry().createBeanFromPlan("yoml", MutableMap.of(v.getKey(), v.getValue()), + RegisteredTypeLoadingContexts.loader(instantiator.loader), EntityInitializer.class)); + } + return result; + } } // Not much value from extending from BrooklynEntityDecorationResolver, but let's not break the convention diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityMatcher.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityMatcher.java index b0356c32df..4522ea99e3 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityMatcher.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynEntityMatcher.java @@ -122,13 +122,13 @@ public boolean apply(Object deploymentPlanItem, AssemblyTemplateConstructor atc) brooklynFlags.putAll((Map)origBrooklynFlags); } - addCustomMapAttributeIfNonNull(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_CONFIG); - addCustomListAttributeIfNonNull(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_POLICIES); - addCustomListAttributeIfNonNull(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_ENRICHERS); - addCustomListAttributeIfNonNull(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_INITIALIZERS); - addCustomListAttributeIfNonNull(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_CHILDREN); - addCustomListAttributeIfNonNull(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_PARAMETERS); - addCustomMapAttributeIfNonNull(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_CATALOG); + addCustomMapAttributeIfNonEmpty(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_CONFIG); + addCustomListAttributeIfNonEmpty(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_POLICIES); + addCustomListAttributeIfNonEmpty(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_ENRICHERS); + addCustomAttributeIfNonEmpty(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_INITIALIZERS, true, true); + addCustomListAttributeIfNonEmpty(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_CHILDREN); + addCustomListAttributeIfNonEmpty(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_PARAMETERS); + addCustomMapAttributeIfNonEmpty(builder, attrs, BrooklynCampReservedKeys.BROOKLYN_CATALOG); brooklynFlags.putAll(attrs); if (!brooklynFlags.isEmpty()) { @@ -145,18 +145,8 @@ public boolean apply(Object deploymentPlanItem, AssemblyTemplateConstructor atc) * as a custom attribute with type List. * @throws java.lang.IllegalArgumentException if map[key] is not an instance of List */ - private void addCustomListAttributeIfNonNull(Builder builder, Map attrs, String key) { - Object items = attrs.remove(key); - if (items != null) { - if (items instanceof List) { - List itemList = (List) items; - if (!itemList.isEmpty()) { - builder.customAttribute(key, Lists.newArrayList(itemList)); - } - } else { - throw new IllegalArgumentException(key + " must be a list, is: " + items.getClass().getName()); - } - } + private void addCustomListAttributeIfNonEmpty(Builder builder, Map attrs, String key) { + addCustomAttributeIfNonEmpty(builder, attrs, key, false, true); } /** @@ -164,16 +154,28 @@ private void addCustomListAttributeIfNonNull(Builder builder, Map attrs, String key) { + private void addCustomMapAttributeIfNonEmpty(Builder builder, Map attrs, String key) { + addCustomAttributeIfNonEmpty(builder, attrs, key, true, false); + } + + private void addCustomAttributeIfNonEmpty(Builder builder, Map attrs, String key, + boolean allowMap, boolean allowList) { Object items = attrs.remove(key); if (items != null) { - if (items instanceof Map) { + if (allowMap && items instanceof Map) { Map itemMap = (Map) items; if (!itemMap.isEmpty()) { builder.customAttribute(key, Maps.newHashMap(itemMap)); } + } else if (allowList && items instanceof List) { + List itemList = (List) items; + if (!itemList.isEmpty()) { + builder.customAttribute(key, Lists.newArrayList(itemList)); + } } else { - throw new IllegalArgumentException(key + " must be a map, is: " + items.getClass().getName()); + throw new IllegalArgumentException(key + " must be a "+ + (allowMap && allowList ? "map or list" : allowMap ? "map" : allowList ? "list" : "")+ + ", is: " + items.getClass().getName()); } } } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlLocationResolver.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlLocationResolver.java index b19e62ee0f..f9dbbb613e 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlLocationResolver.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlLocationResolver.java @@ -23,7 +23,6 @@ import java.util.Map; import java.util.NoSuchElementException; -import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.location.LocationDefinition; import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlTypeInstantiator.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlTypeInstantiator.java index a8f8c5a9bd..5852139a0c 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlTypeInstantiator.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynYamlTypeInstantiator.java @@ -34,7 +34,6 @@ import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; -import com.google.common.base.Optional; import com.google.common.base.Preconditions; /** Assists in loading types referenced from YAML; diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java index 006617f548..64c917a7ce 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampResolver.java @@ -25,7 +25,6 @@ import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; import org.apache.brooklyn.api.location.Location; -import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; import org.apache.brooklyn.api.policy.Policy; diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/UrlServiceSpecResolver.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/UrlServiceSpecResolver.java index b8fe3a2839..40e263ba37 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/UrlServiceSpecResolver.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/UrlServiceSpecResolver.java @@ -20,7 +20,6 @@ import java.util.Set; -import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; @@ -28,8 +27,6 @@ import org.apache.brooklyn.camp.brooklyn.spi.creation.CampTypePlanTransformer; import org.apache.brooklyn.core.resolve.entity.EntitySpecResolver; import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; -import org.apache.brooklyn.core.typereg.RegisteredTypes; -import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.core.ResourceUtils; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.net.Urls; diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java index a5a6990a0e..347d51334c 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java @@ -325,6 +325,7 @@ public String getTypeName(Object obj) { @Override public String getTypeNameOfClass(Class type) { if (type==null) return null; + log.warn("Returning default for type name of "+type); // TODO reverse lookup?? // look in catalog for something where plan matches and consists only of type return getDefaultTypeNameOfClass(type); @@ -346,8 +347,21 @@ public Iterable getSerializersForType(String typeName) { } protected void collectSerializers(Object type, Collection result, Set typesVisited) { - if (type instanceof String) type = registry().get((String)type); if (type==null) return; + if (type instanceof String) { + // convert string to registered type or class + Object typeR = registry().get((String)type); + if (typeR==null) { + // TODO context + typeR = getJavaTypeInternal((String)type, null).orNull(); + } + if (typeR==null) { + // will this ever happen in normal operations? + log.warn("Could not find '"+type+" when collecting serializers"); + return; + } + type = typeR; + } boolean canUpdateCache = typesVisited.isEmpty(); if (!typesVisited.add(type)) return; // already seen Set supers = MutableSet.of(); @@ -372,14 +386,15 @@ protected void collectSerializers(Object type, Collection result supers.addAll(((RegisteredType) type).getSuperTypes()); } } else if (type instanceof Class) { - String name = getTypeNameOfClass((Class)type); - if (name.startsWith("java:")) { - // need to loop through superclasses unless it is a registered type - // TODO result.addAll( ... ); based on annotations on the java class - // then do the following if the evaluation above was not recursive -// supers.add(((Class) type).getSuperclass()); -// supers.addAll(Arrays.asList(((Class) type).getInterfaces())); - } + result.addAll(new BrooklynYomlAnnotations().findSerializerAnnotations((Class)type, true)); +// // could look up the type? but we should be calling this normally with the RT if we have one so probably not necessary +// // and could recurse through superclasses and interfaces -- but the above is a better place to do that if needed +// String name = getTypeNameOfClass((Class)type); +// if (name.startsWith("java:")) { +// find... +//// supers.add(((Class) type).getSuperclass()); +//// supers.addAll(Arrays.asList(((Class) type).getInterfaces())); +// } } else { throw new IllegalStateException("Illegal supertype entry "+type+", visiting "+typesVisited); } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java index fed4ccfe67..44063834f0 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java @@ -43,6 +43,7 @@ import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; import org.yaml.snakeyaml.Yaml; import com.google.common.base.Preconditions; @@ -142,15 +143,19 @@ protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext co Object parsedInput; if (data==null || (data instanceof String)) { if (Strings.isBlank((String)data)) { - // blank plan means to use the java type - Maybe> jt = tr.getJavaTypeInternal(type, context); - if (jt.isAbsent()) throw new YomlException("Type '"+type+"' has no plan or java type in its definition"); - try { - return jt.get().newInstance(); - } catch (Exception e) { - Exceptions.propagateIfFatal(e); - throw new YomlException("Type '"+type+"' has no plan and its java type cannot be instantiated", e); + // blank plan means to use the java type / construction instruction + Maybe> javaType = tr.getJavaTypeInternal(type, context); + ConstructionInstruction constructor = context.getConstructorInstruction(); + if (javaType.isAbsent() && constructor==null) { + return Maybe.absent(new IllegalStateException("Type "+type+" has no plan YAML and error in type", ((Maybe.Absent)javaType).getException())); } + + Maybe result = ConstructionInstruction.Factory.newDefault(javaType.get(), constructor).create(); + + if (result.isAbsent()) { + throw new YomlException("Type '"+type+"' has no plan and its java type cannot be instantiated", ((Maybe.Absent)result).getException()); + } + return result.get(); } // else we need to parse to json objects, then translate it (below) diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java index 3e9ea7e5cd..1cc959cbb7 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java @@ -18,12 +18,14 @@ */ package org.apache.brooklyn.camp.yoml.types; +import org.apache.brooklyn.api.entity.EntityInitializer; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.yoml.BrooklynYomlTypeRegistry; import org.apache.brooklyn.core.BrooklynVersion; import org.apache.brooklyn.core.effector.ssh.SshCommandEffector; +import org.apache.brooklyn.core.sensor.StaticSensor; import org.apache.brooklyn.core.sensor.ssh.SshCommandSensor; import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; import org.apache.brooklyn.util.yoml.YomlSerializer; @@ -279,10 +281,14 @@ public static void addLocalBean(ManagementContext mgmt, String symbolicName, Str addLocalType(mgmt, BrooklynYomlTypeRegistry.newYomlRegisteredType(RegisteredTypeKind.BEAN, null, BrooklynVersion.get(), planYaml, javaConcreteType, addlSuperTypesAsClassOrRegisteredType, serializers)); } - + + /** Put here until there is a better init mechanism */ + @Beta public static void install(ManagementContext mgmt) { - addLocalBean(mgmt, SshCommandEffector.class); + addLocalBean(mgmt, EntityInitializer.class); + addLocalBean(mgmt, StaticSensor.class); addLocalBean(mgmt, SshCommandSensor.class); + addLocalBean(mgmt, SshCommandEffector.class); } } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryBasicTest.java similarity index 99% rename from camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java rename to camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryBasicTest.java index 855a4dd341..ea02508a77 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryBasicTest.java @@ -37,7 +37,7 @@ import org.testng.Assert; import org.testng.annotations.Test; -public class YomlTypeRegistryTest extends BrooklynMgmtUnitTestSupport { +public class YomlTypeRegistryBasicTest extends BrooklynMgmtUnitTestSupport { private BasicBrooklynTypeRegistry registry() { return (BasicBrooklynTypeRegistry) mgmt.getTypeRegistry(); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java new file mode 100644 index 0000000000..c040efe290 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.yoml; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; +import org.apache.brooklyn.camp.yoml.types.YomlInitializers; +import org.apache.brooklyn.core.sensor.DependentConfiguration; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.util.time.Duration; +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.google.common.base.Joiner; +import com.google.common.collect.Iterables; + +public class YomlTypeRegistryEntityInitializersTest extends AbstractYamlTest { + + @Test(enabled=false) // format still runs old camp parse, does not attempt yaml + public void testStaticSensorBasic() throws Exception { + String yaml = Joiner.on("\n").join( + "services:", + "- type: org.apache.brooklyn.core.test.entity.TestEntity", + " brooklyn.initializers:", + " - name: the-answer", + " type: static-sensor", + " value: 42"); + + checkStaticSensor(yaml); + } + + @Test + public void testStaticSensorSingletonMap() throws Exception { + String yaml = Joiner.on("\n").join( + "services:", + "- type: org.apache.brooklyn.core.test.entity.TestEntity", + " brooklyn.initializers:", + " the-answer:", + " type: static-sensor", + " value: 42"); + + checkStaticSensor(yaml); + } + + protected void checkStaticSensor(String yaml) + throws Exception, InterruptedException, ExecutionException, TimeoutException { + // TODO not finding/loading type for serializers + // TODO logically how should it learn details of static-sensor serialization? + YomlInitializers.install(mgmt()); + final Entity app = createStartWaitAndLogApplication(yaml); + TestEntity entity = (TestEntity) Iterables.getOnlyElement(app.getChildren()); + + Assert.assertEquals( + entity.getExecutionContext().submit( + DependentConfiguration.attributeWhenReady(entity, Sensors.newIntegerSensor("the-answer")) ) + .get( Duration.FIVE_SECONDS ), (Integer) 42); + } + +} diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java index ba8d679046..dde4d64d90 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java @@ -33,6 +33,7 @@ import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Boxing; import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.yoml.annotations.Alias; import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; @@ -51,6 +52,7 @@ public class AddSensor implements EntityInitializer { public static final ConfigKey SENSOR_NAME = ConfigKeys.newStringConfigKey("name", "The name of the sensor to create"); public static final ConfigKey SENSOR_PERIOD = ConfigKeys.newConfigKey(Duration.class, "period", "Period, including units e.g. 1m or 5s or 200ms; default 5 minutes", Duration.FIVE_MINUTES); + @Alias({"sensor-type","value-type"}) public static final ConfigKey SENSOR_TYPE = ConfigKeys.newStringConfigKey("targetType", "Target type for the value; default String", "java.lang.String"); protected final String name; diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java index 8d7ca13dd4..3940b504e3 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java @@ -48,15 +48,19 @@ import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.yoml.annotations.Alias; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.collect.Maps; +@Alias(preferred="ssh-effector") public final class SshCommandEffector extends AddEffector { + @Alias({"script", "run"}) public static final ConfigKey EFFECTOR_COMMAND = ConfigKeys.newStringConfigKey("command"); public static final ConfigKey EFFECTOR_EXECUTION_DIR = SshCommandSensor.SENSOR_EXECUTION_DIR; + @Alias(preferred="env", value={"vars","variables","environment"}) public static final MapConfigKey EFFECTOR_SHELL_ENVIRONMENT = BrooklynConfigKeys.SHELL_ENVIRONMENT; public static enum ExecutionTarget { diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/classloading/BrooklynClassLoadingContextSequential.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/classloading/BrooklynClassLoadingContextSequential.java index 19aa74325a..ddf98cb453 100644 --- a/core/src/main/java/org/apache/brooklyn/core/mgmt/classloading/BrooklynClassLoadingContextSequential.java +++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/classloading/BrooklynClassLoadingContextSequential.java @@ -85,7 +85,7 @@ public Maybe> tryLoadClass(String className) { errors.add( Maybe.getException(clazz) ); } - return Maybe.absent(Exceptions.create("Unable to load "+className+" from "+primaries, errors)); + return Maybe.absent(Exceptions.create("Unable to load type "+className+" from "+primaries, errors)); } @Override diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java index a6a617075e..d16da3d81c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java @@ -22,7 +22,6 @@ import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.mgmt.Task; -import org.apache.brooklyn.api.mgmt.TaskAdaptable; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.effector.AddSensor; @@ -30,9 +29,9 @@ import org.apache.brooklyn.enricher.stock.Propagator; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.core.task.Tasks; -import org.apache.brooklyn.util.core.task.ValueResolver; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.yoml.annotations.Alias; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,6 +47,7 @@ * which can be useful if the supplied value is such a function. * However when the source is another sensor, * consider using {@link Propagator} which listens for changes instead. */ +@Alias("static-sensor") public class StaticSensor extends AddSensor { private static final Logger log = LoggerFactory.getLogger(StaticSensor.class); diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java index 8b1d410cd9..891f12264a 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java @@ -21,15 +21,6 @@ import java.util.Map; import java.util.concurrent.ExecutionException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.annotations.Beta; -import com.google.common.base.Function; -import com.google.common.base.Functions; -import com.google.common.base.Preconditions; -import com.google.common.base.Supplier; - import org.apache.brooklyn.api.entity.EntityInitializer; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.config.ConfigKey; @@ -50,6 +41,15 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.base.Preconditions; +import com.google.common.base.Supplier; /** * Configurable {@link EntityInitializer} which adds an SSH sensor feed running the command supplied @@ -58,15 +58,17 @@ * * @see HttpRequestSensor */ -@Beta +@Alias("ssh-sensor") public final class SshCommandSensor extends AddSensor { private static final Logger LOG = LoggerFactory.getLogger(SshCommandSensor.class); + @Alias({"script", "run"}) public static final ConfigKey SENSOR_COMMAND = ConfigKeys.newStringConfigKey("command", "SSH command to execute for sensor"); public static final ConfigKey SENSOR_EXECUTION_DIR = ConfigKeys.newStringConfigKey("executionDir", "Directory where the command should run; " + "if not supplied, executes in the entity's run dir (or home dir if no run dir is defined); " + "use '~' to always execute in the home dir, or 'custom-feed/' to execute in a custom-feed dir relative to the run dir"); + @Alias(preferred="env", value={"vars","variables","environment"}) public static final MapConfigKey SENSOR_SHELL_ENVIRONMENT = BrooklynConfigKeys.SHELL_ENVIRONMENT; protected final String command; diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java b/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java index bd2a1ab0bb..8ca02892b2 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java @@ -24,7 +24,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.ServiceLoader; import java.util.TreeMap; import org.apache.brooklyn.api.framework.FrameworkLookup; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/Alias.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/Alias.java index 474a419f56..20d802bc2b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/Alias.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/Alias.java @@ -31,6 +31,9 @@ public @interface Alias { String[] value() default {}; + + /** Indicates an alias preferred over the name in code, e.g. when serializing an instance. + * This will be added to any list in {@link #value()} so there is no need to declare a preferred alias in both places. */ String preferred() default ""; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java index 27363ae945..bf12fb5e5f 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java @@ -18,6 +18,7 @@ */ package org.apache.brooklyn.util.yoml.internal; +import java.lang.reflect.Modifier; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -142,6 +143,9 @@ else if (type.isAssignableFrom(typeConstraintSoFar)) { /* fine */ } return outerInstruction.create(typeConstraintSoFar, newArgs); } + if (typeConstraintSoFar==null) throw new IllegalStateException("No type information available"); + if ((typeConstraintSoFar.getModifiers() & (Modifier.ABSTRACT | Modifier.INTERFACE)) != 0) + throw new IllegalStateException("Insufficient type information: expected "+type+" is not directly instantiable"); return Reflections.invokeConstructorFromArgsIncludingPrivate(typeConstraintSoFar, newArgs.toArray()); } } From 31662ae9f96c6da9124adddd57e63427c0d24b62 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 22 Sep 2016 16:32:18 +0100 Subject: [PATCH 46/77] support generics in config keys in yoml --- .../yoml/annotations/YomlAnnotations.java | 2 +- .../util/yoml/internal/YomlUtils.java | 54 ++++++++ .../ConfigInMapUnderConfigSerializer.java | 15 ++- .../TopLevelConfigKeySerializer.java | 2 +- .../serializers/TopLevelFieldsBlackboard.java | 16 ++- .../yoml/tests/TopLevelConfigKeysTests.java | 17 ++- .../tests/YomlConfigKeyGenericsTests.java | 122 ++++++++++++++++++ .../util/yoml/tests/YomlMapListTests.java | 2 + .../util/yoml/tests/YomlTestFixture.java | 5 +- .../util/yoml/tests/YomlUtilsTest.java | 51 ++++++++ 10 files changed, 269 insertions(+), 17 deletions(-) create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyGenericsTests.java create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlUtilsTest.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java index 000a874dcb..3f08f6e2dd 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java @@ -79,7 +79,7 @@ public Collection findConfigMapConstructorSerializers(Class t if (ann==null) return Collections.emptyList(); return new InstantiateTypeFromRegistryUsingConfigMap.Factory().newConfigKeySerializersForType( t, - ann.value(), ann.writeAsKey()!=null ? ann.writeAsKey() : ann.value(), + ann.value(), Strings.isNonBlank(ann.writeAsKey()) ? ann.writeAsKey() : ann.value(), ann.validateAheadOfTime(), ann.requireStaticKeys()); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java index 11b58cca73..43860c42a5 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlUtils.java @@ -23,11 +23,13 @@ import java.lang.reflect.Type; import java.util.List; import java.util.Map; +import java.util.Set; import javax.annotation.Nullable; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.javalang.Boxing; import org.apache.brooklyn.util.javalang.FieldOrderings; import org.apache.brooklyn.util.javalang.ReflectionPredicates; @@ -35,16 +37,22 @@ import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yaml.Yamls; import org.apache.brooklyn.util.yoml.YomlConfig; +import org.apache.brooklyn.util.yoml.YomlTypeRegistry; import org.apache.brooklyn.util.yoml.annotations.DefaultKeyValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; +import com.google.common.reflect.TypeToken; public class YomlUtils { + private static final Logger log = LoggerFactory.getLogger(YomlUtils.class); + /** true iff k1 and k2 are case-insensitively equal after removing all - and _. * Note that the definition of mangling may change. * TODO it should be stricter so that "ab" and "a-b" don't match but "aB" and "a-b" and "a_b" do */ @@ -92,6 +100,7 @@ public static boolean isPureJson(Object o) { } } + /** parses a type string and if it is generic it gives access to the underlying types */ public static class GenericsParse { public String warning; public boolean isGeneric = false; @@ -147,6 +156,51 @@ private boolean parse(String s) { public int subTypeCount() { return subTypes.size(); } } + public static String getTypeNameWithGenerics(TypeToken t, YomlTypeRegistry tr) { + return getTypeNameWithGenerics(t.getType(), tr); + } + + @SuppressWarnings("serial") private static class CannotResolveGenerics extends IllegalStateException {} + private final static Set WARNED_ON_UNSUPPORTED_GENERICS = MutableSet.of(); + + public static String getTypeNameWithGenerics(Type t, YomlTypeRegistry tr) { + if (t==null) return null; + + if (t instanceof ParameterizedType) { + String result = getTypeNameWithGenerics( ((ParameterizedType)t).getRawType(), tr ); + try { + StringBuilder sb = new StringBuilder(result); + Type[] args = ((ParameterizedType)t).getActualTypeArguments(); + if (args==null || args.length==0) { + // nothing + } else { + sb.append("<"); + sb.append(getTypeNameWithGenerics( args[0], tr )); + for (int i=1; i"); + } + return sb.toString(); + } catch (CannotResolveGenerics e) { + // fall back to non-generic + return result; + } + } + + if (t instanceof Class) { + return tr.getTypeNameOfClass((Class)t); + } + + // don't support WilcardType, BoundedType, or arrays + String tn = t.getClass().getName(); + if (WARNED_ON_UNSUPPORTED_GENERICS.contains(tn)) { + log.warn("Unsupported generic type: "+t+" ("+tn+"), falling back to raw type (only logging once)"); + } + throw new CannotResolveGenerics(); + } + public static Map getAllNonTransientNonStaticFields(Class type, T optionalInstanceToRequireNonNullFieldValue) { return getAllFields(type, optionalInstanceToRequireNonNullFieldValue, Predicates.and(ReflectionPredicates.IS_FIELD_NON_TRANSIENT, ReflectionPredicates.IS_FIELD_NON_STATIC)); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java index 1229bd41c1..2e785a8d8e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java @@ -25,6 +25,9 @@ import org.apache.brooklyn.util.yoml.internal.YomlContext; import org.apache.brooklyn.util.yoml.internal.YomlContextForRead; import org.apache.brooklyn.util.yoml.internal.YomlContextForWrite; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; + +import com.google.common.reflect.TypeToken; public class ConfigInMapUnderConfigSerializer extends FieldsInMapUnderFields { @@ -72,7 +75,12 @@ protected boolean setKeyValueForJavaObjectOnRead(String key, Object value) throw // for config we try with the optional type, but don't insist Exceptions.propagateIfFatal(e); if (optionalType!=null) optionalType = null; - v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, optionalType, context) ); + try { + v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, optionalType, context) ); + } catch (Exception e2) { + Exceptions.propagateIfFatal(e2); + throw e; + } } fib.fieldsFromReadToConstructJava.put(key, v2); return true; @@ -96,9 +104,10 @@ protected Map writePrepareGeneralMap() { protected String getType(String key, Object value) { TopLevelFieldsBlackboard efb = TopLevelFieldsBlackboard.get(blackboard, getKeyNameForMapOfGeneralValues()); - Class type = efb.getDeclaredType(key); + TypeToken type = efb.getDeclaredType(key); String optionalType = null; - if (type!=null && (value==null || type.isInstance(value))) optionalType = config.getTypeRegistry().getTypeNameOfClass(type); + if (type!=null && (value==null || type.getRawType().isInstance(value))) + optionalType = YomlUtils.getTypeNameWithGenerics(type, config.getTypeRegistry()); return optionalType; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java index fb4d44b2b6..98ac5db54e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java @@ -125,7 +125,7 @@ protected boolean canDoRead() { @Override protected void prepareTopLevelFields() { super.prepareTopLevelFields(); - getTopLevelFieldsBlackboard().setDeclaredTypeIfUnset(fieldName, configKey.getType()); + getTopLevelFieldsBlackboard().setDeclaredTypeIfUnset(fieldName, configKey.getTypeToken()); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java index 5d25eb411e..2498f01e74 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java @@ -34,6 +34,8 @@ import org.apache.brooklyn.util.yoml.internal.YomlContext; import org.apache.brooklyn.util.yoml.serializers.TopLevelFieldSerializer.FieldConstraint; +import com.google.common.reflect.TypeToken; + public class TopLevelFieldsBlackboard implements YomlRequirement { public static final String KEY = TopLevelFieldsBlackboard.class.getCanonicalName(); @@ -55,7 +57,7 @@ public static TopLevelFieldsBlackboard get(Map blackboard, String private final Map fieldsConstraints = MutableMap.of(); private final Map defaultValueForFieldComesFromSerializer = MutableMap.of(); private final Map defaultValueOfField = MutableMap.of(); - private final Map> declaredTypeOfFieldsAndAliases = MutableMap.of(); + private final Map> declaredTypeOfFieldsAndAliases = MutableMap.of(); public String getKeyName(String fieldName) { return Maybe.ofDisallowingNull(keyNames.get(fieldName)).orNull(); @@ -142,17 +144,17 @@ public Maybe getDefault(String fieldName) { } /** optional, and must be called after aliases; not used for fields, is used for config keys */ - public void setDeclaredTypeIfUnset(String fieldName, Class type) { - setDeclaredTypeOfItemIfUnset(fieldName, type); + public void setDeclaredTypeIfUnset(String fieldName, TypeToken type) { + setDeclaredTypeOfIndividualNameOrAliasIfUnset(fieldName, type); for (String alias: getAliases(fieldName)) - setDeclaredTypeOfItemIfUnset(alias, type); + setDeclaredTypeOfIndividualNameOrAliasIfUnset(alias, type); } - protected void setDeclaredTypeOfItemIfUnset(String fieldName, Class type) { + protected void setDeclaredTypeOfIndividualNameOrAliasIfUnset(String fieldName, TypeToken type) { if (declaredTypeOfFieldsAndAliases.get(fieldName)!=null) return; declaredTypeOfFieldsAndAliases.put(fieldName, type); } - /** only if {@link #setDeclaredTypeIfUnset(String, Class)} is being used (eg config keys) */ - public Class getDeclaredType(String key) { + /** only if {@link #setDeclaredTypeIfUnset(String, TypeToken)} is being used (eg config keys) */ + public TypeToken getDeclaredType(String key) { return declaredTypeOfFieldsAndAliases.get(key); } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java index 3f357b20ac..43951bf43e 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java @@ -50,17 +50,28 @@ public class TopLevelConfigKeysTests { static class MockConfigKey implements ConfigKey { String name; Class type; + TypeToken typeToken; T defaultValue; + + ConfigInheritance inheritance = null; public MockConfigKey(Class type, String name) { this.name = name; this.type = type; + this.typeToken = TypeToken.of(type); } - + + @SuppressWarnings("unchecked") + public MockConfigKey(TypeToken typeToken, String name) { + this.name = name; + this.typeToken = typeToken; + this.type = (Class)typeToken.getRawType(); + } + @Override public String getDescription() { return null; } @Override public String getName() { return name; } @Override public Collection getNameParts() { return MutableList.of(name); } - @Override public TypeToken getTypeToken() { return TypeToken.of(type); } + @Override public TypeToken getTypeToken() { return typeToken; } @Override public Class getType() { return type; } @Override public String getTypeName() { throw new UnsupportedOperationException(); } @@ -72,7 +83,7 @@ public MockConfigKey(Class type, String name) { @Override public ConfigInheritance getInheritance() { return null; } @Override public Predicate getConstraint() { return null; } @Override public boolean isValueValid(T value) { return true; } - @Override public ConfigInheritance getInheritanceByContext(ConfigInheritanceContext context) { return null; } + @Override public ConfigInheritance getInheritanceByContext(ConfigInheritanceContext context) { return inheritance; } @Override public Map getInheritanceByContext() { return MutableMap.of(); } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyGenericsTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyGenericsTests.java new file mode 100644 index 0000000000..79d0cf170f --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyGenericsTests.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.tests; + +import java.util.Map; + +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.Jsonya; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConstructor; +import org.apache.brooklyn.util.yoml.tests.TopLevelConfigKeysTests.MockConfigKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import com.google.common.reflect.TypeToken; + +/** Tests that the default serializers can read/write types and fields. + *

+ * And shows how to use them at a low level. + */ +public class YomlConfigKeyGenericsTests { + + private static final Logger log = LoggerFactory.getLogger(YomlConfigKeyGenericsTests.class); + + @YomlConfigMapConstructor("conf") + static class M0 { + Map conf = MutableMap.of(); + M0(Map keys) { this.conf.putAll(keys); } + + @Override + public boolean equals(Object obj) { + return (obj instanceof M0) && ((M0)obj).conf.equals(conf); + } + @Override + public int hashCode() { + return conf.hashCode(); + } + @Override + public String toString() { + return super.toString()+conf; + } + } + + @YomlConfigMapConstructor("conf") + @SuppressWarnings({ "rawtypes" }) + static class MG extends M0 { + static MockConfigKey KR = new MockConfigKey(Map.class, "kr"); + @SuppressWarnings("serial") + static MockConfigKey> KG = new MockConfigKey>(new TypeToken>() {}, "kg"); + + MG(Map keys) { super(keys); } + } + + static final Map CONF_MAP = MutableMap.of("x", 1); + final static String RAW_OUT = "{ type: mg, kr: { type: 'map', value: { x: 1 } } }"; + static final MG RAW_IN = new MG(MutableMap.of("kr", CONF_MAP)); + + @Test + public void testWriteRaw() { + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("mg", MG.class); + + y.write(RAW_IN); + log.info("M1B written as: "+y.lastWriteResult); + YomlTestFixture.assertEqualsIgnoringQuotes(Jsonya.newInstance().add(y.lastWriteResult).toString(), + RAW_OUT, "wrong serialization"); + } + + @Test + public void testReadRaw() { + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("mg", MG.class); + + y.read(RAW_OUT, null); + + MG mg = (MG)y.lastReadResult; + + Asserts.assertEquals(mg.conf.get(MG.KR.getName()), CONF_MAP); + Asserts.assertEquals(mg, RAW_IN); + } + + final static String GEN_OUT = "{ type: mg, kg: { x: 1 } }"; + static final MG GEN_IN = new MG(MutableMap.of("kg", CONF_MAP)); + + @Test + public void testWriteGen() { + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("mg", MG.class); + + y.write(GEN_IN); + log.info("M1B written as: "+y.lastWriteResult); + YomlTestFixture.assertEqualsIgnoringQuotes(Jsonya.newInstance().add(y.lastWriteResult).toString(), + GEN_OUT, "wrong serialization"); + } + + @Test + public void testReadGen() { + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("mg", MG.class); + + y.read(GEN_OUT, null); + + MG mg = (MG)y.lastReadResult; + + Asserts.assertEquals(mg.conf.get(MG.KG.getName()), CONF_MAP); + Asserts.assertEquals(mg, GEN_IN); + } + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlMapListTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlMapListTests.java index 3326a00f5e..ddb3c583d8 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlMapListTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlMapListTests.java @@ -53,8 +53,10 @@ public class YomlMapListTests { "{ key: { type: string, value: b }, value: { type: string, value: bbb } } ]"; @Test public void testReadMapVerbose() { y.read(MAP1_JSON_OBJECT_OBJECT, "map").assertResult(MAP1_OBJ); } + // a generic type Object is written in long form even if it's a primitive, to guard against the wrong interpretation of the primitive String MAP1_JSON_STRING_OBJECT = "{ a: { type: int, value: 1 }, b: { type: string, value: bbb } }"; @Test public void testReadMapVerboseStringKey() { y.read(MAP1_JSON_STRING_OBJECT, "map").assertResult(MAP1_OBJ); } + @Test public void testReadMapVerboseStringLongTypeFormatKey() { y.read(MAP1_JSON_STRING_OBJECT, "java:java.util.Map").assertResult(MAP1_OBJ); } @Test public void testReadMapVerboseJsonKey() { y.read(MAP1_JSON_STRING_OBJECT, "map").assertResult(MAP1_OBJ); } String LIST1_JSON = "[ a, 1, b ]"; diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java index 9ab6d6c46d..8b3f18a4ef 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java @@ -24,6 +24,7 @@ import java.util.Set; import org.apache.brooklyn.util.collections.Jsonya; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.Yoml; import org.apache.brooklyn.util.yoml.YomlConfig; @@ -109,8 +110,8 @@ public YomlTestFixture doReadWriteAssertingJsonMatch() { } static void assertEqualsIgnoringQuotes(Object s1, Object s2, String message) { - if (s1 instanceof String) s1 = Strings.replaceAllNonRegex((String)s1, "\"", ""); - if (s2 instanceof String) s2 = Strings.replaceAllNonRegex((String)s2, "\"", ""); + if (s1 instanceof String) s1 = Strings.replaceAll((String)s1, MutableMap.of("\"", "", "\'", "")); + if (s2 instanceof String) s2 = Strings.replaceAll((String)s2, MutableMap.of("\"", "", "\'", "")); Assert.assertEquals(s1, s2, message); } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlUtilsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlUtilsTest.java new file mode 100644 index 0000000000..ec8da9e53d --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlUtilsTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.tests; + +import java.util.List; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.yoml.internal.YomlUtils; +import org.apache.brooklyn.util.yoml.internal.YomlUtils.GenericsParse; +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.google.common.reflect.TypeToken; + +public class YomlUtilsTest { + + @Test + public void testGenericsParse() { + GenericsParse gp = new YomlUtils.GenericsParse("foo"); + Assert.assertEquals(gp.baseType, "foo"); + Assert.assertEquals(gp.subTypes, MutableList.of("bar")); + Assert.assertTrue(gp.isGeneric); + } + + @SuppressWarnings("serial") + @Test + public void testGenericsWrite() { + MockYomlTypeRegistry tr = new MockYomlTypeRegistry(); + tr.put("lst", List.class); + tr.put("str", String.class); + Assert.assertEquals(YomlUtils.getTypeNameWithGenerics(new TypeToken>() {}, tr), + "lst"); + } + +} From 40e587f4963f1c07ec1e8e61c1e86a86b34eeec5 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 26 Sep 2016 17:31:57 +0100 Subject: [PATCH 47/77] add Asserts.assertPresent and NotPresent for working with maybes --- .../common/src/main/java/org/apache/brooklyn/test/Asserts.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java b/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java index 21a1b531f1..384473834b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java +++ b/utils/common/src/main/java/org/apache/brooklyn/test/Asserts.java @@ -728,6 +728,9 @@ public static AssertionError fail(String message) { public static AssertionError fail(Throwable error) { throw new AssertionError(error); } + public static AssertionError fail(String message, Throwable throwable) { + throw new AssertionError(message, throwable); + } public static AssertionError fail() { throw new AssertionError(); } public static void assertEqualsIgnoringOrder(Iterable actual, Iterable expected) { From 669b95eddf66857434795baca9726681af6b708a Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 26 Sep 2016 17:32:27 +0100 Subject: [PATCH 48/77] tidy and test findConstructor methods --- .../apache/brooklyn/util/javalang/Reflections.java | 11 ++++++----- .../brooklyn/util/javalang/ReflectionsTest.java | 6 ++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java index 66cb327186..228b5750d1 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Reflections.java @@ -645,7 +645,7 @@ public static List> getConstructors(Class clazz) { return (List) MutableList.copyOf(Arrays.asList(clazz.getConstructors())).appendAll(Arrays.asList(clazz.getDeclaredConstructors())); } /** Returns any constructor exactly matching the given signature, including privates and on parent classes. */ - public static Maybe> findConstructorMaybe(Class clazz, Class... parameterTypes) { + public static Maybe> findConstructorExactMaybe(Class clazz, Class... parameterTypes) { if (clazz == null) return Maybe.absentNoTrace("class is null"); Iterable> result = findConstructors(false, clazz, parameterTypes); if (!result.iterator().hasNext()) return Maybe.absentNoTrace("no Constructors matching "+clazz.getName()+"("+Arrays.asList(parameterTypes)+")"); @@ -661,12 +661,13 @@ private static Iterable> findConstructors(boolean allowCovari } List> result = MutableList.of(); - for (Constructor m: getConstructors(clazz)) { - if (m.getParameterTypes().length!=parameterTypes.length) continue; + constructors: for (Constructor m: getConstructors(clazz)) { + if (m.getParameterTypes().length!=parameterTypes.length) continue constructors; parameters: for (int i=0; iasList("hello", 3, 4, 5)).get(), "hello12"); } + @Test + public void testFindConstructors() throws Exception { + Asserts.assertPresent(Reflections.findConstructorExactMaybe(String.class, String.class)); + Asserts.assertNotPresent(Reflections.findConstructorExactMaybe(String.class, Object.class)); + } + @Test public void testConstruction() throws Exception { Assert.assertEquals(Reflections.invokeConstructorFromArgs(CI1.class, new Object[] {"hello", 3}).get().constructorArgs, ImmutableList.of("hello", 3)); From 36502345be3b8026d9b24da0f379658422b352c4 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 22 Sep 2016 17:09:01 +0100 Subject: [PATCH 49/77] prep for using config inheritance strategies, incl failing test and refactor --- .../camp/yoml/BrooklynYomlTypeRegistry.java | 4 +- .../camp/yoml/YomlTypePlanTransformer.java | 4 +- ...antiateTypeFromRegistryUsingConfigBag.java | 3 +- .../internal/ConstructionInstruction.java | 114 -------------- .../internal/ConstructionInstructions.java | 148 ++++++++++++++++++ ...antiateTypeFromRegistryUsingConfigMap.java | 3 +- .../tests/ConstructionInstructionsTest.java | 62 ++++++++ .../util/yoml/tests/MockYomlTypeRegistry.java | 3 +- .../tests/YomlConfigKeyInheritanceTests.java | 90 +++++++++++ .../util/yoml/tests/YomlTestFixture.java | 13 +- 10 files changed, 321 insertions(+), 123 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstructions.java create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConstructionInstructionsTest.java create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyInheritanceTests.java diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java index 347d51334c..b8a591a106 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java @@ -50,7 +50,7 @@ import org.apache.brooklyn.util.yoml.Yoml; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.YomlTypeRegistry; -import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions; import org.apache.brooklyn.util.yoml.internal.YomlUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -180,7 +180,7 @@ public Maybe newInstanceMaybe(String typeName, Yoml yoml, RegisteredType } } try { - return ConstructionInstruction.Factory.newDefault(t.get(), yoml==null ? null : yoml.getConfig().getConstructionInstruction()).create(); + return ConstructionInstructions.Factory.newDefault(t.get(), yoml==null ? null : yoml.getConfig().getConstructionInstruction()).create(); } catch (Exception e) { return Maybe.absent("Error instantiating type "+typeName+": "+Exceptions.collapseText(e)); } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java index 44063834f0..d619eaf6c9 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java @@ -36,7 +36,6 @@ import org.apache.brooklyn.core.typereg.UnsupportedTypePlanException; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.core.task.ValueResolver; -import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.Yoml; @@ -44,6 +43,7 @@ import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions; import org.yaml.snakeyaml.Yaml; import com.google.common.base.Preconditions; @@ -150,7 +150,7 @@ protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext co return Maybe.absent(new IllegalStateException("Type "+type+" has no plan YAML and error in type", ((Maybe.Absent)javaType).getException())); } - Maybe result = ConstructionInstruction.Factory.newDefault(javaType.get(), constructor).create(); + Maybe result = ConstructionInstructions.Factory.newDefault(javaType.get(), constructor).create(); if (result.isAbsent()) { throw new YomlException("Type '"+type+"' has no plan and its java type cannot be instantiated", ((Maybe.Absent)result).getException()); diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java index b6dccfbd8e..54c4aa8c1a 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java @@ -28,6 +28,7 @@ import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions; import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; @Alias("config-bag-constructor") @@ -69,7 +70,7 @@ protected Map getRawConfigMap(Field f, Object obj) throws Illega protected ConstructionInstruction newConstructor(Class type, Map fieldsFromReadToConstructJava, ConstructionInstruction optionalOuter) { Maybe constructor = findConfigBagConstructor(type); if (constructor.isPresent() && ConfigBag.class.isAssignableFrom( (((Constructor)constructor.get()).getParameterTypes()[0]) )) { - return ConstructionInstruction.Factory.newUsingConstructorWithArgs(type, MutableList.of( + return ConstructionInstructions.Factory.newUsingConstructorWithArgs(type, MutableList.of( ConfigBag.newInstance(fieldsFromReadToConstructJava)), optionalOuter); } return super.newConstructor(type, fieldsFromReadToConstructJava, optionalOuter); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java index bf12fb5e5f..9b1476ed90 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java @@ -18,17 +18,9 @@ */ package org.apache.brooklyn.util.yoml.internal; -import java.lang.reflect.Modifier; -import java.util.Iterator; import java.util.List; -import java.util.Map; -import javax.annotation.Nullable; - -import org.apache.brooklyn.util.collections.MutableList; -import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.guava.Maybe; -import org.apache.brooklyn.util.javalang.Reflections; /** Capture instructions on how an object should be created. *

@@ -44,110 +36,4 @@ public interface ConstructionInstruction { public ConstructionInstruction getOuterInstruction(); - public static class Factory { - - public static ConstructionInstruction newDefault(Class type, ConstructionInstruction optionalOuter) { - if (optionalOuter!=null) { - if (optionalOuter instanceof ConstructorWithArgsInstruction) - return newUsingConstructorWithArgs(type, null, optionalOuter); - } - return newUsingConstructorWithArgs(type, null, null); - } - - public static ConstructionInstruction newUsingConstructorWithArgs(Class type, List args, ConstructionInstruction optionalOuter) { - return new ConstructorWithArgsInstruction(type, args, (ConstructorWithArgsInstruction)optionalOuter); - } - - /** Merge arg lists, as follows: - * if either list is null, take the other; - * otherwise require them to be the same length. - *

- * For each entry, if they are maps, merge deep preferring the latter's values when they are not maps. - * If they are anything else, we prefer the latter. - */ - public static List mergeArgumentLists(List olderArgs, List args) { - List newArgs; - if (olderArgs==null || olderArgs.isEmpty()) newArgs = MutableList.copyOf(args); - else if (args==null) newArgs = MutableList.copyOf(olderArgs); - else { - if (olderArgs.size() != args.size()) - throw new IllegalStateException("Incompatible arguments, sizes "+olderArgs.size()+" and "+args.size()+": "+olderArgs+" and "+args); - // merge - newArgs = MutableList.of(); - Iterator i1 = olderArgs.iterator(); - Iterator i2 = args.iterator(); - while (i2.hasNext()) { - Object o1 = i1.next(); - Object o2 = i2.next(); - if ((o2 instanceof Map) && (o1 instanceof Map)) { - o2 = mergeMapsDeep((Map)o1, (Map)o2); - } - newArgs.add(o2); - } - } - - return newArgs; - } - - public static Map mergeMapsDeep(Map o1, Map o2) { - MutableMap result = MutableMap.copyOf(o1); - if (o2!=null) { - for (Map.Entry e2: o2.entrySet()) { - Object v2 = e2.getValue(); - if (v2 instanceof Map) { - Object old = result.get(e2.getKey()); - if (old instanceof Map) { - v2 = mergeMapsDeep((Map)old, (Map)v2); - } - } - result.put(e2.getKey(), v2); - } - } - return result; - } - } - - public static class ConstructorWithArgsInstruction implements ConstructionInstruction { - private final Class type; - private final List args; - private final ConstructorWithArgsInstruction outerInstruction; - - protected ConstructorWithArgsInstruction(Class type, List args, @Nullable ConstructorWithArgsInstruction outerInstruction) { - this.type = type; - this.args = args; - this.outerInstruction = outerInstruction; - } - - @Override public Class getType() { return type; } - @Override public List getArgs() { return args; } - @Override public ConstructionInstruction getOuterInstruction() { return outerInstruction; } - - @Override - public Maybe create() { - return create(null, null); - } - - protected Maybe create(Class typeConstraintSoFar, List argsSoFar) { - if (typeConstraintSoFar==null) typeConstraintSoFar = type; - if (type!=null) { - if (typeConstraintSoFar==null || typeConstraintSoFar.isAssignableFrom(type)) typeConstraintSoFar = type; - else if (type.isAssignableFrom(typeConstraintSoFar)) { /* fine */ } - else { - throw new IllegalStateException("Incompatible expected types "+typeConstraintSoFar+" and "+type); - } - } - - List newArgs = Factory.mergeArgumentLists(argsSoFar, args); - - if (outerInstruction!=null) { - return outerInstruction.create(typeConstraintSoFar, newArgs); - } - - if (typeConstraintSoFar==null) throw new IllegalStateException("No type information available"); - if ((typeConstraintSoFar.getModifiers() & (Modifier.ABSTRACT | Modifier.INTERFACE)) != 0) - throw new IllegalStateException("Insufficient type information: expected "+type+" is not directly instantiable"); - return Reflections.invokeConstructorFromArgsIncludingPrivate(typeConstraintSoFar, newArgs.toArray()); - } - } - } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstructions.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstructions.java new file mode 100644 index 0000000000..cc3a7d3e60 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstructions.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.internal; + +import java.lang.reflect.Modifier; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.javalang.Reflections; + +public class ConstructionInstructions { + + public static class Factory { + + /** Returns a new construction instruction which creates an instance of the given type, + * preferring the optional instruction but enforcing the given type constraint, + * and if there is no outer instruction it creates from a no-arg constructor */ + public static ConstructionInstruction newDefault(Class type, ConstructionInstruction optionalOuter) { + if (optionalOuter!=null) { + if (optionalOuter instanceof ConstructorWithArgsInstruction) + return newUsingConstructorWithArgs(type, null, optionalOuter); + } + return newUsingConstructorWithArgs(type, null, null); + } + + /** Returns a new construction instruction which creates an instance of the given type with the given args, + * preferring the optional instruction but enforcing the given type constraint and passing the given args for it to inherit, + * and if there is no outer instruction it creates from a constructor taking the given args + * (or no-arg if null, making it the same as {@link #newDefault(Class, ConstructionInstruction)}) */ + public static ConstructionInstruction newUsingConstructorWithArgs(Class type, @Nullable List args, ConstructionInstruction optionalOuter) { + return new ConstructorWithArgsInstruction(type, args, (ConstructorWithArgsInstruction)optionalOuter); + } + + /** Merge arg lists, as follows: + * if either list is null, take the other; + * otherwise require them to be the same length. + *

+ * For each entry, if they are maps, merge deep preferring the latter's values when they are not maps. + * If they are anything else, we prefer the latter. + */ + public static List mergeArgumentLists(List olderArgs, List args) { + List newArgs; + if (olderArgs==null || olderArgs.isEmpty()) newArgs = MutableList.copyOf(args); + else if (args==null) newArgs = MutableList.copyOf(olderArgs); + else { + if (olderArgs.size() != args.size()) + throw new IllegalStateException("Incompatible arguments, sizes "+olderArgs.size()+" and "+args.size()+": "+olderArgs+" and "+args); + // merge + newArgs = MutableList.of(); + Iterator i1 = olderArgs.iterator(); + Iterator i2 = args.iterator(); + while (i2.hasNext()) { + Object o1 = i1.next(); + Object o2 = i2.next(); + if ((o2 instanceof Map) && (o1 instanceof Map)) { + o2 = mergeMapsDeep((Map)o1, (Map)o2); + } + newArgs.add(o2); + } + } + + return newArgs; + } + + public static Map mergeMapsDeep(Map o1, Map o2) { + MutableMap result = MutableMap.copyOf(o1); + if (o2!=null) { + for (Map.Entry e2: o2.entrySet()) { + Object v2 = e2.getValue(); + if (v2 instanceof Map) { + Object old = result.get(e2.getKey()); + if (old instanceof Map) { + v2 = mergeMapsDeep((Map)old, (Map)v2); + } + } + result.put(e2.getKey(), v2); + } + } + return result; + } + } + + public static class ConstructorWithArgsInstruction implements ConstructionInstruction { + private final Class type; + private final List args; + private final ConstructorWithArgsInstruction outerInstruction; + + protected ConstructorWithArgsInstruction(Class type, List args, @Nullable ConstructorWithArgsInstruction outerInstruction) { + this.type = type; + this.args = args; + this.outerInstruction = outerInstruction; + } + + @Override public Class getType() { return type; } + @Override public List getArgs() { return args; } + @Override public ConstructionInstruction getOuterInstruction() { return outerInstruction; } + + @Override + public Maybe create() { + return create(null, null); + } + + protected Maybe create(Class typeConstraintSoFar, List argsSoFar) { + if (typeConstraintSoFar==null) typeConstraintSoFar = type; + if (type!=null) { + if (typeConstraintSoFar==null || typeConstraintSoFar.isAssignableFrom(type)) typeConstraintSoFar = type; + else if (type.isAssignableFrom(typeConstraintSoFar)) { /* fine */ } + else { + throw new IllegalStateException("Incompatible expected types "+typeConstraintSoFar+" and "+type); + } + } + + List newArgs = Factory.mergeArgumentLists(argsSoFar, args); + + if (outerInstruction!=null) { + return outerInstruction.create(typeConstraintSoFar, newArgs); + } + + if (typeConstraintSoFar==null) throw new IllegalStateException("No type information available"); + if ((typeConstraintSoFar.getModifiers() & (Modifier.ABSTRACT | Modifier.INTERFACE)) != 0) + throw new IllegalStateException("Insufficient type information: expected "+type+" is not directly instantiable"); + return Reflections.invokeConstructorFromArgsIncludingPrivate(typeConstraintSoFar, newArgs.toArray()); + } + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java index 596d1d750a..6d983b48c6 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java @@ -33,6 +33,7 @@ import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; import org.apache.brooklyn.util.yoml.internal.YomlContext; @@ -264,7 +265,7 @@ protected Maybe findConstructorMaybe(Class type) { } protected ConstructionInstruction newConstructor(Class type, Map fieldsFromReadToConstructJava, ConstructionInstruction optionalOuter) { - return ConstructionInstruction.Factory.newUsingConstructorWithArgs(type, MutableList.of(fieldsFromReadToConstructJava), optionalOuter); + return ConstructionInstructions.Factory.newUsingConstructorWithArgs(type, MutableList.of(fieldsFromReadToConstructJava), optionalOuter); } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConstructionInstructionsTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConstructionInstructionsTest.java new file mode 100644 index 0000000000..44ead18941 --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConstructionInstructionsTest.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.tests; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ConstructionInstructionsTest { + + @Test + public void testBasicNoArgs() { + ConstructionInstruction c1 = ConstructionInstructions.Factory.newDefault(String.class, null); + Assert.assertEquals(c1.create().get(), ""); + } + + @Test + public void testBasicWithArgs() { + ConstructionInstruction c1 = ConstructionInstructions.Factory.newUsingConstructorWithArgs(String.class, MutableList.of("x"), null); + Assert.assertEquals(c1.create().get(), "x"); + } + + @Test + public void testBasicArgsFromWrapper() { + ConstructionInstruction c1 = ConstructionInstructions.Factory.newDefault(String.class, null); + ConstructionInstruction c2 = ConstructionInstructions.Factory.newUsingConstructorWithArgs(null, MutableList.of("x"), c1); + Assert.assertEquals(c2.create().get(), "x"); + } + + @Test + public void testBasicArgsWrapped() { + ConstructionInstruction c1 = ConstructionInstructions.Factory.newUsingConstructorWithArgs(null, MutableList.of("x"), null); + ConstructionInstruction c2 = ConstructionInstructions.Factory.newDefault(String.class, c1); + Assert.assertEquals(c2.create().get(), "x"); + } + + @Test + public void testBasicArgsOverwrite() { + ConstructionInstruction c1 = ConstructionInstructions.Factory.newUsingConstructorWithArgs(String.class, MutableList.of("x"), null); + ConstructionInstruction c2 = ConstructionInstructions.Factory.newUsingConstructorWithArgs(null, MutableList.of("y"), c1); + Assert.assertEquals(c2.create().get(), "x"); + } + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java index bf47a537ef..8b54bf029e 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java @@ -34,6 +34,7 @@ import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.YomlTypeRegistry; import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions; import org.apache.brooklyn.util.yoml.internal.YomlUtils; import com.google.common.collect.Iterables; @@ -82,7 +83,7 @@ public Maybe newInstanceMaybe(String typeName, Yoml yoml) { return Maybe.absent(new IllegalStateException("Incomplete hierarchy for "+type, ((Maybe.Absent)javaType).getException())); } - return ConstructionInstruction.Factory.newDefault(javaType.get(), constructor).create(); + return ConstructionInstructions.Factory.newDefault(javaType.get(), constructor).create(); } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyInheritanceTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyInheritanceTests.java new file mode 100644 index 0000000000..19ed20f6b8 --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyInheritanceTests.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.tests; + +import java.util.Map; + +import org.apache.brooklyn.config.ConfigInheritance; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.Jsonya; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConstructor; +import org.apache.brooklyn.util.yoml.tests.TopLevelConfigKeysTests.MockConfigKey; +import org.apache.brooklyn.util.yoml.tests.YomlConfigKeyGenericsTests.M0; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import com.google.common.reflect.TypeToken; + +/** Tests that the default serializers can read/write types and fields. + *

+ * And shows how to use them at a low level. + */ +public class YomlConfigKeyInheritanceTests { + + private static final Logger log = LoggerFactory.getLogger(YomlConfigKeyInheritanceTests.class); + + @YomlConfigMapConstructor("conf") + @SuppressWarnings({ "deprecation" }) + static class M1 extends M0 { + @SuppressWarnings("serial") + static MockConfigKey> KM = new MockConfigKey>(new TypeToken>() {}, "km"); + static { KM.inheritance = ConfigInheritance.DEEP_MERGE; } + + @SuppressWarnings("serial") + static MockConfigKey> KO = new MockConfigKey>(new TypeToken>() {}, "ko"); + static { KO.inheritance = ConfigInheritance.ALWAYS; } + + M1(Map keys) { super(keys); } + } + + @Test + public void testReadMergedMap() { + YomlTestFixture y = YomlTestFixture.newInstance() + .addTypeWithAnnotations("m1", M1.class) + .addType("m1a", "{ type: m1, km: { a: 1, b: 1 }, ko: { a: 1, b: 1 } }") + .addType("m1b", "{ type: m1a, km: { b: 2 }, ko: { b: 2 } }"); + + y.read("{ type: m1b }", null); + + M1 m1b = (M1)y.lastReadResult; + Asserts.assertEquals(m1b.conf.get(M1.KM.getName()), MutableMap.of("a", 1, "b", 2)); + // MERGE is the default, OVERWRITE not respected: + Asserts.assertEquals(m1b.conf.get(M1.KO.getName()), MutableMap.of("b", 2)); + } + + @Test + public void testWrite() { + // the write is not smart enough to look at default/inherited KV pairs + // (this would be nice to change, but a lot of work and not really worth it) + + YomlTestFixture y = YomlTestFixture.newInstance() + .addTypeWithAnnotationsAndConfig("m1", M1.class, MutableMap.of("keys", "config")); + + M1 m1b = new M1(MutableMap.of("km", MutableMap.of("a", 1, "b", 2), "ko", MutableMap.of("a", 1, "b", 2))); + y.write(m1b); + log.info("written as "+y.lastWriteResult); + YomlTestFixture.assertEqualsIgnoringQuotes(Jsonya.newInstance().add(y.lastWriteResult).toString(), + "{ type=m1, km:{ a: 1, b: 2}, ko: { a: 1, b: 2 } }", "wrong serialization"); + YomlTestFixture.assertEqualsIgnoringQuotes(y.lastWriteResult.toString(), + "{ type=m1, km:{ a: 1, b: 2}, ko: { a: 1, b: 2 } }", "wrong serialization"); + } + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java index 8b3f18a4ef..40507494f6 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java @@ -109,9 +109,18 @@ public YomlTestFixture doReadWriteAssertingJsonMatch() { return this; } + private static String removeGuff(String input) { + return Strings.replaceAll(input, MutableMap.of("\"", "", "\'", "") + .add("=", ": ").add(": ", ": ").add(" :", ":") + .add(" ,", ",").add(", ", ",") + .add("{ ", "{").add(" {", "{") + .add(" }", "}").add("} ", "}") + ); + } + static void assertEqualsIgnoringQuotes(Object s1, Object s2, String message) { - if (s1 instanceof String) s1 = Strings.replaceAll((String)s1, MutableMap.of("\"", "", "\'", "")); - if (s2 instanceof String) s2 = Strings.replaceAll((String)s2, MutableMap.of("\"", "", "\'", "")); + if (s1 instanceof String) s1 = removeGuff((String)s1); + if (s2 instanceof String) s2 = removeGuff((String)s2); Assert.assertEquals(s1, s2, message); } From 12d530f97c39bba1f88dc5169692b08dc3e4ec30 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 22 Sep 2016 20:25:14 +0100 Subject: [PATCH 50/77] config key inheritance strategies working within yoml --- .../camp/yoml/BrooklynYomlAnnotations.java | 21 +- ...figBagConstructionWithArgsInstruction.java | 48 ++++ .../ConfigKeyConstructionInstructions.java | 30 +++ ...KeyMapConstructionWithArgsInstruction.java | 170 +++++++++++++ ...antiateTypeFromRegistryUsingConfigBag.java | 22 +- ...iateTypeFromRegistryUsingConfigKeyMap.java | 44 ++++ .../yoml/YomlConfigKeyInheritanceTests.java | 102 ++++++++ .../camp/yoml/YomlTypeRegistryBasicTest.java | 1 + .../brooklyn/core/config/BasicConfigKey.java | 10 +- .../core}/yoml/YomlConfigBagConstructor.java | 2 +- .../yoml/annotations/YomlAnnotations.java | 27 ++- .../internal/ConstructionInstruction.java | 4 +- .../internal/ConstructionInstructions.java | 225 ++++++++++++++---- .../ConfigInMapUnderConfigSerializer.java | 4 +- ...antiateTypeFromRegistryUsingConfigMap.java | 34 ++- .../TopLevelConfigKeySerializer.java | 2 +- .../serializers/TopLevelFieldsBlackboard.java | 29 ++- .../yoml/tests/TopLevelConfigKeysTests.java | 16 +- .../tests/YomlConfigKeyGenericsTests.java | 4 +- .../tests/YomlConfigKeyInheritanceTests.java | 8 +- .../util/yoml/tests/YomlTestFixture.java | 26 +- 21 files changed, 709 insertions(+), 120 deletions(-) create mode 100644 camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigBagConstructionWithArgsInstruction.java create mode 100644 camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyConstructionInstructions.java create mode 100644 camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyMapConstructionWithArgsInstruction.java create mode 100644 camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigKeyMap.java create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlConfigKeyInheritanceTests.java rename {camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp => core/src/main/java/org/apache/brooklyn/util/core}/yoml/YomlConfigBagConstructor.java (97%) diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java index 82f746b0f2..fc93c0eeaa 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java @@ -23,8 +23,12 @@ import java.util.Set; import org.apache.brooklyn.camp.yoml.serializers.InstantiateTypeFromRegistryUsingConfigBag; +import org.apache.brooklyn.camp.yoml.serializers.InstantiateTypeFromRegistryUsingConfigKeyMap; +import org.apache.brooklyn.util.core.yoml.YomlConfigBagConstructor; +import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations; +import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConstructor; public class BrooklynYomlAnnotations extends YomlAnnotations { @@ -37,10 +41,23 @@ public Collection findConfigBagConstructorSerializers(Class t ann.validateAheadOfTime(), ann.requireStaticKeys()); } + public Collection findConfigMapConstructorSerializersIgnoringInheritance(Class t) { + throw new UnsupportedOperationException("ensure this doesn't get called"); + } + + public Collection findConfigMapConstructorSerializersWithInheritance(Class t) { + YomlConfigMapConstructor ann = t.getAnnotation(YomlConfigMapConstructor.class); + if (ann==null) return Collections.emptyList(); + return new InstantiateTypeFromRegistryUsingConfigKeyMap.Factory().newConfigKeySerializersForType( + t, + ann.value(), Strings.isNonBlank(ann.writeAsKey()) ? ann.writeAsKey() : ann.value(), + ann.validateAheadOfTime(), ann.requireStaticKeys()); + } + @Override - protected void collectSerializerAnnotationsAtClass(Set result, Class type) { + protected void collectSerializersForConfig(Set result, Class type) { result.addAll(findConfigBagConstructorSerializers(type)); - super.collectSerializerAnnotationsAtClass(result, type); + result.addAll(findConfigMapConstructorSerializersWithInheritance(type)); } } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigBagConstructionWithArgsInstruction.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigBagConstructionWithArgsInstruction.java new file mode 100644 index 0000000000..15f82b8a1d --- /dev/null +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigBagConstructionWithArgsInstruction.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.yoml.serializers; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions.WrappingConstructionInstruction; + +import com.google.common.collect.Iterables; + +/** Like {@link ConfigKeyMapConstructionWithArgsInstruction} but passing a {@link ConfigBag} as the single argument + * to the constructor. */ +public class ConfigBagConstructionWithArgsInstruction extends ConfigKeyMapConstructionWithArgsInstruction { + + public ConfigBagConstructionWithArgsInstruction(Class type, Map values, + WrappingConstructionInstruction optionalOuter, Map> keysByAlias) { + super(type, values, optionalOuter, keysByAlias); + } + + @Override + protected List combineArguments(List constructorsSoFarOutermostFirst) { + return MutableList.of( + ConfigBag.newInstance( + (Map)Iterables.getOnlyElement( super.combineArguments(constructorsSoFarOutermostFirst) ))); + } + +} diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyConstructionInstructions.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyConstructionInstructions.java new file mode 100644 index 0000000000..dfeda29475 --- /dev/null +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyConstructionInstructions.java @@ -0,0 +1,30 @@ +package org.apache.brooklyn.camp.yoml.serializers; + +import java.util.Map; + +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions.WrappingConstructionInstruction; + +public class ConfigKeyConstructionInstructions { + + /** As the others, but expecting a one-arg constructor which takes a config map; + * this will deduce the appropriate values by looking at + * the inheritance declarations at the keys and at the values at the outer and inner constructor instructions. */ + public static ConstructionInstruction newUsingConfigKeyMapConstructor(Class type, + Map values, + ConstructionInstruction optionalOuter, + Map> keysByAlias) { + return new ConfigKeyMapConstructionWithArgsInstruction(type, values, (WrappingConstructionInstruction) optionalOuter, + keysByAlias); + } + + public static ConstructionInstruction newUsingConfigBagConstructor(Class type, + Map values, + ConstructionInstruction optionalOuter, + Map> keysByAlias) { + return new ConfigBagConstructionWithArgsInstruction(type, values, (WrappingConstructionInstruction) optionalOuter, + keysByAlias); + } + +} diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyMapConstructionWithArgsInstruction.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyMapConstructionWithArgsInstruction.java new file mode 100644 index 0000000000..aebae24fcf --- /dev/null +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyMapConstructionWithArgsInstruction.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.yoml.serializers; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.brooklyn.config.ConfigInheritance; +import org.apache.brooklyn.config.ConfigInheritances; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.config.ConfigValueAtContainer; +import org.apache.brooklyn.core.config.BasicConfigInheritance; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.config.ConfigKeys.InheritanceContext; +import org.apache.brooklyn.core.config.internal.AncestorContainerAndKeyValueIterator; +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.collections.MutableSet; +import org.apache.brooklyn.util.exceptions.ReferenceWithError; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions.AbstractConstructionWithArgsInstruction; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions.WrappingConstructionInstruction; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.collect.Iterables; + +/** Instruction for using a constructor which takes a single argument being a map of string,object + * config keys. This will look at the actual keys defined on the types and traverse the construction + * instruction hierarchy to ensure the key values are inherited correctly from supertype definitions. */ +public class ConfigKeyMapConstructionWithArgsInstruction extends AbstractConstructionWithArgsInstruction { + protected final Map> keysByNameOrAlias; + + public ConfigKeyMapConstructionWithArgsInstruction(Class type, + Map values, + WrappingConstructionInstruction optionalOuter, + Map> keysByNameOrAlias) { + super(type, MutableList.of(values), optionalOuter); + this.keysByNameOrAlias = keysByNameOrAlias; + } + + @Override + protected List combineArguments(final List constructorsSoFarOutermostFirst) { + Set innerNames = MutableSet.of(); + for (ConstructionInstruction i: constructorsSoFarOutermostFirst) { + innerNames.addAll(getKeyValuesAt(i).keySet()); + } + + // collect all keys, real local ones, and anonymous ones for anonymous config from parents + + // TODO if keys are defined at yaml-based type or type parent we don't currently have a way to get them + // (the TypeRegistry API needs to return more than just java class for that) + final Map> keysByName = MutableMap.of(); + + for (ConfigKey keyDeclaredHere: keysByNameOrAlias.values()) { + keysByName.put(keyDeclaredHere.getName(), keyDeclaredHere); + } + + MutableMap,Object> localValues = MutableMap.of(); + for (Map.Entry aliasAndValueHere: getKeyValuesAt(this).entrySet()) { + ConfigKey k = keysByNameOrAlias.get(aliasAndValueHere.getKey()); + if (k==null) { + // don't think it should come here; all keys will be known + k = anonymousKey(aliasAndValueHere.getKey()); + } + if (!keysByName.containsKey(k.getName())) { + keysByName.put(k.getName(), k); + } + if (!localValues.containsKey(k.getName())) { + localValues.put(k, aliasAndValueHere.getValue()); + } + } + + for (String innerKeyName: innerNames) { + if (!keysByName.containsKey(innerKeyName)) { + // parent defined a value under a key which doesn't match config keys we know + keysByName.put(innerKeyName, anonymousKey(innerKeyName)); + } + } + + Map result = MutableMap.of(); + for (final ConfigKey k: keysByName.values()) { + // go through all keys defined here recognising aliases, + // and anonymous keys for other keys at parents (ignoring aliases) + Maybe value = localValues.containsKey(k) ? Maybe.ofAllowingNull(localValues.get(k)) : Maybe.absent(); + // don't set default values +// Maybe defaultValue = k.hasDefaultValue() ? Maybe.ofAllowingNull((Object)k.getDefaultValue()) : Maybe.absent(); + + Function> keyFn = new Function>() { + @SuppressWarnings("unchecked") + @Override + public ConfigKey apply(ConstructionInstruction input) { + // type inheritance so pretty safe to assume outermost key declaration + return (ConfigKey) keysByName.get(input); + } + }; + Function> lookupFn = new Function>() { + @Override + public Maybe apply(ConstructionInstruction input) { + Map values = getKeyValuesAt(input); // TODO allow aliases? + if (values.containsKey(k.getName())) return Maybe.of((Object)values.get(k.getName())); + return Maybe.absent(); + } + }; + Function, Maybe> coerceFn = Functions.identity(); + Function parentFn = new Function() { + @Override + public ConstructionInstruction apply(ConstructionInstruction input) { + // parent is the one *after* us in the list, *not* input.getOuterInstruction() + Iterator ci = constructorsSoFarOutermostFirst.iterator(); + ConstructionInstruction cc = ConfigKeyMapConstructionWithArgsInstruction.this; + while (ci.hasNext()) { + if (input.equals(cc)) { + return ci.next(); + } + cc = ci.next(); + } + return null; + } + }; + Iterator> ancestors = new AncestorContainerAndKeyValueIterator( + this, keyFn, lookupFn, coerceFn, parentFn); + + ConfigInheritance inheritance = ConfigInheritances.findInheritance(k, InheritanceContext.TYPE_DEFINITION, BasicConfigInheritance.OVERWRITE); + + @SuppressWarnings("unchecked") + ReferenceWithError> newValue = + ConfigInheritances.resolveInheriting(this, (ConfigKey)k, value, Maybe.absent(), + ancestors, InheritanceContext.TYPE_DEFINITION, inheritance); + + if (newValue.getWithError().isValueExplicitlySet()) + result.put(k.getName(), newValue.getWithError().get()); + } + + return MutableList.of(result); + } + + protected ConfigKey anonymousKey(String key) { + return ConfigKeys.newConfigKey(Object.class, key); + } + + @SuppressWarnings("unchecked") + protected Map getKeyValuesAt(ConstructionInstruction i) { + if (i.getArgs()==null) return MutableMap.of(); + if (i.getArgs().size()!=1) throw new IllegalArgumentException("Wrong length of constructor params, expected one: "+i.getArgs()); + Object arg = Iterables.getOnlyElement(i.getArgs()); + if (arg==null) return MutableMap.of(); + if (!(arg instanceof Map)) throw new IllegalArgumentException("Wrong type of constructor param, expected map: "+arg); + return (Map)arg; + } +} diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java index 54c4aa8c1a..6b0116eae3 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java @@ -18,17 +18,15 @@ */ package org.apache.brooklyn.camp.yoml.serializers; -import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.Map; -import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; -import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions; import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; @Alias("config-bag-constructor") @@ -43,14 +41,15 @@ protected InstantiateTypeFromRegistryUsingConfigBag newInstance() { protected Maybe findConstructorMaybe(Class type) { Maybe c = findConfigBagConstructor(type); if (c.isPresent()) return c; - Maybe c2 = Reflections.findConstructorMaybe(type, Map.class); + Maybe c2 = super.findConstructorMaybe(type); if (c2.isPresent()) return c2; return c; } protected Maybe findConfigBagConstructor(Class type) { - return Reflections.findConstructorMaybe(type, ConfigBag.class); + return Reflections.findConstructorExactMaybe(type, ConfigBag.class); } + protected Maybe findFieldMaybe(Class type) { Maybe f = Reflections.findFieldMaybe(type, fieldNameForConfigInJavaIfPreset); if (f.isPresent() && !(Map.class.isAssignableFrom(f.get().getType()) || ConfigBag.class.isAssignableFrom(f.get().getType()))) @@ -67,13 +66,14 @@ protected Map getRawConfigMap(Field f, Object obj) throws Illega } @Override - protected ConstructionInstruction newConstructor(Class type, Map fieldsFromReadToConstructJava, ConstructionInstruction optionalOuter) { - Maybe constructor = findConfigBagConstructor(type); - if (constructor.isPresent() && ConfigBag.class.isAssignableFrom( (((Constructor)constructor.get()).getParameterTypes()[0]) )) { - return ConstructionInstructions.Factory.newUsingConstructorWithArgs(type, MutableList.of( - ConfigBag.newInstance(fieldsFromReadToConstructJava)), optionalOuter); + protected ConstructionInstruction newConstructor(Class type, Map> keysByAlias, + Map fieldsFromReadToConstructJava, ConstructionInstruction optionalOuter) { + if (findConfigBagConstructor(type).isPresent()) { + return ConfigKeyConstructionInstructions.newUsingConfigBagConstructor(type, fieldsFromReadToConstructJava, optionalOuter, + keysByAlias); } - return super.newConstructor(type, fieldsFromReadToConstructJava, optionalOuter); + return ConfigKeyConstructionInstructions.newUsingConfigKeyMapConstructor(type, fieldsFromReadToConstructJava, optionalOuter, + keysByAlias); } } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigKeyMap.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigKeyMap.java new file mode 100644 index 0000000000..7750714e33 --- /dev/null +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigKeyMap.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.yoml.serializers; + +import java.util.Map; + +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; + +@Alias("config-map-constructor") +public class InstantiateTypeFromRegistryUsingConfigKeyMap extends InstantiateTypeFromRegistryUsingConfigMap { + + public static class Factory extends InstantiateTypeFromRegistryUsingConfigMap.Factory { + protected InstantiateTypeFromRegistryUsingConfigKeyMap newInstance() { + return new InstantiateTypeFromRegistryUsingConfigKeyMap(); + } + } + + @Override + protected ConstructionInstruction newConstructor(Class type, Map> keysByAlias, + Map fieldsFromReadToConstructJava, ConstructionInstruction optionalOuter) { + return ConfigKeyConstructionInstructions.newUsingConfigKeyMapConstructor(type, fieldsFromReadToConstructJava, optionalOuter, + keysByAlias); + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlConfigKeyInheritanceTests.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlConfigKeyInheritanceTests.java new file mode 100644 index 0000000000..b74fe11789 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlConfigKeyInheritanceTests.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.yoml; + +import java.util.Map; + +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.BasicConfigInheritance; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConstructor; +import org.apache.brooklyn.util.yoml.tests.YomlTestFixture; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import com.google.common.reflect.TypeToken; + +/** Tests that config key inheritance strategies are obeyed when reading with supertypes. + */ +public class YomlConfigKeyInheritanceTests { + + private static final Logger log = LoggerFactory.getLogger(YomlConfigKeyInheritanceTests.class); + + @YomlConfigMapConstructor("conf") + static class M0 { + Map conf = MutableMap.of(); + M0(Map keys) { this.conf.putAll(keys); } + + @Override + public boolean equals(Object obj) { + return (obj instanceof M0) && ((M0)obj).conf.equals(conf); + } + @Override + public int hashCode() { + return conf.hashCode(); + } + @Override + public String toString() { + return super.toString()+conf; + } + } + + @YomlConfigMapConstructor("conf") + static class M1 extends M0 { + @SuppressWarnings("serial") + static ConfigKey> KM = ConfigKeys.builder(new TypeToken>() {}, "km") + .typeInheritance(BasicConfigInheritance.DEEP_MERGE).build(); + + @SuppressWarnings("serial") + static ConfigKey> KO = ConfigKeys.builder(new TypeToken>() {}, "ko") + .typeInheritance(BasicConfigInheritance.OVERWRITE).build(); + + M1(Map keys) { super(keys); } + } + + @Test + public void testReadMergedMap() { + YomlTestFixture y = BrooklynYomlTestFixture.newInstance().addTypeWithAnnotations("m1", M1.class) + .addType("m1a", "{ type: m1, km: { a: 1, b: 1 }, ko: { a: 1, b: 1 } }") + .addType("m1b", "{ type: m1a, km: { b: 2 }, ko: { b: 2 } }"); + + y.read("{ type: m1b }", null); + + M1 m1b = (M1)y.getLastReadResult(); + Asserts.assertEquals(m1b.conf.get(M1.KM.getName()), MutableMap.of("a", 1, "b", 2)); + Asserts.assertEquals(m1b.conf.get(M1.KO.getName()), MutableMap.of("b", 2)); + } + + @Test + public void testWrite() { + // the write is not smart enough to look at default/inherited KV pairs + // (this would be nice to change, but a lot of work and not really worth it) + + YomlTestFixture y = YomlTestFixture.newInstance() + .addTypeWithAnnotationsAndConfigFieldsIgnoringInheritance("m1", M1.class, MutableMap.of("keys", "config")); + + M1 m1b = new M1(MutableMap.of("km", MutableMap.of("a", 1, "b", 2), "ko", MutableMap.of("a", 1, "b", 2))); + y.write(m1b); + log.info("written as "+y.getLastWriteResult()); + y.assertLastWriteIgnoringQuotes( + "{ type=m1, km:{ a: 1, b: 2 }, ko: { a: 1, b: 2 }}", "wrong serialization"); + } + +} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryBasicTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryBasicTest.java index ea02508a77..488867f680 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryBasicTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryBasicTest.java @@ -31,6 +31,7 @@ import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.yoml.YomlConfigBagConstructor; import org.apache.brooklyn.util.javalang.JavaClassNames; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; diff --git a/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java index 2600ae7557..49d67a8e9e 100644 --- a/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java +++ b/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java @@ -243,7 +243,7 @@ public BasicConfigKey(Builder builder) { this.description = builder.description; this.defaultValue = builder.defaultValue; this.reconfigurable = builder.reconfigurable; - this.parentInheritance = builder.runtimeInheritance; + this.runtimeInheritance = builder.runtimeInheritance; this.typeInheritance = builder.typeInheritance; // Note: it's intentionally possible to have default values that are not valid // per the configured constraint. If validity were checked here any class that @@ -319,16 +319,12 @@ public ConfigInheritance getInheritance() { @Deprecated @Override @Nullable public ConfigInheritance getTypeInheritance() { - return typeInheritance; + return getInheritanceByContext(InheritanceContext.TYPE_DEFINITION); } @Deprecated @Override @Nullable public ConfigInheritance getParentInheritance() { - if (parentInheritance == null && inheritance != null) { - parentInheritance = inheritance; - inheritance = null; - } - return parentInheritance; + return getInheritanceByContext(InheritanceContext.RUNTIME_MANAGEMENT); } /** @see ConfigKey#getConstraint() */ diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConfigBagConstructor.java b/core/src/main/java/org/apache/brooklyn/util/core/yoml/YomlConfigBagConstructor.java similarity index 97% rename from camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConfigBagConstructor.java rename to core/src/main/java/org/apache/brooklyn/util/core/yoml/YomlConfigBagConstructor.java index 21c9fc153c..632eadce64 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlConfigBagConstructor.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/yoml/YomlConfigBagConstructor.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.brooklyn.camp.yoml; +package org.apache.brooklyn.util.core.yoml; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java index 3f08f6e2dd..f00c0addde 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java @@ -74,10 +74,10 @@ public Collection findTopLevelFieldSerializers(Class return result; } - public Collection findConfigMapConstructorSerializers(Class t) { + public Collection findConfigMapConstructorSerializersIgnoringInheritance(Class t) { YomlConfigMapConstructor ann = t.getAnnotation(YomlConfigMapConstructor.class); if (ann==null) return Collections.emptyList(); - return new InstantiateTypeFromRegistryUsingConfigMap.Factory().newConfigKeySerializersForType( + return InstantiateTypeFromRegistryUsingConfigMap.newFactoryIgnoringInheritance().newConfigKeySerializersForType( t, ann.value(), Strings.isNonBlank(ann.writeAsKey()) ? ann.writeAsKey() : ann.value(), ann.validateAheadOfTime(), ann.requireStaticKeys()); @@ -127,17 +127,26 @@ public Set findSerializerAnnotations(Class type, boolean recu } protected void collectSerializerAnnotationsAtClass(Set result, Class type) { + collectSerializersLowLevel(result, type); + collectSerializersForConfig(result, type); + collectSerializersFields(result, type); + // subclasses can extend or override the methods above + } + + protected void collectSerializersFields(Set result, Class type) { + YomlAllFieldsTopLevel allFields = type.getAnnotation(YomlAllFieldsTopLevel.class); + result.addAll(findTopLevelFieldSerializers(type, allFields==null)); + } + + protected void collectSerializersForConfig(Set result, Class type) { + result.addAll(findConfigMapConstructorSerializersIgnoringInheritance(type)); + } + + protected void collectSerializersLowLevel(Set result, Class type) { result.addAll(findConvertFromPrimitiveSerializers(type)); result.addAll(findRenameKeySerializers(type)); result.addAll(findSingletonMapSerializers(type)); result.addAll(findDefaultMapValuesSerializers(type)); - - result.addAll(findConfigMapConstructorSerializers(type)); - - YomlAllFieldsTopLevel allFields = type.getAnnotation(YomlAllFieldsTopLevel.class); - result.addAll(findTopLevelFieldSerializers(type, allFields==null)); - - // subclasses can extend } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java index 9b1476ed90..8ed5bd5924 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstruction.java @@ -26,7 +26,9 @@ *

* This is used when we need information from an outer instance definition in order to construct the object. * (The default pathway is to use a no-arg constructor and to apply the outer definitions to the instance. - * But that doesn't necessarily work if a specific constructor or static method is being expected.) + * But that doesn't necessarily work if a specific constructor or static method is being expected. + * It gets more complicated if the outer instance is overwriting information from an inner instance, + * but it is the inner instance which actually defines the java type to instantiate ... and that isn't so uncommon!) */ public interface ConstructionInstruction { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstructions.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstructions.java index cc3a7d3e60..b25668f8e8 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstructions.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/ConstructionInstructions.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.util.yoml.internal; import java.lang.reflect.Modifier; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -29,71 +30,154 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.google.common.base.Preconditions; + +/** Utilities for working with {@link ConstructionInstruction} */ public class ConstructionInstructions { + private static final Logger log = LoggerFactory.getLogger(ConstructionInstructions.class); + public static class Factory { /** Returns a new construction instruction which creates an instance of the given type, * preferring the optional instruction but enforcing the given type constraint, - * and if there is no outer instruction it creates from a no-arg constructor */ + * instructing to create using a no-arg constructor if this is the outermost instruction, + * and if an inner instruction not putting any constraints on the arguments. + * see {@link #newUsingConstructorWithArgs(Class, List, ConstructionInstruction)}. */ public static ConstructionInstruction newDefault(Class type, ConstructionInstruction optionalOuter) { if (optionalOuter!=null) { - if (optionalOuter instanceof ConstructorWithArgsInstruction) + if (optionalOuter instanceof WrappingConstructionInstruction) { return newUsingConstructorWithArgs(type, null, optionalOuter); + } else { + log.warn("Ignoring nested construction instruction which is not a wrapping instruction: "+optionalOuter+" for "+type); + } } return newUsingConstructorWithArgs(type, null, null); } /** Returns a new construction instruction which creates an instance of the given type with the given args, - * preferring the optional instruction but enforcing the given type constraint and passing the given args for it to inherit, - * and if there is no outer instruction it creates from a constructor taking the given args - * (or no-arg if null, making it the same as {@link #newDefault(Class, ConstructionInstruction)}) */ - public static ConstructionInstruction newUsingConstructorWithArgs(Class type, @Nullable List args, ConstructionInstruction optionalOuter) { - return new ConstructorWithArgsInstruction(type, args, (ConstructorWithArgsInstruction)optionalOuter); - } - - /** Merge arg lists, as follows: - * if either list is null, take the other; - * otherwise require them to be the same length. - *

- * For each entry, if they are maps, merge deep preferring the latter's values when they are not maps. - * If they are anything else, we prefer the latter. - */ - public static List mergeArgumentLists(List olderArgs, List args) { + * preferring the optional instruction but enforcing the given type constraint and exposing the given args for it to inherit, + * and if there is no outer instruction it creates from a constructor taking the given args + * and merging with any inner instruction's args using the given strategy + * (if args are null here, the merge strategies should prefer the inner args, + * and if all args everywhere are null, using the no-arg constuctor, as per {@link #newDefault(Class, ConstructionInstruction)}) */ + public static ConstructionInstruction newUsingConstructorWithArgsAndArgsListMergeStrategy( + Class type, @Nullable List args, ConstructionInstruction optionalOuter, ObjectMergeStrategy argsListMergeStrategy) { + return new BasicConstructionWithArgsInstruction(type, args, (BasicConstructionWithArgsInstruction)optionalOuter, + argsListMergeStrategy); + } + + /** As {@link #newUsingConstructorWithArgsAndArgsListMergeStrategy(Class, List, ConstructionInstruction, ObjectMergeStrategy)} + * but using the default argument list strategy {@link ArgsListMergeStrategy} + * (which asserts arguments lists are the same size and fails if they are different, ignoring any null lists) + * configured to merge individual arguments according to the given provided strategy */ + public static ConstructionInstruction newUsingConstructorWithArgsAndArgumentMergeStrategy( + Class type, @Nullable List args, ConstructionInstruction optionalOuter, ObjectMergeStrategy argumentMergeStrategy) { + return new BasicConstructionWithArgsInstruction(type, args, (WrappingConstructionInstruction)optionalOuter, + new ArgsListMergeStrategy(argumentMergeStrategy)); + } + + /** As {@link #newUsingConstructorWithArgsAndArgumentMergeStrategy(Class, List, ConstructionInstruction, ObjectMergeStrategy)} + * but using {@link MapMergeStrategy} to merge arguments, ie: + * argument lists are required to be the same length or at least one null; + * where an argument in the list is a map they are deeply merged, and otherwise the othermost is preferred */ + public static ConstructionInstruction newUsingConstructorWithArgs( + Class type, @Nullable List args, ConstructionInstruction optionalOuter) { + return newUsingConstructorWithArgsAndArgumentMergeStrategy(type, args, optionalOuter, + MapMergeStrategy.DEEP_MERGE); + } + + } + + public interface ObjectMergeStrategy { + public Object merge(Object obj1, Object obj2); + } + + /** Merge arg lists, as follows: + * if either list is null, take the other; + * otherwise require them to be the same length. + *

+ * If any arg is not a list, this throws. The return type is guaranteed to be null, + * or a list the same size as the (at least one) non-null list argument. + *

+ * For each entry, it applies the given deep merge strategy. + */ + public static class ArgsListMergeStrategy implements ObjectMergeStrategy { + final ObjectMergeStrategy strategyForEachArgument; + + public ArgsListMergeStrategy(ObjectMergeStrategy strategyForEachArgument) { + this.strategyForEachArgument = Preconditions.checkNotNull(strategyForEachArgument); + } + + public Object merge(Object olderArgs, Object args) { + if (!(olderArgs==null || olderArgs instanceof List) || !(args==null || args instanceof List)) + throw new IllegalArgumentException("Only merges lists, not "+olderArgs+" and "+args); + return mergeLists((List)olderArgs, (List)args); + } + + public List mergeLists(List olderArgs, List args) { + if (olderArgs==null) return args; + if (args==null) return olderArgs; + List newArgs; - if (olderArgs==null || olderArgs.isEmpty()) newArgs = MutableList.copyOf(args); - else if (args==null) newArgs = MutableList.copyOf(olderArgs); - else { - if (olderArgs.size() != args.size()) - throw new IllegalStateException("Incompatible arguments, sizes "+olderArgs.size()+" and "+args.size()+": "+olderArgs+" and "+args); - // merge - newArgs = MutableList.of(); - Iterator i1 = olderArgs.iterator(); - Iterator i2 = args.iterator(); - while (i2.hasNext()) { - Object o1 = i1.next(); - Object o2 = i2.next(); - if ((o2 instanceof Map) && (o1 instanceof Map)) { - o2 = mergeMapsDeep((Map)o1, (Map)o2); - } - newArgs.add(o2); - } + if (olderArgs.size() != args.size()) + throw new IllegalStateException("Incompatible arguments, sizes "+olderArgs.size()+" and "+args.size()+": "+olderArgs+" and "+args); + // merge + newArgs = MutableList.of(); + Iterator i1 = olderArgs.iterator(); + Iterator i2 = args.iterator(); + while (i2.hasNext()) { + Object o1 = i1.next(); + Object o2 = i2.next(); + newArgs.add(strategyForEachArgument.merge(o1, o2)); } - return newArgs; } + } + + public static class AlwaysPreferLatter implements ObjectMergeStrategy { + @Override + public Object merge(Object obj1, Object obj2) { + return obj2; + } + } + + public static class MapMergeStrategy implements ObjectMergeStrategy { - public static Map mergeMapsDeep(Map o1, Map o2) { + public static ObjectMergeStrategy DEEP_MERGE = new MapMergeStrategy(); + static { ((MapMergeStrategy)DEEP_MERGE).setStrategyForMapEntries(DEEP_MERGE); } + + public MapMergeStrategy() {} + + ObjectMergeStrategy strategyForMapEntries = new AlwaysPreferLatter(); + + public void setStrategyForMapEntries(ObjectMergeStrategy strategyForMapEntries) { + this.strategyForMapEntries = strategyForMapEntries; + } + + @Override + public Object merge(Object o1, Object o2) { + if (applies(o1, o2)) { + return mergeMapsDeep((Map)o1, (Map)o2); + } + // prefer o2 even if null, as a way of overwriting a map in its entirety + return o2; + } + + public boolean applies(Object o1, Object o2) { + return (o1 instanceof Map) && (o2 instanceof Map); + } + + public Map mergeMapsDeep(Map o1, Map o2) { MutableMap result = MutableMap.copyOf(o1); if (o2!=null) { for (Map.Entry e2: o2.entrySet()) { Object v2 = e2.getValue(); - if (v2 instanceof Map) { - Object old = result.get(e2.getKey()); - if (old instanceof Map) { - v2 = mergeMapsDeep((Map)old, (Map)v2); - } + if (result.containsKey(e2.getKey())) { + v2 = strategyForMapEntries.merge(result.get(e2.getKey()), v2); } result.put(e2.getKey(), v2); } @@ -102,12 +186,20 @@ public static Map mergeMapsDeep(Map o1, Map o2) { } } - public static class ConstructorWithArgsInstruction implements ConstructionInstruction { + /** interface for a construction instruction which may be invoked by one it wraps */ + public interface WrappingConstructionInstruction extends ConstructionInstruction { + /** constructors list has next-outermost first */ + public Maybe create(Class typeConstraintSoFar, @Nullable List innerConstructors); + } + + /** see {@link Factory#newUsingConstructorWithArgsAndArgsListMergeStrategy(Class, List, ConstructionInstruction, ObjectMergeStrategy)} + * and {@link Factory#newUsingConstructorWithArgsAndArgumentMergeStrategy(Class, List, ConstructionInstruction, ObjectMergeStrategy)} */ + public static abstract class AbstractConstructionWithArgsInstruction implements WrappingConstructionInstruction { private final Class type; private final List args; - private final ConstructorWithArgsInstruction outerInstruction; + private final WrappingConstructionInstruction outerInstruction; - protected ConstructorWithArgsInstruction(Class type, List args, @Nullable ConstructorWithArgsInstruction outerInstruction) { + protected AbstractConstructionWithArgsInstruction(Class type, List args, @Nullable WrappingConstructionInstruction outerInstruction) { this.type = type; this.args = args; this.outerInstruction = outerInstruction; @@ -122,7 +214,8 @@ public Maybe create() { return create(null, null); } - protected Maybe create(Class typeConstraintSoFar, List argsSoFar) { + @Override + public Maybe create(Class typeConstraintSoFar, List constructorsSoFarOutermostFirst) { if (typeConstraintSoFar==null) typeConstraintSoFar = type; if (type!=null) { if (typeConstraintSoFar==null || typeConstraintSoFar.isAssignableFrom(type)) typeConstraintSoFar = type; @@ -132,17 +225,51 @@ else if (type.isAssignableFrom(typeConstraintSoFar)) { /* fine */ } } } - List newArgs = Factory.mergeArgumentLists(argsSoFar, args); - if (outerInstruction!=null) { - return outerInstruction.create(typeConstraintSoFar, newArgs); + if (constructorsSoFarOutermostFirst==null) constructorsSoFarOutermostFirst = MutableList.of(); + else constructorsSoFarOutermostFirst = MutableList.copyOf(constructorsSoFarOutermostFirst); + + constructorsSoFarOutermostFirst.add(0, this); + return outerInstruction.create(typeConstraintSoFar, constructorsSoFarOutermostFirst); } + return createWhenNoOuter(typeConstraintSoFar, constructorsSoFarOutermostFirst); + } + + protected Maybe createWhenNoOuter(Class typeConstraintSoFar, List constructorsSoFar) { + List combinedArgs = combineArguments(constructorsSoFar); + if (typeConstraintSoFar==null) throw new IllegalStateException("No type information available"); if ((typeConstraintSoFar.getModifiers() & (Modifier.ABSTRACT | Modifier.INTERFACE)) != 0) throw new IllegalStateException("Insufficient type information: expected "+type+" is not directly instantiable"); - return Reflections.invokeConstructorFromArgsIncludingPrivate(typeConstraintSoFar, newArgs.toArray()); + return Reflections.invokeConstructorFromArgsIncludingPrivate(typeConstraintSoFar, combinedArgs==null ? new Object[0] : combinedArgs.toArray()); } + + protected abstract List combineArguments(@Nullable List constructorsSoFar); } + + /** see {@link Factory#newDefault(Class, ConstructionInstruction)} and other methods in that factory class */ + protected static class BasicConstructionWithArgsInstruction extends AbstractConstructionWithArgsInstruction { + private ObjectMergeStrategy argsListMergeStrategy; + + protected BasicConstructionWithArgsInstruction(Class type, List args, @Nullable WrappingConstructionInstruction outerInstruction, + ObjectMergeStrategy argsListMergeStrategy) { + super(type, args, outerInstruction); + this.argsListMergeStrategy = argsListMergeStrategy; + } + + protected List combineArguments(@Nullable List constructorsSoFarOutermostFirst) { + List combinedArgs = null; + List constructorsInnermostFirst = MutableList.copyOf(constructorsSoFarOutermostFirst); + Collections.reverse(constructorsInnermostFirst); + + for (ConstructionInstruction i: constructorsInnermostFirst) { + combinedArgs = (List) argsListMergeStrategy.merge(combinedArgs, i.getArgs()); + } + combinedArgs = (List) argsListMergeStrategy.merge(combinedArgs, getArgs()); + return combinedArgs; + } + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java index 2e785a8d8e..91e978b504 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java @@ -20,6 +20,7 @@ import java.util.Map; +import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.yoml.internal.YomlContext; @@ -104,7 +105,8 @@ protected Map writePrepareGeneralMap() { protected String getType(String key, Object value) { TopLevelFieldsBlackboard efb = TopLevelFieldsBlackboard.get(blackboard, getKeyNameForMapOfGeneralValues()); - TypeToken type = efb.getDeclaredType(key); + ConfigKey typeKey = efb.getConfigKey(key); + TypeToken type = typeKey==null ? null : typeKey.getTypeToken(); String optionalType = null; if (type!=null && (value==null || type.getRawType().isInstance(value))) optionalType = YomlUtils.getTypeNameWithGenerics(type, config.getTypeRegistry()); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java index 6d983b48c6..c30315c46c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Set; +import org.apache.brooklyn.config.ConfigInheritance; +import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; @@ -54,8 +56,14 @@ public class InstantiateTypeFromRegistryUsingConfigMap extends InstantiateTypeFr // (for now this field can be used to load explicit config keys, if the field name is supplied) boolean inferByScanning = false; + public static Factory newFactoryIgnoringInheritance() { + return new Factory(); + } + public static class Factory { + protected Factory() {} + /** creates a set of serializers handling config for any type, with the given field/key combination; * the given field will be checked at serialization time to determine whether this is applicable */ public Set newConfigKeyClassScanningSerializers( @@ -152,6 +160,11 @@ protected void addExtraTypeSerializers(Class clazz) { } } + protected TopLevelFieldsBlackboard getTopLevelFieldsBlackboard() { + // keys recorded here by the individual serializers + return TopLevelFieldsBlackboard.get(blackboard, keyNameForConfigWhenSerialized); + } + protected void readFinallyCreate() { if (hasJavaObject()) return; @@ -164,7 +177,7 @@ protected void readFinallyCreate() { Preconditions.checkNotNull(keyNameForConfigWhenSerialized); YomlConfig newConfig = YomlConfig.Builder.builder(config).constructionInstruction( - newConstructor(type, fib.fieldsFromReadToConstructJava, config.getConstructionInstruction())).build(); + newConstructor(type, getTopLevelFieldsBlackboard().getConfigKeys(), fib.fieldsFromReadToConstructJava, config.getConstructionInstruction())).build(); Maybe resultM = config.getTypeRegistry().newInstanceMaybe(fib.typeNameFromReadToConstructJavaLater, Yoml.newInstance(newConfig)); @@ -261,11 +274,24 @@ protected Maybe findFieldMaybe(Class type) { } protected Maybe findConstructorMaybe(Class type) { - return Reflections.findConstructorMaybe(type, Map.class); + return Reflections.findConstructorExactMaybe(type, Map.class); } - protected ConstructionInstruction newConstructor(Class type, Map fieldsFromReadToConstructJava, ConstructionInstruction optionalOuter) { - return ConstructionInstructions.Factory.newUsingConstructorWithArgs(type, MutableList.of(fieldsFromReadToConstructJava), optionalOuter); + /** + * creates an instruction for working with a single-argument constructor which takes a simple map of + * config values. + *

+ * this ignores inheritance since within this project specific ConfigInheritanceContext + * and {@link ConfigInheritance} strategies are not available. + *

+ * callers will have already invoked {@link #findConstructorMaybe(Class)} so implementations can + * assume the constructor exists. subclassers should ensure that {@link #findConstructorMaybe(Class)} is + * also updated if required. + */ + protected ConstructionInstruction newConstructor(Class type, Map> keysByAlias, + Map fieldsFromReadToConstructJava, ConstructionInstruction optionalOuter) { + return ConstructionInstructions.Factory.newUsingConstructorWithArgs(type, + MutableList.of(fieldsFromReadToConstructJava), optionalOuter); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java index 98ac5db54e..90c8b20b97 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java @@ -125,7 +125,7 @@ protected boolean canDoRead() { @Override protected void prepareTopLevelFields() { super.prepareTopLevelFields(); - getTopLevelFieldsBlackboard().setDeclaredTypeIfUnset(fieldName, configKey.getTypeToken()); + getTopLevelFieldsBlackboard().recordConfigKey(fieldName, configKey); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java index 2498f01e74..db7a063587 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Set; +import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; @@ -34,8 +35,6 @@ import org.apache.brooklyn.util.yoml.internal.YomlContext; import org.apache.brooklyn.util.yoml.serializers.TopLevelFieldSerializer.FieldConstraint; -import com.google.common.reflect.TypeToken; - public class TopLevelFieldsBlackboard implements YomlRequirement { public static final String KEY = TopLevelFieldsBlackboard.class.getCanonicalName(); @@ -57,7 +56,7 @@ public static TopLevelFieldsBlackboard get(Map blackboard, String private final Map fieldsConstraints = MutableMap.of(); private final Map defaultValueForFieldComesFromSerializer = MutableMap.of(); private final Map defaultValueOfField = MutableMap.of(); - private final Map> declaredTypeOfFieldsAndAliases = MutableMap.of(); + private final Map> keyForFieldsAndAliases = MutableMap.of(); public String getKeyName(String fieldName) { return Maybe.ofDisallowingNull(keyNames.get(fieldName)).orNull(); @@ -143,19 +142,23 @@ public Maybe getDefault(String fieldName) { return Maybe.of(defaultValueOfField.get(fieldName)); } - /** optional, and must be called after aliases; not used for fields, is used for config keys */ - public void setDeclaredTypeIfUnset(String fieldName, TypeToken type) { - setDeclaredTypeOfIndividualNameOrAliasIfUnset(fieldName, type); + /** optional, and must be called after aliases; records the config key for subsequent retrieval */ + public void recordConfigKey(String fieldName, ConfigKey key) { + setKeyForIndividualNameOrAliasIfUnset(fieldName, key); for (String alias: getAliases(fieldName)) - setDeclaredTypeOfIndividualNameOrAliasIfUnset(alias, type); + setKeyForIndividualNameOrAliasIfUnset(alias, key); + } + protected void setKeyForIndividualNameOrAliasIfUnset(String fieldName, ConfigKey type) { + if (keyForFieldsAndAliases.get(fieldName)!=null) return; + keyForFieldsAndAliases.put(fieldName, type); } - protected void setDeclaredTypeOfIndividualNameOrAliasIfUnset(String fieldName, TypeToken type) { - if (declaredTypeOfFieldsAndAliases.get(fieldName)!=null) return; - declaredTypeOfFieldsAndAliases.put(fieldName, type); + /** only if {@link #recordConfigKey(String, ConfigKey)} has been used */ + public ConfigKey getConfigKey(String fieldNameOrAlias) { + return keyForFieldsAndAliases.get(fieldNameOrAlias); } - /** only if {@link #setDeclaredTypeIfUnset(String, TypeToken)} is being used (eg config keys) */ - public TypeToken getDeclaredType(String key) { - return declaredTypeOfFieldsAndAliases.get(key); + /** only if {@link #recordConfigKey(String, ConfigKey)} has been used */ + public Map> getConfigKeys() { + return MutableMap.copyOf(keyForFieldsAndAliases); } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java index 43951bf43e..9c9543bd3b 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java @@ -109,7 +109,7 @@ public String toString() { @Test public void testRead() { YomlTestFixture y = YomlTestFixture.newInstance() - .addTypeWithAnnotationsAndConfig("s1", S1.class, MutableMap.of("keys", "config")); + .addTypeWithAnnotationsAndConfigFieldsIgnoringInheritance("s1", S1.class, MutableMap.of("keys", "config")); y.read("{ type: s1, k1: foo }", null); @@ -120,17 +120,17 @@ public void testRead() { @Test public void testWrite() { YomlTestFixture y = YomlTestFixture.newInstance() - .addTypeWithAnnotationsAndConfig("s1", S1.class, MutableMap.of("keys", "config")); + .addTypeWithAnnotationsAndConfigFieldsIgnoringInheritance("s1", S1.class, MutableMap.of("keys", "config")); S1 s1 = new S1(MutableMap.of("k1", "foo")); y.write(s1); - YomlTestFixture.assertEqualsIgnoringQuotes(Jsonya.newInstance().add(y.lastWriteResult).toString(), "{ type: s1, k1: foo }", "wrong serialization"); + YomlTestFixture.assertEqualish(Jsonya.newInstance().add(y.lastWriteResult).toString(), "{ type: s1, k1: foo }", "wrong serialization"); } @Test public void testReadWrite() { YomlTestFixture.newInstance() - .addTypeWithAnnotationsAndConfig("s1", S1.class, MutableMap.of("keys", "config")) + .addTypeWithAnnotationsAndConfigFieldsIgnoringInheritance("s1", S1.class, MutableMap.of("keys", "config")) .reading("{ type: s1, k1: foo }").writing(new S1(MutableMap.of("k1", "foo"))) .doReadWriteAssertingJsonMatch(); } @@ -144,7 +144,7 @@ static class S2 extends S1 { @Test public void testReadWriteInherited() { YomlTestFixture.newInstance() - .addTypeWithAnnotationsAndConfig("s2", S2.class, MutableMap.of("keys", "config")) + .addTypeWithAnnotationsAndConfigFieldsIgnoringInheritance("s2", S2.class, MutableMap.of("keys", "config")) .reading("{ type: s2, k1: foo, k2: bar }").writing(new S2(MutableMap.of("k1", "foo", "k2", "bar"))) .doReadWriteAssertingJsonMatch(); } @@ -152,15 +152,15 @@ public void testReadWriteInherited() { @Test public void testReadWriteNested() { YomlTestFixture.newInstance() - .addTypeWithAnnotationsAndConfig("s1", S1.class, MutableMap.of("keys", "config")) - .addTypeWithAnnotationsAndConfig("s2", S2.class, MutableMap.of("keys", "config")) + .addTypeWithAnnotationsAndConfigFieldsIgnoringInheritance("s1", S1.class, MutableMap.of("keys", "config")) + .addTypeWithAnnotationsAndConfigFieldsIgnoringInheritance("s2", S2.class, MutableMap.of("keys", "config")) .reading("{ type: s2, ks: { k1: foo } }").writing(new S2(MutableMap.of("ks", new S1(MutableMap.of("k1", "foo"))))) .doReadWriteAssertingJsonMatch(); } @Test public void testReadWriteNestedGlobalConfigKeySupport() { YomlTestFixture y = YomlTestFixture.newInstance(YomlConfig.Builder.builder() - .serializersPostAdd(new InstantiateTypeFromRegistryUsingConfigMap.Factory().newConfigKeyClassScanningSerializers("keys", "config", true)) + .serializersPostAdd(InstantiateTypeFromRegistryUsingConfigMap.newFactoryIgnoringInheritance().newConfigKeyClassScanningSerializers("keys", "config", true)) .serializersPostAddDefaults().build()); y.addTypeWithAnnotations("s1", S1.class) .addTypeWithAnnotations("s2", S2.class) diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyGenericsTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyGenericsTests.java index 79d0cf170f..0f66d8840e 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyGenericsTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyGenericsTests.java @@ -78,7 +78,7 @@ public void testWriteRaw() { y.write(RAW_IN); log.info("M1B written as: "+y.lastWriteResult); - YomlTestFixture.assertEqualsIgnoringQuotes(Jsonya.newInstance().add(y.lastWriteResult).toString(), + YomlTestFixture.assertEqualish(Jsonya.newInstance().add(y.lastWriteResult).toString(), RAW_OUT, "wrong serialization"); } @@ -103,7 +103,7 @@ public void testWriteGen() { y.write(GEN_IN); log.info("M1B written as: "+y.lastWriteResult); - YomlTestFixture.assertEqualsIgnoringQuotes(Jsonya.newInstance().add(y.lastWriteResult).toString(), + YomlTestFixture.assertEqualish(Jsonya.newInstance().add(y.lastWriteResult).toString(), GEN_OUT, "wrong serialization"); } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyInheritanceTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyInheritanceTests.java index 19ed20f6b8..24f056a598 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyInheritanceTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyInheritanceTests.java @@ -76,15 +76,17 @@ public void testWrite() { // (this would be nice to change, but a lot of work and not really worth it) YomlTestFixture y = YomlTestFixture.newInstance() - .addTypeWithAnnotationsAndConfig("m1", M1.class, MutableMap.of("keys", "config")); + .addTypeWithAnnotationsAndConfigFieldsIgnoringInheritance("m1", M1.class, MutableMap.of("keys", "config")); M1 m1b = new M1(MutableMap.of("km", MutableMap.of("a", 1, "b", 2), "ko", MutableMap.of("a", 1, "b", 2))); y.write(m1b); log.info("written as "+y.lastWriteResult); - YomlTestFixture.assertEqualsIgnoringQuotes(Jsonya.newInstance().add(y.lastWriteResult).toString(), + YomlTestFixture.assertEqualish(Jsonya.newInstance().add(y.lastWriteResult).toString(), "{ type=m1, km:{ a: 1, b: 2}, ko: { a: 1, b: 2 } }", "wrong serialization"); - YomlTestFixture.assertEqualsIgnoringQuotes(y.lastWriteResult.toString(), + YomlTestFixture.assertEqualish(y.lastWriteResult.toString(), "{ type=m1, km:{ a: 1, b: 2}, ko: { a: 1, b: 2 } }", "wrong serialization"); } + // TODO same for bag + } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java index 40507494f6..5d814b2d36 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java @@ -92,9 +92,9 @@ public YomlTestFixture read(String objectToRead, String expectedType) { public YomlTestFixture assertResult(Object expectation) { if (expectation instanceof String) { if (lastResult instanceof Map || lastResult instanceof Collection) { - assertEqualsIgnoringQuotes(Jsonya.newInstance().add(lastResult).toString(), expectation, "Result as JSON string does not match expectation"); + assertEqualish(Jsonya.newInstance().add(lastResult).toString(), expectation, "Result as JSON string does not match expectation"); } else { - assertEqualsIgnoringQuotes(Strings.toString(lastResult), expectation, "Result toString does not match expectation"); + assertEqualish(Strings.toString(lastResult), expectation, "Result toString does not match expectation"); } } else { Assert.assertEquals(lastResult, expectation); @@ -104,8 +104,8 @@ public YomlTestFixture assertResult(Object expectation) { public YomlTestFixture doReadWriteAssertingJsonMatch() { read(readObject, readObjectExpectedType); write(writeObject, writeObjectExpectedType); - assertEqualsIgnoringQuotes(Jsonya.newInstance().add(lastWriteResult).toString(), readObject, "Write output should match read input"); - assertEqualsIgnoringQuotes(lastReadResult, writeObject, "Read output should match write input"); + assertEqualish(Jsonya.newInstance().add(lastWriteResult).toString(), readObject, "Write output should match read input"); + assertEqualish(lastReadResult, writeObject, "Read output should match write input"); return this; } @@ -118,12 +118,16 @@ private static String removeGuff(String input) { ); } - static void assertEqualsIgnoringQuotes(Object s1, Object s2, String message) { + static void assertEqualish(Object s1, Object s2, String message) { if (s1 instanceof String) s1 = removeGuff((String)s1); if (s2 instanceof String) s2 = removeGuff((String)s2); Assert.assertEquals(s1, s2, message); } + public void assertLastWriteIgnoringQuotes(String expected, String message) { + assertEqualish(Jsonya.newInstance().add(getLastWriteResult()).toString(), expected, message); + } + public YomlTestFixture addType(String name, Class type) { tr.put(name, type); return this; } public YomlTestFixture addType(String name, Class type, List serializers) { tr.put(name, type, serializers); return this; } public YomlTestFixture addType(String name, String yamlDefinition) { tr.put(name, yamlDefinition); return this; } @@ -139,11 +143,11 @@ public YomlTestFixture addTypeWithAnnotations(String optionalName, Class type } return this; } - public YomlTestFixture addTypeWithAnnotationsAndConfig(String optionalName, Class type, + public YomlTestFixture addTypeWithAnnotationsAndConfigFieldsIgnoringInheritance(String optionalName, Class type, Map configFieldsToKeys) { Set serializers = annotationsProvider().findSerializerAnnotations(type, true); for (Map.Entry entry: configFieldsToKeys.entrySet()) { - serializers.addAll( new InstantiateTypeFromRegistryUsingConfigMap.Factory().newConfigKeyClassScanningSerializers( + serializers.addAll( InstantiateTypeFromRegistryUsingConfigMap.newFactoryIgnoringInheritance().newConfigKeyClassScanningSerializers( entry.getKey(), entry.getValue(), true) ); } for (String n: new YomlAnnotations().findTypeNamesFromAnnotations(type, optionalName, false)) { @@ -154,5 +158,11 @@ public YomlTestFixture addTypeWithAnnotationsAndConfig(String optionalName, Clas protected YomlAnnotations annotationsProvider() { return new YomlAnnotations(); } - + + public Object getLastReadResult() { + return lastReadResult; + } + public Object getLastWriteResult() { + return lastWriteResult; + } } From 4f92b501d5cc11348ac61daafcaf08d9d74be649 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 27 Sep 2016 13:05:38 +0100 Subject: [PATCH 51/77] config map/bag can be passed to constructor even if not stored as field --- .../camp/yoml/BrooklynYomlAnnotations.java | 2 +- .../camp/yoml/BrooklynYomlTypeRegistry.java | 4 +- ...antiateTypeFromRegistryUsingConfigBag.java | 2 +- .../yoml/YomlConfigKeyInheritanceTests.java | 1 - ...omlTypeRegistryEntityInitializersTest.java | 37 ++++-- .../brooklyn/core/effector/AddSensor.java | 4 + .../brooklyn/core/sensor/StaticSensor.java | 1 + .../core/yoml/YomlConfigBagConstructor.java | 10 +- .../brooklyn/util/yoml/YomlException.java | 10 +- .../brooklyn/util/yoml/annotations/Alias.java | 2 + .../yoml/annotations/DefaultKeyValue.java | 2 + .../annotations/YomlAllFieldsTopLevel.java | 2 + .../yoml/annotations/YomlAnnotations.java | 4 +- .../annotations/YomlConfigMapConstructor.java | 8 +- .../annotations/YomlDefaultMapValues.java | 2 + .../yoml/annotations/YomlFromPrimitive.java | 2 + .../util/yoml/annotations/YomlRenameKey.java | 2 + .../yoml/annotations/YomlSingletonMap.java | 2 + .../serializers/FieldsInMapUnderFields.java | 6 +- ...antiateTypeFromRegistryUsingConfigMap.java | 43 ++++--- .../TopLevelConfigKeySerializer.java | 7 +- .../serializers/TopLevelFieldSerializer.java | 10 +- .../yoml/tests/TopLevelConfigKeysTests.java | 106 +++++++++++++++++- .../tests/YomlConfigKeyGenericsTests.java | 1 - .../tests/YomlConfigKeyInheritanceTests.java | 92 --------------- .../util/yoml/tests/YomlTestFixture.java | 7 +- 26 files changed, 222 insertions(+), 147 deletions(-) delete mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyInheritanceTests.java diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java index fc93c0eeaa..c025c5e48c 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlAnnotations.java @@ -41,7 +41,7 @@ public Collection findConfigBagConstructorSerializers(Class t ann.validateAheadOfTime(), ann.requireStaticKeys()); } - public Collection findConfigMapConstructorSerializersIgnoringInheritance(Class t) { + public Collection findConfigMapConstructorSerializersIgnoringConfigInheritance(Class t) { throw new UnsupportedOperationException("ensure this doesn't get called"); } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java index b8a591a106..f7a65c10b3 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java @@ -386,7 +386,7 @@ protected void collectSerializers(Object type, Collection result supers.addAll(((RegisteredType) type).getSuperTypes()); } } else if (type instanceof Class) { - result.addAll(new BrooklynYomlAnnotations().findSerializerAnnotations((Class)type, true)); + result.addAll(new BrooklynYomlAnnotations().findSerializerAnnotations((Class)type, false)); // // could look up the type? but we should be calling this normally with the RT if we have one so probably not necessary // // and could recurse through superclasses and interfaces -- but the above is a better place to do that if needed // String name = getTypeNameOfClass((Class)type); @@ -427,7 +427,7 @@ public static RegisteredType newYomlRegisteredType(RegisteredTypeKind kind, public static RegisteredType newYomlRegisteredType(RegisteredTypeKind kind, @Nullable String symbolicName, String version, Class clazz) { Set names = new BrooklynYomlAnnotations().findTypeNamesFromAnnotations(clazz, symbolicName, false); - Set serializers = new BrooklynYomlAnnotations().findSerializerAnnotations(clazz, true); + Set serializers = new BrooklynYomlAnnotations().findSerializerAnnotations(clazz, false); RegisteredType type = BrooklynYomlTypeRegistry.newYomlRegisteredType(kind, // symbolicName, version, diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java index 6b0116eae3..36ff98c3ec 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/InstantiateTypeFromRegistryUsingConfigBag.java @@ -51,7 +51,7 @@ protected Maybe findConfigBagConstructor(Class type) { } protected Maybe findFieldMaybe(Class type) { - Maybe f = Reflections.findFieldMaybe(type, fieldNameForConfigInJavaIfPreset); + Maybe f = Reflections.findFieldMaybe(type, fieldNameForConfigInJava); if (f.isPresent() && !(Map.class.isAssignableFrom(f.get().getType()) || ConfigBag.class.isAssignableFrom(f.get().getType()))) f = Maybe.absent(); return f; diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlConfigKeyInheritanceTests.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlConfigKeyInheritanceTests.java index b74fe11789..c49b166185 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlConfigKeyInheritanceTests.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlConfigKeyInheritanceTests.java @@ -58,7 +58,6 @@ public String toString() { } } - @YomlConfigMapConstructor("conf") static class M1 extends M0 { @SuppressWarnings("serial") static ConfigKey> KM = ConfigKeys.builder(new TypeToken>() {}, "km") diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java index c040efe290..44d337a5fa 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java @@ -26,9 +26,13 @@ import org.apache.brooklyn.camp.yoml.types.YomlInitializers; import org.apache.brooklyn.core.sensor.DependentConfiguration; import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.core.sensor.StaticSensor; import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.yaml.Yamls; import org.testng.Assert; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.common.base.Joiner; @@ -36,6 +40,15 @@ public class YomlTypeRegistryEntityInitializersTest extends AbstractYamlTest { + @BeforeMethod(alwaysRun = true) + @Override + public void setUp() throws Exception { + super.setUp(); + + // TODO logically how should we populate the catalog? see notes on the method called below + YomlInitializers.install(mgmt()); + } + @Test(enabled=false) // format still runs old camp parse, does not attempt yaml public void testStaticSensorBasic() throws Exception { String yaml = Joiner.on("\n").join( @@ -46,9 +59,20 @@ public void testStaticSensorBasic() throws Exception { " type: static-sensor", " value: 42"); - checkStaticSensor(yaml); + checkStaticSensorInApp(yaml); } + @Test + public void testReadSensor() throws Exception { + String yaml = Joiner.on("\n").join( + "name: the-answer", + "type: static-sensor", + "value: { type: int, value: 42 }"); + + Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(yaml).iterator().next(), null, null); + Asserts.assertInstanceOf(ss, StaticSensor.class); + } + @Test public void testStaticSensorSingletonMap() throws Exception { String yaml = Joiner.on("\n").join( @@ -57,16 +81,13 @@ public void testStaticSensorSingletonMap() throws Exception { " brooklyn.initializers:", " the-answer:", " type: static-sensor", - " value: 42"); + " value: { type: int, value: 42 }"); - checkStaticSensor(yaml); + checkStaticSensorInApp(yaml); } - protected void checkStaticSensor(String yaml) - throws Exception, InterruptedException, ExecutionException, TimeoutException { - // TODO not finding/loading type for serializers - // TODO logically how should it learn details of static-sensor serialization? - YomlInitializers.install(mgmt()); + protected void checkStaticSensorInApp(String yaml) + throws Exception, InterruptedException, ExecutionException, TimeoutException { final Entity app = createStartWaitAndLogApplication(yaml); TestEntity entity = (TestEntity) Iterables.getOnlyElement(app.getChildren()); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java index dde4d64d90..85f8e5adce 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java @@ -30,10 +30,12 @@ import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.util.core.ClassLoaderUtils; import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.yoml.YomlConfigBagConstructor; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Boxing; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; @@ -48,6 +50,8 @@ * @since 0.7.0 */ @Beta +@YomlConfigBagConstructor("") +@YomlAllFieldsTopLevel public class AddSensor implements EntityInitializer { public static final ConfigKey SENSOR_NAME = ConfigKeys.newStringConfigKey("name", "The name of the sensor to create"); diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java index d16da3d81c..0dad809aa1 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java @@ -32,6 +32,7 @@ import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/org/apache/brooklyn/util/core/yoml/YomlConfigBagConstructor.java b/core/src/main/java/org/apache/brooklyn/util/core/yoml/YomlConfigBagConstructor.java index 632eadce64..b960c6d72d 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/yoml/YomlConfigBagConstructor.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/yoml/YomlConfigBagConstructor.java @@ -21,6 +21,7 @@ import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -30,18 +31,15 @@ * Indicates that a class should be yoml-serialized using a one-arg constructor taking a map or bag of config. * Similar to {@link YomlConfigMapConstructor} but accepting config-bag constructors * and defaulting to `brooklyn.config` as the key for unknown config. + *

+ * See {@link YomlConfigMapConstructor} for the meaning of all methods. */ @Retention(RUNTIME) @Target({ TYPE }) +@Inherited public @interface YomlConfigBagConstructor { - /** YOML needs to know which field contains the config at serialization time. */ String value(); - /** By default here reads/writes unrecognised key values against `brooklyn.config`. */ String writeAsKey() default "brooklyn.config"; - - /** Validate that a suitable field and constructor exist, failing fast if not */ boolean validateAheadOfTime() default true; - - /** Skip if there are no declared config keys (default false) */ boolean requireStaticKeys() default false; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlException.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlException.java index 8fb5058a4c..c282521b7e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlException.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlException.java @@ -36,9 +36,13 @@ public YomlContext getContext() { } @Override - public String toString() { - if (context==null) return super.toString(); - return super.toString() + " ("+context+")"; + public String getMessage() { + if (context==null) return getBaseMessage(); + return getBaseMessage() + " ("+context+")"; + } + + public String getBaseMessage() { + return super.getMessage(); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/Alias.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/Alias.java index 20d802bc2b..6532ebd355 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/Alias.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/Alias.java @@ -22,11 +22,13 @@ import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.Target; @Retention(RUNTIME) @Target({ TYPE, FIELD }) +@Inherited /** Indicates that a class or field should be known by a set of given aliases */ public @interface Alias { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/DefaultKeyValue.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/DefaultKeyValue.java index 0e90aa44b1..ee990bef84 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/DefaultKeyValue.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/DefaultKeyValue.java @@ -21,11 +21,13 @@ import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.Target; @Retention(RUNTIME) @Target({ TYPE }) +@Inherited /** Indicates that default key-value pair should be supplied, e.g. for YomlSingletonMap */ public @interface DefaultKeyValue { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAllFieldsTopLevel.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAllFieldsTopLevel.java index 1bf6468b59..86f3cf05c3 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAllFieldsTopLevel.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAllFieldsTopLevel.java @@ -21,11 +21,13 @@ import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.Target; @Retention(RUNTIME) @Target({ TYPE }) +@Inherited /** Indicates that all fields should be available at the top-level when reading yoml, * ie none require to be inside a fields block. */ public @interface YomlAllFieldsTopLevel { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java index f00c0addde..8bcf3cb5f1 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java @@ -74,7 +74,7 @@ public Collection findTopLevelFieldSerializers(Class return result; } - public Collection findConfigMapConstructorSerializersIgnoringInheritance(Class t) { + public Collection findConfigMapConstructorSerializersIgnoringConfigInheritance(Class t) { YomlConfigMapConstructor ann = t.getAnnotation(YomlConfigMapConstructor.class); if (ann==null) return Collections.emptyList(); return InstantiateTypeFromRegistryUsingConfigMap.newFactoryIgnoringInheritance().newConfigKeySerializersForType( @@ -139,7 +139,7 @@ protected void collectSerializersFields(Set result, Class typ } protected void collectSerializersForConfig(Set result, Class type) { - result.addAll(findConfigMapConstructorSerializersIgnoringInheritance(type)); + result.addAll(findConfigMapConstructorSerializersIgnoringConfigInheritance(type)); } protected void collectSerializersLowLevel(Set result, Class type) { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConfigMapConstructor.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConfigMapConstructor.java index b92118856b..0a44e9895b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConfigMapConstructor.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlConfigMapConstructor.java @@ -21,6 +21,7 @@ import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -32,8 +33,13 @@ */ @Retention(RUNTIME) @Target({ TYPE }) +@Inherited public @interface YomlConfigMapConstructor { - /** YOML needs to know which field contains the config at serialization time. */ + /** YOML needs to know which field contains the config at serialization time. + *

+ * It can be supplied as blank to mean that a map should be taken as a constructor + * but data will be written to fields. In that case the output YAML serialization will refer to + * the fields but config keys will be accepted for input. */ String value(); /** By default YOML reads/writes unrecognised key values against a key with the same name as {@link #value()}. * This can be set to use a different key in the YAML. */ diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlDefaultMapValues.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlDefaultMapValues.java index 32ea0779aa..f0f746b7aa 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlDefaultMapValues.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlDefaultMapValues.java @@ -21,6 +21,7 @@ import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -31,6 +32,7 @@ */ @Retention(RUNTIME) @Target({ TYPE }) +@Inherited public @interface YomlDefaultMapValues { DefaultKeyValue[] value(); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java index 73d5377d29..25cac5ddb4 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java @@ -21,6 +21,7 @@ import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -36,6 +37,7 @@ */ @Retention(RUNTIME) @Target({ TYPE }) +@Inherited public @interface YomlFromPrimitive { /** The key to insert for the given value */ diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java index 40da369787..598537659f 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java @@ -21,6 +21,7 @@ import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -33,6 +34,7 @@ */ @Retention(RUNTIME) @Target({ TYPE }) +@Inherited public @interface YomlRenameKey { /** The key name to change from when reading */ diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlSingletonMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlSingletonMap.java index b8ff8b2d8b..a505f860e2 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlSingletonMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlSingletonMap.java @@ -21,6 +21,7 @@ import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -40,6 +41,7 @@ */ @Retention(RUNTIME) @Target({ TYPE }) +@Inherited public @interface YomlSingletonMap { /** The single key is taken as a value against the key name given here. */ String keyForKey() default ConvertSingletonMap.DEFAULT_KEY_FOR_KEY; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java index d8a465937c..e807fa5ba8 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java @@ -99,10 +99,10 @@ public void read() { } catch (Exception e) { throw Exceptions.propagate(e); } } + if (((Map)fields).isEmpty()) { + removeFromYamlKeysOnBlackboard(getKeyNameForMapOfGeneralValues()); + } if (changed) { - if (((Map)fields).isEmpty()) { - removeFromYamlKeysOnBlackboard(getKeyNameForMapOfGeneralValues()); - } // restart (there is normally nothing after this so could equally continue with rerun) context.phaseRestart(); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java index c30315c46c..616ebcebc7 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java @@ -30,6 +30,7 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.Yoml; import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.YomlSerializer; @@ -48,7 +49,7 @@ public class InstantiateTypeFromRegistryUsingConfigMap extends InstantiateTypeFr public static final String PHASE_INSTANTIATE_TYPE_DEFERRED = "handling-type-deferred-after-config"; protected String keyNameForConfigWhenSerialized = null; - protected String fieldNameForConfigInJavaIfPreset = null; + protected String fieldNameForConfigInJava = null; boolean staticKeysRequired; // don't currently fully support inferring setup from annotations; we need the field above. @@ -90,9 +91,9 @@ protected Set findSerializers( MutableSet result = MutableSet.of(); if (fieldNameForConfigInJava==null) return result; InstantiateTypeFromRegistryUsingConfigMap instantiator = newInstance(); - instantiator.fieldNameForConfigInJavaIfPreset = fieldNameForConfigInJava; + instantiator.fieldNameForConfigInJava = fieldNameForConfigInJava; if (validateAheadOfTime) { - instantiator.findFieldMaybe(type).get(); + Preconditions.checkArgument(instantiator.isValidConfigFieldOrBlankSoWeCanRead(type), "Missing config field "+fieldNameForConfigInJava+" in "+type); instantiator.findConstructorMaybe(type).get(); } instantiator.keyNameForConfigWhenSerialized = keyNameForConfigWhenSerialized; @@ -177,7 +178,8 @@ protected void readFinallyCreate() { Preconditions.checkNotNull(keyNameForConfigWhenSerialized); YomlConfig newConfig = YomlConfig.Builder.builder(config).constructionInstruction( - newConstructor(type, getTopLevelFieldsBlackboard().getConfigKeys(), fib.fieldsFromReadToConstructJava, config.getConstructionInstruction())).build(); + newConstructor(type, getTopLevelFieldsBlackboard().getConfigKeys(), MutableMap.copyOf(fib.fieldsFromReadToConstructJava), + config.getConstructionInstruction())).build(); Maybe resultM = config.getTypeRegistry().newInstanceMaybe(fib.typeNameFromReadToConstructJavaLater, Yoml.newInstance(newConfig)); @@ -194,26 +196,17 @@ protected void readFinallyCreate() { protected boolean canDoWrite() { if (!super.canDoWrite()) return false; if (!isConfigurable(getJavaObject().getClass())) return false; - if (!hasValidConfigFieldSoWeCanWriteConfigMap()) return false; + if (!isValidConfigFieldSoWeCanWrite()) return false; return true; } - - protected boolean hasValidConfigFieldSoWeCanWriteConfigMap() { - if (fieldNameForConfigInJavaIfPreset!=null) { - return findFieldMaybe(getJavaObject().getClass()).isPresent(); - } - - // if supporting autodetect of the field, do the test here - return false; - } @Override protected void writingPopulateBlackboard() { super.writingPopulateBlackboard(); try { - String configMapKeyName = fieldNameForConfigInJavaIfPreset; + String configMapKeyName = fieldNameForConfigInJava; if (configMapKeyName==null) { if (!inferByScanning) { throw new IllegalStateException("no config key name set and not allowed to infer; " @@ -227,7 +220,7 @@ protected void writingPopulateBlackboard() { // write clues for ConfigInMapUnder... JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard); - Field f = Reflections.findFieldMaybe(getJavaObject().getClass(), fieldNameForConfigInJavaIfPreset).get(); + Field f = Reflections.findFieldMaybe(getJavaObject().getClass(), fieldNameForConfigInJava).get(); f.setAccessible(true); Map configMap = getRawConfigMap(f, getJavaObject()); if (configMap!=null) { @@ -244,6 +237,13 @@ protected void writingPopulateBlackboard() { addExtraTypeSerializers(getJavaObject().getClass()); } + + /** see {@link #isValidConfigFieldOrBlankSoWeCanRead(Class)}; cannot write via this if field blank + * as we cannot reverse engineer config keys from fields */ + protected boolean isValidConfigFieldSoWeCanWrite() { + if (Strings.isBlank(fieldNameForConfigInJava)) return false; + return findFieldMaybe(getJavaObject().getClass()).isPresent(); + } protected void writingInsertPhases() { super.writingInsertPhases(); @@ -262,13 +262,20 @@ protected Map getRawConfigMap(Field f, Object obj) throws Illega protected boolean isConfigurable(Class type) { if (type==null) return false; if (findConstructorMaybe(type).isAbsent()) return false; - if (findFieldMaybe(type).isAbsent()) return false; + if (!isValidConfigFieldOrBlankSoWeCanRead(type)) return false; if (staticKeysRequired && TopLevelConfigKeySerializer.findConfigKeys(type).isEmpty()) return false; return true; } + /** can be blank if reads are supported to a constructor but no config map field is used within + * the class, ie fields for each config value are populated on construction */ + protected boolean isValidConfigFieldOrBlankSoWeCanRead(Class type) { + if ("".equals(fieldNameForConfigInJava)) return true; + return findFieldMaybe(type).isPresent(); + } + protected Maybe findFieldMaybe(Class type) { - Maybe f = Reflections.findFieldMaybe(type, fieldNameForConfigInJavaIfPreset); + Maybe f = Reflections.findFieldMaybe(type, fieldNameForConfigInJava); if (f.isPresent() && !Map.class.isAssignableFrom(f.get().getType())) f = Maybe.absent(); return f; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java index 90c8b20b97..9495aa8c95 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java @@ -59,6 +59,8 @@ protected String getKeyNameForMapOfGeneralValues() { return keyNameForConfigWhenSerialized; } + @Override protected boolean includeFieldNameAsAlias() { return false; } + public static Set> findConfigKeys(Class clazz) { MutableMap> result = MutableMap.of(); @@ -128,5 +130,8 @@ protected void prepareTopLevelFields() { getTopLevelFieldsBlackboard().recordConfigKey(fieldName, configKey); } } - + + @Override + protected String toStringPrefix() { return "top-level-config"; } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java index 3a2fefd393..84747c9df0 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java @@ -66,7 +66,9 @@ public TopLevelFieldSerializer(String name, Field f) { keyName = alias.preferred(); aliases.add(alias.preferred()); } - aliases.add(f.getName()); + if (includeFieldNameAsAlias()) { + aliases.add(f.getName()); + } aliases.addAll(Arrays.asList(alias.value())); } @@ -74,6 +76,8 @@ public TopLevelFieldSerializer(String name, Field f) { // YomlFieldAtTopLevel ytf = f.getAnnotation(YomlFieldAtTopLevel.class); } + protected boolean includeFieldNameAsAlias() { return true; } + protected YomlSerializerWorker newWorker() { return new Worker(); } @@ -279,8 +283,10 @@ public void write() { } } + protected String toStringPrefix() { return "top-level-field"; } + @Override public String toString() { - return "top-level-field["+fieldName+"->"+keyName+":"+alias+"/"+aliases+"]"; + return toStringPrefix()+"["+fieldName+"->"+keyName+":"+alias+"/"+aliases+"]"; } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java index 9c9543bd3b..3b08b41c89 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Map; +import java.util.Objects; import org.apache.brooklyn.config.ConfigInheritance; import org.apache.brooklyn.config.ConfigInheritance.ConfigInheritanceContext; @@ -29,10 +30,13 @@ import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.yoml.YomlConfig; +import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConstructor; import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistryUsingConfigMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.testng.Assert; import org.testng.annotations.Test; import com.google.common.base.Predicate; @@ -53,8 +57,6 @@ static class MockConfigKey implements ConfigKey { TypeToken typeToken; T defaultValue; - ConfigInheritance inheritance = null; - public MockConfigKey(Class type, String name) { this.name = name; this.type = type; @@ -83,7 +85,7 @@ public MockConfigKey(TypeToken typeToken, String name) { @Override public ConfigInheritance getInheritance() { return null; } @Override public Predicate getConstraint() { return null; } @Override public boolean isValueValid(T value) { return true; } - @Override public ConfigInheritance getInheritanceByContext(ConfigInheritanceContext context) { return inheritance; } + @Override public ConfigInheritance getInheritanceByContext(ConfigInheritanceContext context) { return null; } @Override public Map getInheritanceByContext() { return MutableMap.of(); } } @@ -201,4 +203,102 @@ public void testReadWriteExtraField() { .doReadWriteAssertingJsonMatch(); } + @YomlConfigMapConstructor("") + @YomlAllFieldsTopLevel + static class KF { + @Alias("k") + static ConfigKey K1 = new MockConfigKey(String.class, "key1"); + final String key1Field; + transient final Map keysSuppliedToConstructorForTestAssertions; + KF(Map keys) { + key1Field = (String) keys.get(K1.getName()); + keysSuppliedToConstructorForTestAssertions = keys; + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof KF) && ((KF)obj).key1Field.equals(key1Field); + } + @Override + public int hashCode() { + return Objects.hash(key1Field); + } + @Override + public String toString() { + return super.toString()+":"+key1Field; + } + } + + final KF KF_FOO = new KF(MutableMap.of("key1", "foo")); + + @Test + public void testNoConfigMapFieldCanReadKeyToMapConstructor() { + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf", KF.class); + y.read("{ key1: foo }", "kf").assertResult(KF_FOO); + Assert.assertEquals(((KF)y.lastReadResult).keysSuppliedToConstructorForTestAssertions, KF_FOO.keysSuppliedToConstructorForTestAssertions); + } + @Test + public void testNoConfigMapFieldCanReadKeyAliasToMapConstructor() { + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf", KF.class); + y.read("{ k: foo }", "kf").assertResult(KF_FOO); + Assert.assertEquals(((KF)y.lastReadResult).keysSuppliedToConstructorForTestAssertions, KF_FOO.keysSuppliedToConstructorForTestAssertions); + } + @Test + public void testStaticFieldNameNotRelevant() { + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf", KF.class); + try { + y.read("{ k1: foo }", "kf"); + Asserts.shouldHaveFailedPreviously("Got "+y.lastReadResult); + } catch (Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, "k1", "foo"); + } + } + + @Test + public void testNoConfigMapFieldCanWriteAndReadToFieldDirectly() { + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf", KF.class); + // writing must write to the field directly because it is not defined how to reverse map to the config key + y.writing(KF_FOO).reading("{ type: kf, key1Field: foo }").doReadWriteAssertingJsonMatch(); + // nothing passed to constructor, but constructor is invoked + Assert.assertEquals(((KF)y.lastReadResult).keysSuppliedToConstructorForTestAssertions, MutableMap.of(), + "Constructor given unexpectedly non-empty map: "+((KF)y.lastReadResult).keysSuppliedToConstructorForTestAssertions); + } + + static class KF2 extends KF { + KF2(Map keys) { super(keys); } + + @Alias("key1Field") // alias same name as field means it *is* passed to constructor + static ConfigKey K1 = new MockConfigKey(String.class, "key1"); + } + + private static final KF2 KF2_FOO = new KF2(MutableMap.of("key1", "foo")); + + @Test + public void testConfigKeyTopLevelInherited() { + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf2", KF2.class); + y.read("{ key1: foo }", "kf2").assertResult(KF2_FOO); + } + + @Test + public void testConfigKeyOverrideHidesParentAlias() { + // this could be weakened to be allowed (but aliases at types must not be, for obvious reasons!) + + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf2", KF2.class); + try { + y.read("{ k: foo }", "kf2"); + Asserts.shouldHaveFailedPreviously("Got "+y.lastReadResult); + } catch (Exception e) { + Asserts.expectedFailureContainsIgnoreCase(e, "k", "foo"); + } + } + + @Test + public void testNoConfigMapFieldWillPreferConstructorIfKeyForFieldCanBeFound() { + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf2", KF2.class); + // writing must write to the field because it is not defined how to reverse map to the config key + y.writing(KF2_FOO).reading("{ type: kf2, key1Field: foo }").doReadWriteAssertingJsonMatch(); + // is passed to constructor + Assert.assertEquals(((KF)y.lastReadResult).keysSuppliedToConstructorForTestAssertions, KF_FOO.keysSuppliedToConstructorForTestAssertions); + } + } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyGenericsTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyGenericsTests.java index 0f66d8840e..c1fdbc9e18 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyGenericsTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyGenericsTests.java @@ -58,7 +58,6 @@ public String toString() { } } - @YomlConfigMapConstructor("conf") @SuppressWarnings({ "rawtypes" }) static class MG extends M0 { static MockConfigKey KR = new MockConfigKey(Map.class, "kr"); diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyInheritanceTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyInheritanceTests.java deleted file mode 100644 index 24f056a598..0000000000 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlConfigKeyInheritanceTests.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.brooklyn.util.yoml.tests; - -import java.util.Map; - -import org.apache.brooklyn.config.ConfigInheritance; -import org.apache.brooklyn.test.Asserts; -import org.apache.brooklyn.util.collections.Jsonya; -import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConstructor; -import org.apache.brooklyn.util.yoml.tests.TopLevelConfigKeysTests.MockConfigKey; -import org.apache.brooklyn.util.yoml.tests.YomlConfigKeyGenericsTests.M0; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testng.annotations.Test; - -import com.google.common.reflect.TypeToken; - -/** Tests that the default serializers can read/write types and fields. - *

- * And shows how to use them at a low level. - */ -public class YomlConfigKeyInheritanceTests { - - private static final Logger log = LoggerFactory.getLogger(YomlConfigKeyInheritanceTests.class); - - @YomlConfigMapConstructor("conf") - @SuppressWarnings({ "deprecation" }) - static class M1 extends M0 { - @SuppressWarnings("serial") - static MockConfigKey> KM = new MockConfigKey>(new TypeToken>() {}, "km"); - static { KM.inheritance = ConfigInheritance.DEEP_MERGE; } - - @SuppressWarnings("serial") - static MockConfigKey> KO = new MockConfigKey>(new TypeToken>() {}, "ko"); - static { KO.inheritance = ConfigInheritance.ALWAYS; } - - M1(Map keys) { super(keys); } - } - - @Test - public void testReadMergedMap() { - YomlTestFixture y = YomlTestFixture.newInstance() - .addTypeWithAnnotations("m1", M1.class) - .addType("m1a", "{ type: m1, km: { a: 1, b: 1 }, ko: { a: 1, b: 1 } }") - .addType("m1b", "{ type: m1a, km: { b: 2 }, ko: { b: 2 } }"); - - y.read("{ type: m1b }", null); - - M1 m1b = (M1)y.lastReadResult; - Asserts.assertEquals(m1b.conf.get(M1.KM.getName()), MutableMap.of("a", 1, "b", 2)); - // MERGE is the default, OVERWRITE not respected: - Asserts.assertEquals(m1b.conf.get(M1.KO.getName()), MutableMap.of("b", 2)); - } - - @Test - public void testWrite() { - // the write is not smart enough to look at default/inherited KV pairs - // (this would be nice to change, but a lot of work and not really worth it) - - YomlTestFixture y = YomlTestFixture.newInstance() - .addTypeWithAnnotationsAndConfigFieldsIgnoringInheritance("m1", M1.class, MutableMap.of("keys", "config")); - - M1 m1b = new M1(MutableMap.of("km", MutableMap.of("a", 1, "b", 2), "ko", MutableMap.of("a", 1, "b", 2))); - y.write(m1b); - log.info("written as "+y.lastWriteResult); - YomlTestFixture.assertEqualish(Jsonya.newInstance().add(y.lastWriteResult).toString(), - "{ type=m1, km:{ a: 1, b: 2}, ko: { a: 1, b: 2 } }", "wrong serialization"); - YomlTestFixture.assertEqualish(y.lastWriteResult.toString(), - "{ type=m1, km:{ a: 1, b: 2}, ko: { a: 1, b: 2 } }", "wrong serialization"); - } - - // TODO same for bag - -} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java index 5d814b2d36..64f05c59fa 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java @@ -127,6 +127,9 @@ static void assertEqualish(Object s1, Object s2, String message) { public void assertLastWriteIgnoringQuotes(String expected, String message) { assertEqualish(Jsonya.newInstance().add(getLastWriteResult()).toString(), expected, message); } + public void assertLastWriteIgnoringQuotes(String expected) { + assertEqualish(Jsonya.newInstance().add(getLastWriteResult()).toString(), expected, "mismatch"); + } public YomlTestFixture addType(String name, Class type) { tr.put(name, type); return this; } public YomlTestFixture addType(String name, Class type, List serializers) { tr.put(name, type, serializers); return this; } @@ -137,7 +140,7 @@ public YomlTestFixture addTypeWithAnnotations(Class type) { return addTypeWithAnnotations(null, type); } public YomlTestFixture addTypeWithAnnotations(String optionalName, Class type) { - Set serializers = annotationsProvider().findSerializerAnnotations(type, true); + Set serializers = annotationsProvider().findSerializerAnnotations(type, false); for (String n: new YomlAnnotations().findTypeNamesFromAnnotations(type, optionalName, false)) { tr.put(n, type, serializers); } @@ -145,7 +148,7 @@ public YomlTestFixture addTypeWithAnnotations(String optionalName, Class type } public YomlTestFixture addTypeWithAnnotationsAndConfigFieldsIgnoringInheritance(String optionalName, Class type, Map configFieldsToKeys) { - Set serializers = annotationsProvider().findSerializerAnnotations(type, true); + Set serializers = annotationsProvider().findSerializerAnnotations(type, false); for (Map.Entry entry: configFieldsToKeys.entrySet()) { serializers.addAll( InstantiateTypeFromRegistryUsingConfigMap.newFactoryIgnoringInheritance().newConfigKeyClassScanningSerializers( entry.getKey(), entry.getValue(), true) ); From 52ccd7c322dcc46c172e4d681c773de9561837f0 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 27 Sep 2016 15:49:50 +0100 Subject: [PATCH 52/77] yoml allows one key to specify the type of another key and that other key can be real in the object or virtual at runtime --- .../yoml/annotations/YomlAnnotations.java | 14 +- .../annotations/YomlTypeFromOtherField.java | 46 ++++++ .../ConfigInMapUnderConfigSerializer.java | 7 +- .../serializers/FieldsInMapUnderFields.java | 91 ++++++++++- .../serializers/TopLevelFieldSerializer.java | 2 +- .../TypeFromOtherFieldBlackboard.java | 54 +++++++ .../TypeFromOtherFieldSerializer.java | 73 +++++++++ .../org/apache/brooklyn/util/yoml/sketch.md | 5 +- .../tests/FieldTypeFromOtherFieldTest.java | 143 ++++++++++++++++++ 9 files changed, 422 insertions(+), 13 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlTypeFromOtherField.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TypeFromOtherFieldBlackboard.java create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TypeFromOtherFieldSerializer.java create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/FieldTypeFromOtherFieldTest.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java index 8bcf3cb5f1..fa82dc6c9f 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAnnotations.java @@ -41,6 +41,7 @@ import org.apache.brooklyn.util.yoml.serializers.RenameKeySerializer.RenameDefaultKey; import org.apache.brooklyn.util.yoml.serializers.RenameKeySerializer.RenameDefaultValue; import org.apache.brooklyn.util.yoml.serializers.TopLevelFieldSerializer; +import org.apache.brooklyn.util.yoml.serializers.TypeFromOtherFieldSerializer; public class YomlAnnotations { @@ -64,12 +65,17 @@ public Set findTypeNamesFromAnnotations(Class type, String optionalDe return names; } - public Collection findTopLevelFieldSerializers(Class t, boolean requireAnnotation) { - List result = MutableList.of(); + public Collection findTopLevelFieldSerializers(Class t, boolean requireAnnotation) { + List result = MutableList.of(); Map fields = YomlUtils.getAllNonTransientNonStaticFields(t, null); for (Map.Entry f: fields.entrySet()) { - if (!requireAnnotation || f.getValue().isAnnotationPresent(YomlTopLevelField.class)) - result.add(new TopLevelFieldSerializer(f.getKey(), f.getValue())); + if (!requireAnnotation || f.getValue().isAnnotationPresent(YomlTopLevelField.class)) { + result.add(new TopLevelFieldSerializer(f.getKey(), f.getValue())); + YomlTypeFromOtherField typeFromOther = f.getValue().getAnnotation(YomlTypeFromOtherField.class); + if (typeFromOther!=null) { + result.add(new TypeFromOtherFieldSerializer(f.getKey(), typeFromOther)); + } + } } return result; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlTypeFromOtherField.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlTypeFromOtherField.java new file mode 100644 index 0000000000..e321ff6b1e --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlTypeFromOtherField.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.annotations; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Indicates that the type of a field should be taken at runtime from another field. + * This is useful to allow a user to specify e.g. { objType: int, obj: 3 } + * rather than requiring { obj: { type: int, value: e } }. + *

+ * By default the other field is assumed to exist but that need not be the case. + */ +@Retention(RUNTIME) +@Target(FIELD) +public @interface YomlTypeFromOtherField { + + /** The other field which will supply the type. */ + String value(); + + /** Whether the other field is real and present in the java object and + * should be serialized/deserialized normally; + * if false it will be created on writing and deleted after reading */ + boolean real() default true; + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java index 91e978b504..28f082cf38 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java @@ -23,6 +23,7 @@ import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.internal.YomlContext; import org.apache.brooklyn.util.yoml.internal.YomlContextForRead; import org.apache.brooklyn.util.yoml.internal.YomlContextForWrite; @@ -66,9 +67,13 @@ public void read() { protected boolean shouldHaveJavaObject() { return false; } @Override - protected boolean setKeyValueForJavaObjectOnRead(String key, Object value) throws IllegalAccessException { + protected boolean setKeyValueForJavaObjectOnRead(String key, Object value, String optionalTypeConstraint) throws IllegalAccessException { JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard, getKeyNameForMapOfGeneralValues()); String optionalType = getType(key, null); + if (optionalTypeConstraint!=null) { + throw new YomlException("Types from other types not supported for config keys"); + // TODO see behaviour in super to override + } Object v2; try { v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, optionalType, context) ); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java index e807fa5ba8..d29929025d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java @@ -20,6 +20,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.List; import java.util.Map; import org.apache.brooklyn.util.collections.MutableList; @@ -27,11 +28,12 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.javalang.Reflections; +import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.internal.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlContext.StandardPhases; import org.apache.brooklyn.util.yoml.internal.YomlContextForRead; import org.apache.brooklyn.util.yoml.internal.YomlContextForWrite; import org.apache.brooklyn.util.yoml.internal.YomlUtils; -import org.apache.brooklyn.util.yoml.internal.YomlContext.StandardPhases; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,7 +57,7 @@ protected String getExpectedPhaseRead() { public class Worker extends YomlSerializerWorker { - protected boolean setKeyValueForJavaObjectOnRead(String key, Object value) + protected boolean setKeyValueForJavaObjectOnRead(String key, Object value, String optionalTypeConstraint) throws IllegalAccessException { Maybe ffm = Reflections.findFieldMaybe(getJavaObject().getClass(), key); if (ffm.isAbsentOrNull()) { @@ -67,7 +69,7 @@ protected boolean setKeyValueForJavaObjectOnRead(String key, Object value) // as above return false; } else { - String fieldType = YomlUtils.getFieldTypeName(ff, config); + String fieldType = getFieldTypeName(ff, optionalTypeConstraint); Object v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, fieldType, context) ); ff.setAccessible(true); @@ -77,6 +79,21 @@ protected boolean setKeyValueForJavaObjectOnRead(String key, Object value) } } + protected String getFieldTypeName(Field ff, String optionalTypeConstraint) { + String fieldType; + if (optionalTypeConstraint!=null) { + if (!Object.class.equals(ff.getType())) { + throw new YomlException("Cannot apply inferred type "+optionalTypeConstraint+" for non-Object field "+ff, context); + // is there a "combineTypes(fieldType, optionalTypeConstraint)" method? + // that would let us weaken the above + } + fieldType = optionalTypeConstraint; + } else { + fieldType = YomlUtils.getFieldTypeName(ff, config); + } + return fieldType; + } + protected boolean shouldHaveJavaObject() { return true; } public void read() { @@ -87,17 +104,52 @@ public void read() { Map fields = peekFromYamlKeysOnBlackboard(getKeyNameForMapOfGeneralValues(), Map.class).orNull(); if (fields==null) return; + MutableMap initialFields = MutableMap.copyOf(fields); + + List deferred = MutableList.of(); boolean changed = false; - for (Object fo: MutableList.copyOf( ((Map)fields).keySet() )) { + for (Object fo: initialFields.keySet()) { String f = (String)fo; + if (TypeFromOtherFieldBlackboard.get(blackboard).getTypeConstraintField(f)!=null) { + deferred.add(f); + continue; + } Object v = ((Map)fields).get(f); try { - if (setKeyValueForJavaObjectOnRead(f, v)) { + if (setKeyValueForJavaObjectOnRead(f, v, null)) { ((Map)fields).remove(f); changed = true; } } catch (Exception e) { throw Exceptions.propagate(e); } } + + // defer for objects whose types come from another field + for (String f: deferred) { + Object typeO; + String tf = TypeFromOtherFieldBlackboard.get(blackboard).getTypeConstraintField(f); + boolean isTypeFieldReal = TypeFromOtherFieldBlackboard.get(blackboard).isTypeConstraintFieldReal(f); + + if (isTypeFieldReal) { + typeO = initialFields.get(tf); + } else { + typeO = peekFromYamlKeysOnBlackboard(tf, Object.class).orNull(); + } + if (typeO!=null && !(typeO instanceof String)) { + throw new YomlException("Wrong type of value '"+typeO+"' inferred as type of '"+f+"' on "+getJavaObject(), context); + } + String type = (String)typeO; + + Object v = ((Map)fields).get(f); + try { + if (setKeyValueForJavaObjectOnRead(f, v, type)) { + ((Map)fields).remove(f); + if (type!=null && !isTypeFieldReal) { + removeFromYamlKeysOnBlackboard(tf); + } + changed = true; + } + } catch (Exception e) { throw Exceptions.propagate(e); } + } if (((Map)fields).isEmpty()) { removeFromYamlKeysOnBlackboard(getKeyNameForMapOfGeneralValues()); @@ -134,7 +186,34 @@ protected Map writePrepareGeneralMap() { // silently drop null fields } else { Field ff = Reflections.findFieldMaybe(getJavaObject().getClass(), f).get(); - String fieldType = YomlUtils.getFieldTypeName(ff, config); + + String fieldType = getFieldTypeName(ff, null); + + String tf = TypeFromOtherFieldBlackboard.get(blackboard).getTypeConstraintField(f); + if (tf!=null) { + if (!Object.class.equals(ff.getType())) { + // currently we only support smart types if the base type is object; + // see getFieldTypeName + } else { + if (!TypeFromOtherFieldBlackboard.get(blackboard).isTypeConstraintFieldReal(f)) { + String realType = config.getTypeRegistry().getTypeName(v.get()); + fieldType = realType; + // for non-real, just write the pseudo-type-field at root + getYamlMap().put(tf, realType); + + } else { + Maybe rt = Reflections.getFieldValueMaybe(getJavaObject(), tf); + if (rt.isPresentAndNonNull()) { + if (rt.get() instanceof String) { + fieldType = (String) rt.get(); + } else { + throw new YomlException("Cannot use type information from "+tf+" for "+f+" as it is "+rt.get(), context); + } + } + } + } + } + Object v2 = converter.write(new YomlContextForWrite(v.get(), context.getJsonPath()+"/"+f, fieldType, context) ); fields.put(f, v2); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java index 84747c9df0..dfea380be1 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java @@ -226,7 +226,7 @@ public void read() { } } if (keysMatched>0) { - // repeat the preparing phase if we set any keys, so that remapping can apply + // repeat this manipulating phase if we set any keys, so that remapping can apply getTopLevelFieldsBlackboard().setFieldDone(fieldName); context.phaseInsert(StandardPhases.MANIPULATING); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TypeFromOtherFieldBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TypeFromOtherFieldBlackboard.java new file mode 100644 index 0000000000..be63b0ccd8 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TypeFromOtherFieldBlackboard.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.serializers; + +import java.util.Map; + +import org.apache.brooklyn.util.collections.MutableMap; + +public class TypeFromOtherFieldBlackboard { + + public static final String KEY = TypeFromOtherFieldBlackboard.class.getCanonicalName(); + + public static TypeFromOtherFieldBlackboard get(Map blackboard) { + Object v = blackboard.get(KEY); + if (v==null) { + v = new TypeFromOtherFieldBlackboard(); + blackboard.put(KEY, v); + } + return (TypeFromOtherFieldBlackboard) v; + } + + private final Map typeConstraintField = MutableMap.of(); + private final Map typeConstraintFieldIsReal = MutableMap.of(); + + public void setTypeConstraint(String field, String typeField, boolean isReal) { + typeConstraintField.put(field, typeField); + typeConstraintFieldIsReal.put(field, isReal); + } + + public String getTypeConstraintField(String field) { + return typeConstraintField.get(field); + } + + public boolean isTypeConstraintFieldReal(String f) { + return typeConstraintFieldIsReal.get(f); + } + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TypeFromOtherFieldSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TypeFromOtherFieldSerializer.java new file mode 100644 index 0000000000..0ce91914fd --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TypeFromOtherFieldSerializer.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.serializers; + +import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlTypeFromOtherField; +import org.apache.brooklyn.util.yoml.internal.YomlContext.StandardPhases; + +/** Populates a blackboard recording the fact, for use by {@link FieldsInMapUnderFields} */ +@YomlAllFieldsTopLevel +@Alias("type-from-other-field") +public class TypeFromOtherFieldSerializer extends YomlSerializerComposition { + + public TypeFromOtherFieldSerializer() {} + public TypeFromOtherFieldSerializer(String fieldName, YomlTypeFromOtherField otherFieldInfo) { + this(fieldName, otherFieldInfo.value(), otherFieldInfo.real()); + } + public TypeFromOtherFieldSerializer(String fieldNameToDecorate, String fieldNameContainingType, boolean isFieldReal) { + this.field = fieldNameToDecorate; + this.typeField = fieldNameContainingType; + this.typeFieldReal = isFieldReal; + } + + String field; + String typeField; + boolean typeFieldReal; + + protected YomlSerializerWorker newWorker() { + return new Worker(); + } + + public class Worker extends YomlSerializerWorker { + + public void go() { + // probably runs too often but optimize that later + TypeFromOtherFieldBlackboard.get(blackboard).setTypeConstraint(field, typeField, typeFieldReal); + } + + public void read() { + if (!context.isPhase(StandardPhases.MANIPULATING)) return; + if (!isYamlMap()) return; + go(); + } + + public void write() { + if (!context.isPhase(StandardPhases.HANDLING_TYPE)) return; + go(); + } + + } + + @Override + public String toString() { + return super.toString()+"["+field+"<-"+typeField+"]"; + } +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md index 8c6551c22b..f497089172 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/sketch.md @@ -547,6 +547,9 @@ either on a global or a per-class basis in the registry. * `default-map-values` (`@YomlDefaultMapValues`) * allows default key-value pairs to be added on read and removed on write +* `type-from-other-field` (`@YomlTypeFromOtherField`) + * indicates that type information for one field can be found in the value of another field + ## Implementation notes @@ -570,7 +573,7 @@ the right order whilst allowing them to be extended, but care does need to be ta The general phases are: * `manipulating` (custom serializers, operating directly on the input YAML map) -* `handling-type` (default to instantaiate the java type, on read, or set the `type` field, on write), +* `handling-type` (default to instantiate the java type, on read, or set the `type` field, on write), on read, sets the Java object and sets YamlKeysOnBlackboard which are subsequently used for manipulation; on write, sets the YAML object and sets JavaFieldsOnBlackboard (and sets ReadingTypeOnBlackboard with errors); inserting new phases: diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/FieldTypeFromOtherFieldTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/FieldTypeFromOtherFieldTest.java new file mode 100644 index 0000000000..bd908eebdd --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/FieldTypeFromOtherFieldTest.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.tests; + +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlTypeFromOtherField; +import org.testng.Assert; +import org.testng.annotations.Test; + +import com.google.common.base.Objects; + +/** Tests that top-level fields can be set at the outer level in yaml. */ +public class FieldTypeFromOtherFieldTest { + + @YomlAllFieldsTopLevel + static abstract class FieldTypeFromOtherAbstract { + + public abstract Object val(); + + @Override + public int hashCode() { + return Objects.hashCode(val()); + } + @Override + public boolean equals(Object obj) { + if (!(obj instanceof FieldTypeFromOtherAbstract)) return false; + return Objects.equal(val(), ((FieldTypeFromOtherAbstract)obj).val()); + } + } + + + @Alias("fto") + static class FieldTypeFromOther extends FieldTypeFromOtherAbstract { + + public FieldTypeFromOther(String typ, Object val) { + this.typ = typ; + this.val = val; + } + public FieldTypeFromOther() {} + + String typ; + + @YomlTypeFromOtherField("typ") + Object val; + + @Override + public Object val() { + return val; + } + } + + protected FieldTypeFromOther read(String input) { + YomlTestFixture y = fixture(); + y.read(input, "fto" ); + Asserts.assertInstanceOf(y.lastReadResult, FieldTypeFromOther.class); + return (FieldTypeFromOther)y.lastReadResult; + } + + protected YomlTestFixture fixture() { + return YomlTestFixture.newInstance().addTypeWithAnnotations(FieldTypeFromOther.class); + } + + @Test + public void testValueFromMap() { + Assert.assertEquals(read("{ val: { type: int, value: 42 } }").val, 42); + } + + @Test + public void testTypeUsed() { + Assert.assertEquals(read("{ typ: int, val: 42 }").val, 42); + } + + @Test + public void testReadWriteWithType() { + fixture().reading("{ type: fto, typ: int, val: 42 }").writing(new FieldTypeFromOther("int", 42)).doReadWriteAssertingJsonMatch(); + } + + @Test + public void testReadWriteWithoutType() { + fixture().reading("{ type: fto, val: { type: int, value: 42 } }").writing(new FieldTypeFromOther(null, 42)).doReadWriteAssertingJsonMatch(); + } + + @Test + public void testFailsIfTypeUnknown() { + try { + FieldTypeFromOther result = read("{ val: 42 }"); + Asserts.shouldHaveFailedPreviously("Instead got "+result); + } catch (Exception e) { + Asserts.expectedFailureContains(e, "val"); + } + } + + @Test + public void testMapSupportedWithType() { + Assert.assertEquals(read("{ typ: int, val: { type: int, value: 42 } }").val, 42); + } + + @Alias("fto-type-key-not-real") + static class FieldTypeFromOtherNotReal extends FieldTypeFromOtherAbstract { + + public FieldTypeFromOtherNotReal(Object val) { + this.val = val; + } + public FieldTypeFromOtherNotReal() {} + + @YomlTypeFromOtherField(value="typ", real=false) + Object val; + + @Override + public Object val() { + return val; + } + } + + @Test + public void testReadWriteWithTypeInNotRealKey() { + // in this mode the field is in yaml but not on the object + YomlTestFixture.newInstance().addTypeWithAnnotations(FieldTypeFromOtherNotReal.class) + .reading("{ type: fto-type-key-not-real, typ: int, val: 42 }").writing(new FieldTypeFromOtherNotReal(42)).doReadWriteAssertingJsonMatch(); + } + + + // TODO test w config + +} From 6e013ee14ac8dbe94385d8ef396640ba2f87ad3c Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 27 Sep 2016 17:11:45 +0100 Subject: [PATCH 53/77] support config key's type coming from another config key --- .../annotations/YomlTypeFromOtherField.java | 10 +-- .../ConfigInMapUnderConfigSerializer.java | 54 +++++++++++++--- .../serializers/FieldsInMapUnderFields.java | 12 +++- ...antiateTypeFromRegistryUsingConfigMap.java | 5 +- .../TopLevelConfigKeySerializer.java | 18 ++++-- .../tests/FieldTypeFromOtherFieldTest.java | 63 ++++++++++++++++++- 6 files changed, 136 insertions(+), 26 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlTypeFromOtherField.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlTypeFromOtherField.java index e321ff6b1e..4a8ac35ffa 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlTypeFromOtherField.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlTypeFromOtherField.java @@ -35,12 +35,14 @@ @Target(FIELD) public @interface YomlTypeFromOtherField { - /** The other field which will supply the type. */ + /** The other field which will supply the type. This must point at the field name or the config key name, + * not any alias, although aliases can be used in the YAML when specifying the type. */ String value(); - /** Whether the other field is real and present in the java object and - * should be serialized/deserialized normally; - * if false it will be created on writing and deleted after reading */ + /** Whether the other field is real in the java object, + * ie present as a field or config key, and so should be serialized/deserialized normally; + * if false it will be created on writing to yaml and deleted while reading, + * ie not reflected in the java object */ boolean real() default true; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java index 28f082cf38..9b43a8dd1f 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java @@ -20,6 +20,8 @@ import java.util.Map; +import javax.annotation.Nullable; + import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.exceptions.Exceptions; @@ -70,10 +72,10 @@ public void read() { protected boolean setKeyValueForJavaObjectOnRead(String key, Object value, String optionalTypeConstraint) throws IllegalAccessException { JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.peek(blackboard, getKeyNameForMapOfGeneralValues()); String optionalType = getType(key, null); - if (optionalTypeConstraint!=null) { - throw new YomlException("Types from other types not supported for config keys"); - // TODO see behaviour in super to override - } + ConfigKey cKey = getKey(key); + + optionalType = merge(key, cKey==null ? null : cKey.getType(), optionalType, optionalTypeConstraint); + Object v2; try { v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, optionalType, context) ); @@ -98,8 +100,37 @@ protected Map writePrepareGeneralMap() { Map configMap = MutableMap.of(); for (Map.Entry entry: fib.configToWriteFromJava.entrySet()) { - // NB: won't normally have a type, the explicit config keys will take those String optionalType = getType(entry.getKey(), entry.getValue()); + ConfigKey cKey = getKey(entry.getKey()); + + // can we record additional information about the type in the yaml? + // TODO merge with similar code in overwritten method + String tf = TypeFromOtherFieldBlackboard.get(blackboard).getTypeConstraintField(entry.getKey()); + if (tf!=null) { + if (cKey!=null && !Object.class.equals(cKey.getType())) { + // currently we only support smart types if the base type is object; + // see getFieldTypeName + + } else { + if (!TypeFromOtherFieldBlackboard.get(blackboard).isTypeConstraintFieldReal(entry.getKey())) { + String realType = config.getTypeRegistry().getTypeName(entry.getValue()); + optionalType = realType; + // for non-real, just write the pseudo-type-field at root + getYamlMap().put(tf, realType); + + } else { + Object rt = fib.configToWriteFromJava.get(tf); + if (rt!=null) { + if (rt instanceof String) { + optionalType = (String) rt; + } else { + throw new YomlException("Cannot use type information from "+tf+" for "+cKey+" as it is "+rt, context); + } + } + } + } + } + Object v = converter.write(new YomlContextForWrite(entry.getValue(), context.getJsonPath()+"/"+entry.getKey(), optionalType, context) ); configMap.put(entry.getKey(), v); } @@ -108,16 +139,21 @@ protected Map writePrepareGeneralMap() { return configMap; } - protected String getType(String key, Object value) { - TopLevelFieldsBlackboard efb = TopLevelFieldsBlackboard.get(blackboard, getKeyNameForMapOfGeneralValues()); - ConfigKey typeKey = efb.getConfigKey(key); - TypeToken type = typeKey==null ? null : typeKey.getTypeToken(); + @Nullable protected String getType(String keyName, Object value) { + ConfigKey keyForTypeInfo = getKey(keyName); + TypeToken type = keyForTypeInfo==null ? null : keyForTypeInfo.getTypeToken(); String optionalType = null; if (type!=null && (value==null || type.getRawType().isInstance(value))) optionalType = YomlUtils.getTypeNameWithGenerics(type, config.getTypeRegistry()); return optionalType; } + @Nullable protected ConfigKey getKey(String keyName) { + TopLevelFieldsBlackboard efb = TopLevelFieldsBlackboard.get(blackboard, getKeyNameForMapOfGeneralValues()); + ConfigKey typeKey = efb.getConfigKey(keyName); + return typeKey; + } + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java index d29929025d..aca2688881 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java @@ -80,16 +80,20 @@ protected boolean setKeyValueForJavaObjectOnRead(String key, Object value, Strin } protected String getFieldTypeName(Field ff, String optionalTypeConstraint) { + return merge(ff, ff.getType(), YomlUtils.getFieldTypeName(ff, config), optionalTypeConstraint); + } + + protected String merge(Object elementForError, Class localTypeJ, String localTypeName, String optionalTypeConstraint) { String fieldType; if (optionalTypeConstraint!=null) { - if (!Object.class.equals(ff.getType())) { - throw new YomlException("Cannot apply inferred type "+optionalTypeConstraint+" for non-Object field "+ff, context); + if (localTypeJ!=null && !Object.class.equals(localTypeJ)) { + throw new YomlException("Cannot apply inferred type "+optionalTypeConstraint+" for non-Object field "+elementForError, context); // is there a "combineTypes(fieldType, optionalTypeConstraint)" method? // that would let us weaken the above } fieldType = optionalTypeConstraint; } else { - fieldType = YomlUtils.getFieldTypeName(ff, config); + fieldType = localTypeName; } return fieldType; } @@ -189,11 +193,13 @@ protected Map writePrepareGeneralMap() { String fieldType = getFieldTypeName(ff, null); + // can we record additional information about the type in the yaml? String tf = TypeFromOtherFieldBlackboard.get(blackboard).getTypeConstraintField(f); if (tf!=null) { if (!Object.class.equals(ff.getType())) { // currently we only support smart types if the base type is object; // see getFieldTypeName + } else { if (!TypeFromOtherFieldBlackboard.get(blackboard).isTypeConstraintFieldReal(f)) { String realType = config.getTypeRegistry().getTypeName(v.get()); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java index 616ebcebc7..28f2f6f349 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java @@ -101,8 +101,7 @@ protected Set findSerializers( if (type!=null) { instantiator.inferByScanning = false; - Map keys = TopLevelConfigKeySerializer.findConfigKeySerializers(keyNameForConfigWhenSerialized, type); - result.addAll(keys.values()); + result.addAll(TopLevelConfigKeySerializer.findConfigKeySerializers(keyNameForConfigWhenSerialized, type)); } else { instantiator.inferByScanning = true; } @@ -157,7 +156,7 @@ protected boolean readType(String type) { protected void addExtraTypeSerializers(Class clazz) { if (inferByScanning) { SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers( - TopLevelConfigKeySerializer.findConfigKeySerializers(keyNameForConfigWhenSerialized, clazz).values()); + TopLevelConfigKeySerializer.findConfigKeySerializers(keyNameForConfigWhenSerialized, clazz) ); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java index 9495aa8c95..19d56c71a0 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java @@ -19,7 +19,6 @@ package org.apache.brooklyn.util.yoml.serializers; import java.lang.reflect.Field; -import java.util.Map; import java.util.Set; import org.apache.brooklyn.config.ConfigKey; @@ -28,6 +27,7 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.annotations.YomlTypeFromOtherField; import org.apache.brooklyn.util.yoml.internal.YomlUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -88,8 +88,9 @@ public static Set> findConfigKeys(Class clazz) { } /** only useful in conjuction with {@link InstantiateTypeFromRegistryUsingConfigMap} static serializer factory methods */ - public static Map findConfigKeySerializers(String keyNameForConfigWhenSerialized, Class clazz) { - MutableMap result = MutableMap.of(); + public static Set findConfigKeySerializers(String keyNameForConfigWhenSerialized, Class clazz) { + MutableMap resultKeys = MutableMap.of(); + Set resultOthers = MutableSet.of(); for (Field f: YomlUtils.getAllNonTransientStaticFields(clazz).values()) { try { @@ -101,9 +102,14 @@ public static Map findConfigKeySerializers(String keyName else if (ckO instanceof HasConfigKey) ck = ((HasConfigKey)ckO).getConfigKey(); if (ck==null) continue; - if (result.containsKey(ck.getName())) continue; + if (resultKeys.containsKey(ck.getName())) continue; + + resultKeys.put(ck.getName(), new TopLevelConfigKeySerializer(keyNameForConfigWhenSerialized, ck, f)); - result.put(ck.getName(), new TopLevelConfigKeySerializer(keyNameForConfigWhenSerialized, ck, f)); + YomlTypeFromOtherField typeFromOther = f.getAnnotation(YomlTypeFromOtherField.class); + if (typeFromOther!=null) { + resultOthers.add(new TypeFromOtherFieldSerializer(ck.getName(), typeFromOther)); + } } catch (Exception e) { Exceptions.propagateIfFatal(e); @@ -112,7 +118,7 @@ public static Map findConfigKeySerializers(String keyName } - return result; + return MutableSet.copyOf(resultKeys.values()).putAll(resultOthers); } protected YomlSerializerWorker newWorker() { diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/FieldTypeFromOtherFieldTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/FieldTypeFromOtherFieldTest.java index bd908eebdd..9f52070459 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/FieldTypeFromOtherFieldTest.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/FieldTypeFromOtherFieldTest.java @@ -18,9 +18,14 @@ */ package org.apache.brooklyn.util.yoml.tests; +import java.util.Map; + +import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlConfigMapConstructor; import org.apache.brooklyn.util.yoml.annotations.YomlTypeFromOtherField; import org.testng.Assert; import org.testng.annotations.Test; @@ -137,7 +142,63 @@ public void testReadWriteWithTypeInNotRealKey() { .reading("{ type: fto-type-key-not-real, typ: int, val: 42 }").writing(new FieldTypeFromOtherNotReal(42)).doReadWriteAssertingJsonMatch(); } + + @YomlConfigMapConstructor("vals") + @Alias("fto-from-config") + static class FieldTypeFromOtherConfig extends FieldTypeFromOtherAbstract { + + private Map vals; + + @YomlTypeFromOtherField(value="valType") + public static final ConfigKey VALUE = new TopLevelConfigKeysTests.MockConfigKey(Object.class, "val"); + + @Alias(preferred="typ") + public static final ConfigKey TYPE = new TopLevelConfigKeysTests.MockConfigKey(String.class, "valType"); + + public FieldTypeFromOtherConfig(Map vals) { + this.vals = vals; + } + + @Override + public Object val() { + return vals.get(VALUE.getName()); + } + } - // TODO test w config + @Test + public void testReadWriteWithTypeInConfig() { + YomlTestFixture.newInstance().addTypeWithAnnotations(FieldTypeFromOtherConfig.class) + .reading("{ type: fto-from-config, typ: int, val: 42 }").writing(new FieldTypeFromOtherConfig( + MutableMap.of("valType", (Object)"int", "val", 42))) + .doReadWriteAssertingJsonMatch(); + } + + @YomlConfigMapConstructor("vals") + @Alias("fto-from-config-type-not-real") + static class FieldTypeFromOtherConfigTypeNotReal extends FieldTypeFromOtherAbstract { + + private Map vals; + + @YomlTypeFromOtherField(value="typ", real=false) + public static final ConfigKey VALUE = new TopLevelConfigKeysTests.MockConfigKey(Object.class, "val"); + + public FieldTypeFromOtherConfigTypeNotReal(Map vals) { + this.vals = vals; + } + + @Override + public Object val() { + return vals.get(VALUE.getName()); + } + } + + @Test + public void testReadWriteWithTypeInConfigTypeNotReal() { + YomlTestFixture.newInstance().addTypeWithAnnotations(FieldTypeFromOtherConfigTypeNotReal.class) + .reading("{ type: fto-from-config-type-not-real, typ: int, val: 42 }").writing(new FieldTypeFromOtherConfigTypeNotReal( + MutableMap.of("val", (Object)42))) + .doReadWriteAssertingJsonMatch(); + } + } From 45d435c4493c46b05df3359725d16bb16b1202e9 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 27 Sep 2016 19:36:41 +0100 Subject: [PATCH 54/77] static sensor loaded via yoml !!! --- .../camp/yoml/BrooklynYomlTypeRegistry.java | 2 ++ ...omlTypeRegistryEntityInitializersTest.java | 34 +++++++++++++++++-- .../brooklyn/core/sensor/StaticSensor.java | 4 +++ .../brooklyn/util/yoml/YomlTypeRegistry.java | 4 ++- .../yoml/serializers/ConvertSingletonMap.java | 1 + .../yoml/serializers/RenameKeySerializer.java | 11 +++--- .../serializers/YamlKeysOnBlackboard.java | 1 + 7 files changed, 49 insertions(+), 8 deletions(-) diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java index f7a65c10b3..fb6e99e19e 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java @@ -55,6 +55,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.reflect.TypeToken; @@ -218,6 +219,7 @@ public YomlClassNotFoundException(String message, Throwable cause) { */ @Override public Maybe> getJavaTypeMaybe(String typeName) { + if (typeName==null) return Maybe.absent("null type"); return getJavaTypeInternal(typeName, defaultLoadingContext); } diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java index 44d337a5fa..db5ca547f9 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java @@ -22,6 +22,7 @@ import java.util.concurrent.TimeoutException; import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntityInitializer; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.camp.yoml.types.YomlInitializers; import org.apache.brooklyn.core.sensor.DependentConfiguration; @@ -57,6 +58,7 @@ public void testStaticSensorBasic() throws Exception { " brooklyn.initializers:", " - name: the-answer", " type: static-sensor", + " sensor-type: int", " value: 42"); checkStaticSensorInApp(yaml); @@ -67,12 +69,37 @@ public void testReadSensor() throws Exception { String yaml = Joiner.on("\n").join( "name: the-answer", "type: static-sensor", - "value: { type: int, value: 42 }"); + "sensor-type: int", + "value: 42"); Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(yaml).iterator().next(), null, null); Asserts.assertInstanceOf(ss, StaticSensor.class); } - + + @Test + public void testReadSensorWithExpectedSuperType() throws Exception { + String yaml = Joiner.on("\n").join( + "name: the-answer", + "type: static-sensor", + "sensor-type: int", + "value: 42"); + + Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(yaml).iterator().next(), null, EntityInitializer.class); + Asserts.assertInstanceOf(ss, StaticSensor.class); + } + + @Test + public void testReadSensorAsMapWithName() throws Exception { + String yaml = Joiner.on("\n").join( + "the-answer:", + " type: static-sensor", + " sensor-type: int", + " value: 42"); + + Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(yaml).iterator().next(), null, EntityInitializer.class); + Asserts.assertInstanceOf(ss, StaticSensor.class); + } + @Test public void testStaticSensorSingletonMap() throws Exception { String yaml = Joiner.on("\n").join( @@ -81,7 +108,8 @@ public void testStaticSensorSingletonMap() throws Exception { " brooklyn.initializers:", " the-answer:", " type: static-sensor", - " value: { type: int, value: 42 }"); + " sensor-type: int", + " value: 42"); checkStaticSensorInApp(yaml); } diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java index 0dad809aa1..935db529d3 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java @@ -33,6 +33,7 @@ import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultKey; +import org.apache.brooklyn.util.yoml.annotations.YomlTypeFromOtherField; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,14 +50,17 @@ * However when the source is another sensor, * consider using {@link Propagator} which listens for changes instead. */ @Alias("static-sensor") +@YomlRenameDefaultKey("name") public class StaticSensor extends AddSensor { private static final Logger log = LoggerFactory.getLogger(StaticSensor.class); + @Alias("value") @YomlTypeFromOtherField("targetType") public static final ConfigKey STATIC_VALUE = ConfigKeys.newConfigKey(Object.class, "static.value"); public static final ConfigKey TIMEOUT = ConfigKeys.newConfigKey( Duration.class, "static.timeout", "Duration to wait for the value to resolve", Duration.PRACTICALLY_FOREVER); + @YomlTypeFromOtherField("targetType") private final Object value; private final Duration timeout; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java index d53f0423e4..bf21a2de52 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.util.yoml; +import javax.annotation.Nullable; + import org.apache.brooklyn.util.guava.Maybe; public interface YomlTypeRegistry { @@ -33,7 +35,7 @@ public interface YomlTypeRegistry { * This is needed so that the right deserialization strategies can be applied for * things like collections and enums. */ - Maybe> getJavaTypeMaybe(String typeName); + Maybe> getJavaTypeMaybe(@Nullable String typeName); /** Return the best known type name to describe the given java instance */ String getTypeName(Object obj); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java index a9f8b8a186..56daa32a2d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java @@ -156,6 +156,7 @@ public void read() { YomlUtils.addDefaults(defaults, newYamlMap); context.setYamlObject(newYamlMap); + // TODO should the above apply to YamlKeysOnBlackboard? Or clear it? Or does this happen early enough that isn't an issue? context.phaseRestart(); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java index 3edb54ba3c..6774d7863d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java @@ -62,11 +62,14 @@ public void read() { if (hasJavaObject()) return; if (!isYamlMap()) return; - if (!getYamlMap().containsKey(oldKeyName)) return; - if (getYamlMap().containsKey(newKeyName)) return; + YamlKeysOnBlackboard ym = YamlKeysOnBlackboard.getOrCreate(blackboard, getYamlMap()); + Map ymj = ym.yamlKeysToReadToJava; - getYamlMap().put(newKeyName, getYamlMap().remove(oldKeyName)); - YomlUtils.addDefaults(defaults, getYamlMap()); + if (!ymj.containsKey(oldKeyName)) return; + if (ymj.containsKey(newKeyName)) return; + + ymj.put(newKeyName, ymj.remove(oldKeyName)); + YomlUtils.addDefaults(defaults, ymj); context.phaseRestart(); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java index a2bf3adbc6..05c741eae5 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java @@ -64,4 +64,5 @@ public void checkCompletion(YomlContext context) { public String toString() { return super.toString()+"("+yamlKeysToReadToJava+")"; } + } From db64b3306fff4db3a497b992a0d0b0e8b6a452cb Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 28 Sep 2016 12:52:27 +0100 Subject: [PATCH 55/77] tidy of yoml, being stricter and clearer --- .../internal/SerializersOnBlackboard.java | 31 +++++-- .../util/yoml/internal/YomlContext.java | 3 + .../util/yoml/internal/YomlConverter.java | 7 +- .../ConfigInMapUnderConfigSerializer.java | 2 +- .../serializers/ConvertFromPrimitive.java | 4 +- .../yoml/serializers/ConvertSingletonMap.java | 16 ++-- .../DefaultMapValuesSerializer.java | 4 +- .../serializers/FieldsInMapUnderFields.java | 17 ++-- .../InstantiateTypeFromRegistry.java | 18 ++-- ...antiateTypeFromRegistryUsingConfigMap.java | 12 ++- .../yoml/serializers/InstantiateTypeMap.java | 4 +- .../InstantiateTypeWorkerAbstract.java | 27 ++++-- .../serializers/ReadingTypeOnBlackboard.java | 4 + .../yoml/serializers/RenameKeySerializer.java | 39 ++++++--- .../serializers/TopLevelFieldSerializer.java | 32 ++++--- .../serializers/YamlKeysOnBlackboard.java | 60 ++++++++++++-- .../YomlSerializerComposition.java | 72 ++++++++++------ .../yoml/tests/TopLevelConfigKeysTests.java | 83 ++++++++++++++----- .../util/yoml/tests/YomlTestFixture.java | 12 ++- 19 files changed, 317 insertions(+), 130 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java index 502aa79518..40696e1bbe 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java @@ -24,6 +24,8 @@ import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; @@ -31,6 +33,8 @@ /** Stores serializers that should be used */ public class SerializersOnBlackboard { + private static final Logger log = LoggerFactory.getLogger(SerializersOnBlackboard.class); + private static String KEY = SerializersOnBlackboard.class.getName(); public static boolean isPresent(Map blackboard) { @@ -60,20 +64,25 @@ public static SerializersOnBlackboard getOrCreate(Map blackboard) private List expectedTypeSerializers = MutableList.of(); private List postSerializers = MutableList.of(); - public boolean addInstantiatedTypeSerializers(Iterable newInstantiatedTypeSerializers) { - return addNewSerializers(instantiatedTypeSerializers, newInstantiatedTypeSerializers); + public void addInstantiatedTypeSerializers(Iterable newInstantiatedTypeSerializers) { + addNewSerializers(instantiatedTypeSerializers, newInstantiatedTypeSerializers, "instantiated type"); } - public boolean addExpectedTypeSerializers(Iterable newExpectedTypeSerializers) { - return addNewSerializers(expectedTypeSerializers, newExpectedTypeSerializers); + public void addExpectedTypeSerializers(Iterable newExpectedTypeSerializers) { + addNewSerializers(expectedTypeSerializers, newExpectedTypeSerializers, "expected type"); } - public boolean addPostSerializers(List newPostSerializers) { - return addNewSerializers(postSerializers, newPostSerializers); + public void addPostSerializers(List newPostSerializers) { + addNewSerializers(postSerializers, newPostSerializers, "post"); } - protected static boolean addNewSerializers(List addTo, Iterable elementsToAddIfNotPresent) { + protected static void addNewSerializers(List addTo, Iterable elementsToAddIfNotPresent, String description) { MutableSet newOnes = MutableSet.copyOf(elementsToAddIfNotPresent); + int sizeBefore = newOnes.size(); + // removal isn't expected to work as hashCode and equals aren't typically implemented; + // callers should make sure only to add when needed newOnes.removeAll(addTo); - return addTo.addAll(newOnes); + if (log.isTraceEnabled()) + log.trace("Adding "+newOnes.size()+" serializers ("+sizeBefore+" initially requested) for "+description+" (had "+addTo.size()+"): "+newOnes); + addTo.addAll(newOnes); } public Iterable getSerializers() { @@ -85,5 +94,9 @@ public static boolean isAddedByTypeInstantiation(Map blackboard, if (sb!=null && sb.instantiatedTypeSerializers.contains(serializer)) return true; return false; } - + + public String toString() { + return super.toString()+"["+preSerializers.size()+"@pre,"+instantiatedTypeSerializers.size()+"@inst,"+ + expectedTypeSerializers.size()+"@exp,"+postSerializers.size()+"@post]"; + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContext.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContext.java index 45a75cdb84..576d0233b1 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContext.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContext.java @@ -75,6 +75,9 @@ public void setJavaObject(Object javaObject) { public Object getYamlObject() { return yamlObject; } + /** Sets the YAML object that will be returned from a write. + * In special cases of major YAML transformation this may also be used during read + * but most minor modifications should use blackboard objects. */ public void setYamlObject(Object yamlObject) { this.yamlObject = yamlObject; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java index e2256695e3..9de32bda0c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java @@ -23,9 +23,7 @@ import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.YomlRequirement; import org.apache.brooklyn.util.yoml.YomlSerializer; -import org.apache.brooklyn.util.yoml.serializers.JavaFieldsOnBlackboard; import org.apache.brooklyn.util.yoml.serializers.ReadingTypeOnBlackboard; -import org.apache.brooklyn.util.yoml.serializers.YamlKeysOnBlackboard; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -79,7 +77,10 @@ protected void loopOverSerializers(YomlContext context) { while (context.phaseStepAdvance() bb: blackboard.entrySet()) { + log.trace(" "+bb.getKey()+": "+bb.getValue()); + } } } YomlSerializer s = Iterables.get(serializers.getSerializers(), context.phaseCurrentStep()); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java index 9b43a8dd1f..178bb01b38 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java @@ -116,7 +116,7 @@ protected Map writePrepareGeneralMap() { String realType = config.getTypeRegistry().getTypeName(entry.getValue()); optionalType = realType; // for non-real, just write the pseudo-type-field at root - getYamlMap().put(tf, realType); + getOutputYamlMap().put(tf, realType); } else { Object rt = fib.configToWriteFromJava.get(tf); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java index c07d4228a6..e59b770825 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java @@ -76,11 +76,11 @@ public void write() { // don't run if we're only added after instantiating the type (because then we couldn't read back!) if (SerializersOnBlackboard.isAddedByTypeInstantiation(blackboard, ConvertFromPrimitive.this)) return; - Object value = getYamlMap().get(keyToInsert); + Object value = getOutputYamlMap().get(keyToInsert); if (value==null) return; if (!isJsonPrimitiveObject(value)) return; - Map yamlMap = MutableMap.copyOf(getYamlMap()); + Map yamlMap = MutableMap.copyOf(getOutputYamlMap()); yamlMap.remove(keyToInsert); YomlUtils.removeDefaults(defaults, yamlMap); if (!yamlMap.isEmpty()) return; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java index 56daa32a2d..747a8fb59b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java @@ -119,12 +119,12 @@ public void read() { if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return; if (!isYamlMap()) return; - if (getYamlMap().size()!=1) return; + if (getRawInputYamlMap().size()!=1) return; if (!enterModeRead(SingletonMapMode.NON_LIST)) return; - Object key = Iterables.getOnlyElement(getYamlMap().keySet()); - Object value = Iterables.getOnlyElement(getYamlMap().values()); + Object key = Iterables.getOnlyElement(getRawInputYamlMap().keySet()); + Object value = Iterables.getOnlyElement(getRawInputYamlMap().values()); // key should always be primitive if (!isJsonPrimitiveObject(key)) return; @@ -224,14 +224,14 @@ protected List readManipulatingInList(Collection list, SingletonMapMo protected void readManipulatingMapToList() { // convert from a map to a list; then manipulate in list List result = MutableList.of(); - for (Map.Entry entry: getYamlMap().entrySet()) { + for (Map.Entry entry: getRawInputYamlMap().entrySet()) { result.add(MutableMap.of(entry.getKey(), entry.getValue())); } result = readManipulatingInList(result, SingletonMapMode.LIST_AS_MAP); if (result==null) return; context.setYamlObject(result); - YamlKeysOnBlackboard.getOrCreate(blackboard, null).yamlKeysToReadToJava.clear(); + YamlKeysOnBlackboard.getOrCreate(blackboard, null).clear(); context.phaseAdvance(); } @@ -280,8 +280,8 @@ public void write() { } } - if (!getYamlMap().containsKey(keyForKey)) return; - Object newKey = getYamlMap().get(keyForKey); + if (!getOutputYamlMap().containsKey(keyForKey)) return; + Object newKey = getOutputYamlMap().get(keyForKey); if (!isJsonPrimitiveObject(newKey)) { // NB this is potentially irreversible - // e.g. if given say we want for { color: red, xxx: yyy } to write { red: { xxx: yyy } } @@ -291,7 +291,7 @@ public void write() { return; } - Map yamlMap = MutableMap.copyOf(getYamlMap()); + Map yamlMap = MutableMap.copyOf(getOutputYamlMap()); yamlMap.remove(keyForKey); YomlUtils.removeDefaults(defaults, yamlMap); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/DefaultMapValuesSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/DefaultMapValuesSerializer.java index e730e6fcfc..0d8d9f8877 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/DefaultMapValuesSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/DefaultMapValuesSerializer.java @@ -56,7 +56,7 @@ public void read() { if (hasJavaObject()) return; if (!isYamlMap()) return; - if (YomlUtils.addDefaults(defaults, getYamlMap())==0) return; + if (getYamlKeysOnBlackboardInitializedFromYamlMap().addDefaults(defaults)==0) return; context.phaseRestart(); } @@ -65,7 +65,7 @@ public void write() { if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return; if (!isYamlMap()) return; - if (YomlUtils.removeDefaults(defaults, getYamlMap())==0) return; + if (YomlUtils.removeDefaults(defaults, getOutputYamlMap())==0) return; context.phaseRestart(); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java index aca2688881..c9c3e1ecc2 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java @@ -105,7 +105,8 @@ public void read() { if (hasJavaObject() != shouldHaveJavaObject()) return; @SuppressWarnings("unchecked") - Map fields = peekFromYamlKeysOnBlackboard(getKeyNameForMapOfGeneralValues(), Map.class).orNull(); + // written by the individual TopLevelFieldSerializers + Map fields = peekFromYamlKeysOnBlackboardRemaining(getKeyNameForMapOfGeneralValues(), Map.class).orNull(); if (fields==null) return; MutableMap initialFields = MutableMap.copyOf(fields); @@ -136,7 +137,7 @@ public void read() { if (isTypeFieldReal) { typeO = initialFields.get(tf); } else { - typeO = peekFromYamlKeysOnBlackboard(tf, Object.class).orNull(); + typeO = peekFromYamlKeysOnBlackboardRemaining(tf, Object.class).orNull(); } if (typeO!=null && !(typeO instanceof String)) { throw new YomlException("Wrong type of value '"+typeO+"' inferred as type of '"+f+"' on "+getJavaObject(), context); @@ -148,7 +149,7 @@ public void read() { if (setKeyValueForJavaObjectOnRead(f, v, type)) { ((Map)fields).remove(f); if (type!=null && !isTypeFieldReal) { - removeFromYamlKeysOnBlackboard(tf); + removeFromYamlKeysOnBlackboardRemaining(tf); } changed = true; } @@ -156,7 +157,7 @@ public void read() { } if (((Map)fields).isEmpty()) { - removeFromYamlKeysOnBlackboard(getKeyNameForMapOfGeneralValues()); + removeFromYamlKeysOnBlackboardRemaining(getKeyNameForMapOfGeneralValues()); } if (changed) { // restart (there is normally nothing after this so could equally continue with rerun) @@ -167,11 +168,13 @@ public void read() { public void write() { if (!context.isPhase(StandardPhases.HANDLING_FIELDS)) return; if (!isYamlMap()) return; - if (getFromYamlMap(getKeyNameForMapOfGeneralValues(), Map.class).isPresent()) return; + + // should have been set by FieldsInMapUnderFields if we are to run + if (getFromOutputYamlMap(getKeyNameForMapOfGeneralValues(), Map.class).isPresent()) return; Map fields = writePrepareGeneralMap(); if (fields!=null && !fields.isEmpty()) { - setInYamlMap(getKeyNameForMapOfGeneralValues(), fields); + setInOutputYamlMap(getKeyNameForMapOfGeneralValues(), fields); // restart in case a serializer moves the `fields` map somewhere else context.phaseRestart(); } @@ -205,7 +208,7 @@ protected Map writePrepareGeneralMap() { String realType = config.getTypeRegistry().getTypeName(v.get()); fieldType = realType; // for non-real, just write the pseudo-type-field at root - getYamlMap().put(tf, realType); + getOutputYamlMap().put(tf, realType); } else { Maybe rt = Reflections.getFieldValueMaybe(getJavaObject(), tf); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java index 3c73e6c286..cc4b8eb192 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java @@ -33,6 +33,7 @@ protected YomlSerializerWorker newWorker() { } public class Worker extends InstantiateTypeWorkerAbstract { + public void read() { if (!canDoRead()) return; @@ -48,21 +49,22 @@ public void read() { } if (type==null) return; - if (addSerializersForDiscoveredRealType(type)) { - // added new serializers, need to restart phase - // in case another serializer wants to create it - context.phaseRestart(); - return; - } - + if (!readType(type)) return; if (isYamlMap()) { - removeFromYamlKeysOnBlackboard("type"); + removeFromYamlKeysOnBlackboardRemaining("type"); } } protected boolean readType(String type) { + if (addSerializersForDiscoveredRealType(type)) { + // added new serializers so restart phase + // in case another serializer wants to create it + context.phaseRestart(); + return false; + } + Maybe resultM = config.getTypeRegistry().newInstanceMaybe(type, Yoml.newInstance(config)); if (resultM.isAbsent()) { String message = "Unable to create type '"+type+"'"; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java index 28f2f6f349..f566d9ad75 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java @@ -141,6 +141,7 @@ protected boolean readType(String type) { // prepare blackboard, annotations, then do handling_config JavaFieldsOnBlackboard fib = JavaFieldsOnBlackboard.create(blackboard, keyNameForConfigWhenSerialized); + fib.typeNameFromReadToConstructJavaLater = type; fib.typeFromReadToConstructJavaLater = clazz; fib.fieldsFromReadToConstructJava = MutableMap.of(); @@ -154,10 +155,13 @@ protected boolean readType(String type) { } protected void addExtraTypeSerializers(Class clazz) { - if (inferByScanning) { - SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers( - TopLevelConfigKeySerializer.findConfigKeySerializers(keyNameForConfigWhenSerialized, clazz) ); - } + if (!inferByScanning) return; + + // prevent multiple additions + if (!putLabelOnBlackboard("extra-type-serializers="+clazz, true)) return; + + SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers( + TopLevelConfigKeySerializer.findConfigKeySerializers(keyNameForConfigWhenSerialized, clazz) ); } protected TopLevelFieldsBlackboard getTopLevelFieldsBlackboard() { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java index 77472913e4..3bf5f199c1 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java @@ -88,7 +88,7 @@ public void read() { // json is pass-through context.setJavaObject( context.getYamlObject() ); context.phaseAdvance(); - YamlKeysOnBlackboard.getOrCreate(blackboard, null).yamlKeysToReadToJava.clear(); + YamlKeysOnBlackboard.getOrCreate(blackboard, null).clear(); return; } @@ -191,7 +191,7 @@ public void read() { context.setJavaObject(jo); context.phaseAdvance(); - YamlKeysOnBlackboard.getOrCreate(blackboard, null).yamlKeysToReadToJava.clear(); + YamlKeysOnBlackboard.getOrCreate(blackboard, null).clear(); } private String getAlias(Class type) { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java index 3eb23751e5..40ad156574 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java @@ -18,11 +18,12 @@ */ package org.apache.brooklyn.util.yoml.serializers; +import java.util.Objects; + import javax.annotation.Nullable; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; import org.apache.brooklyn.util.yoml.internal.YomlContext; @@ -30,6 +31,13 @@ public abstract class InstantiateTypeWorkerAbstract extends YomlSerializerWorker { + /** puts the given label on the blackboard, in a namespace qualified to this class type, with the given value. + * returns whether the value is new on the blackboard, ie false if the value is already there. + */ + protected boolean putLabelOnBlackboard(String label, Object value) { + return !Objects.equals(value, blackboard.put(getClass().getName()+":"+label, value)); + } + protected boolean canDoRead() { if (!context.isPhase(YomlContext.StandardPhases.HANDLING_TYPE)) return false; if (hasJavaObject()) return false; @@ -51,7 +59,10 @@ protected boolean addSerializersForDiscoveredRealType(@Nullable String type) { if (type!=null) { // (if null, we were writing what was expected, and we'll have added from expected type serializers) if (!type.equals(context.getExpectedType())) { - return SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.getTypeRegistry().getSerializersForType(type)); + if (putLabelOnBlackboard("discovered-type="+type, true)) { + SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.getTypeRegistry().getSerializersForType(type)); + return true; + } } } return false; @@ -84,8 +95,8 @@ protected void storeWriteObjectAndAdvance(Object jo) { protected String readingTypeFromFieldOrExpected() { String type = null; if (isYamlMap()) { - YamlKeysOnBlackboard.getOrCreate(blackboard, getYamlMap()); - type = peekFromYamlKeysOnBlackboard("type", String.class).orNull(); + getYamlKeysOnBlackboardInitializedFromYamlMap(); + type = peekFromYamlKeysOnBlackboardRemaining("type", String.class).orNull(); } if (type==null) type = context.getExpectedType(); return type; @@ -95,14 +106,14 @@ protected Maybe readingValueFromTypeValueMap() { } protected Maybe readingValueFromTypeValueMap(Class requiredType) { if (!isYamlMap()) return Maybe.absent(); - if (YamlKeysOnBlackboard.peek(blackboard).yamlKeysToReadToJava.size()>2) return Maybe.absent(); - if (!MutableSet.of("type", "value").containsAll(YamlKeysOnBlackboard.peek(blackboard).yamlKeysToReadToJava.keySet())) { + if (YamlKeysOnBlackboard.peek(blackboard).size()>2) return Maybe.absent(); + if (!YamlKeysOnBlackboard.peek(blackboard).hasKeysLeft("type", "value")) { return Maybe.absent(); } - return peekFromYamlKeysOnBlackboard("value", requiredType); + return peekFromYamlKeysOnBlackboardRemaining("value", requiredType); } protected void removeTypeAndValueKeys() { - removeFromYamlKeysOnBlackboard("type", "value"); + removeFromYamlKeysOnBlackboardRemaining("type", "value"); } /** null type-name means we are writing the expected type */ diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java index 2775ebb52b..a7901c9fc2 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ReadingTypeOnBlackboard.java @@ -75,4 +75,8 @@ public void addNote(Throwable message) { errorNotes.add(message); } + @Override + public String toString() { + return super.toString()+"["+errorNotes.size()+" notes]"; + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java index 6774d7863d..a7baa0bc7f 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java @@ -20,6 +20,7 @@ import java.util.Map; +import org.apache.brooklyn.util.javalang.JavaClassNames; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey; @@ -27,11 +28,15 @@ import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultValue; import org.apache.brooklyn.util.yoml.internal.YomlContext; import org.apache.brooklyn.util.yoml.internal.YomlUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @YomlAllFieldsTopLevel @Alias("rename-key") public class RenameKeySerializer extends YomlSerializerComposition { + private static final Logger log = LoggerFactory.getLogger(RenameKeySerializer.class); + RenameKeySerializer() { } public RenameKeySerializer(YomlRenameKey ann) { @@ -54,7 +59,7 @@ protected YomlSerializerWorker newWorker() { @Alias("to") String newKeyName; Map defaults; - + public class Worker extends YomlSerializerWorker { public void read() { if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return; @@ -62,14 +67,17 @@ public void read() { if (hasJavaObject()) return; if (!isYamlMap()) return; - YamlKeysOnBlackboard ym = YamlKeysOnBlackboard.getOrCreate(blackboard, getYamlMap()); - Map ymj = ym.yamlKeysToReadToJava; + YamlKeysOnBlackboard ym = getYamlKeysOnBlackboardInitializedFromYamlMap(); + + if (!ym.hasKeysLeft(oldKeyName)) return; + if (ym.hadKeysEver(newKeyName)) return; - if (!ymj.containsKey(oldKeyName)) return; - if (ymj.containsKey(newKeyName)) return; + ym.putNewKey(newKeyName, ym.removeKey(oldKeyName).get()); + ym.addDefaults(defaults); - ymj.put(newKeyName, ymj.remove(oldKeyName)); - YomlUtils.addDefaults(defaults, ymj); + if (log.isTraceEnabled()) { + log.trace(this+" read, keys left now: "+ym); + } context.phaseRestart(); } @@ -79,16 +87,25 @@ public void write() { if (!isYamlMap()) return; // reverse order - if (!getYamlMap().containsKey(newKeyName)) return; - if (getYamlMap().containsKey(oldKeyName)) return; + if (!getOutputYamlMap().containsKey(newKeyName)) return; + if (getOutputYamlMap().containsKey(oldKeyName)) return; - getYamlMap().put(oldKeyName, getYamlMap().remove(newKeyName)); - YomlUtils.removeDefaults(defaults, getYamlMap()); + getOutputYamlMap().put(oldKeyName, getOutputYamlMap().remove(newKeyName)); + YomlUtils.removeDefaults(defaults, getOutputYamlMap()); + + if (log.isTraceEnabled()) { + log.trace(this+" write, output now: "+getOutputYamlMap()); + } context.phaseRestart(); } } + @Override + public String toString() { + return JavaClassNames.simpleClassName(getClass())+"["+oldKeyName+"->"+newKeyName+"]"; + } + @YomlAllFieldsTopLevel @Alias("rename-default-key") public static class RenameDefaultKey extends RenameKeySerializer { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java index dfea380be1..52c5ba852e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java @@ -30,6 +30,7 @@ import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; import org.apache.brooklyn.util.yoml.internal.YomlContext; @@ -58,6 +59,10 @@ public TopLevelFieldSerializer(Field f) { /** preferred constructor for dealing with shadowed fields using superclass.field naming convention */ public TopLevelFieldSerializer(String name, Field f) { fieldName = keyName = name; + // if field is called type insert _ to prevent confusion + if (keyName.matches("_*type")) { + keyName = "_"+keyName; + } Alias alias = f.getAnnotation(Alias.class); if (alias!=null) { @@ -183,22 +188,25 @@ public void read() { if (!readyForMainEvent()) return; if (!canDoRead()) return; if (!isYamlMap()) return; - if (!hasYamlKeysOnBlackboard()) return; + if (!hasYamlKeysOnBlackboardRemaining()) return; + + boolean fieldsCreated = false; @SuppressWarnings("unchecked") - Map fields = peekFromYamlKeysOnBlackboard(getKeyNameForMapOfGeneralValues(), Map.class).orNull(); + Map fields = peekFromYamlKeysOnBlackboardRemaining(getKeyNameForMapOfGeneralValues(), Map.class).orNull(); if (fields==null) { // create the fields if needed; FieldsInFieldsMap will remove (even if empty) + fieldsCreated = true; fields = MutableMap.of(); - YamlKeysOnBlackboard.getOrCreate(blackboard, null).yamlKeysToReadToJava.put(getKeyNameForMapOfGeneralValues(), fields); + getYamlKeysOnBlackboardInitializedFromYamlMap().putNewKey(getKeyNameForMapOfGeneralValues(), fields); } int keysMatched = 0; for (String aliasO: getKeyNameAndAliases()) { Set aliasMangles = getTopLevelFieldsBlackboard().isAliasesStrict(fieldName) ? - Collections.singleton(aliasO) : findAllKeyManglesYamlKeys(aliasO); + Collections.singleton(aliasO) : findAllYamlKeysOnBlackboardRemainingMangleMatching(aliasO); for (String alias: aliasMangles) { - Maybe value = peekFromYamlKeysOnBlackboard(alias, Object.class); + Maybe value = peekFromYamlKeysOnBlackboardRemaining(alias, Object.class); if (value.isAbsent()) continue; if (log.isTraceEnabled()) { log.trace(TopLevelFieldSerializer.this+": found "+alias+" for "+fieldName); @@ -212,7 +220,7 @@ public void read() { continue; } // value present, field not yet handled - removeFromYamlKeysOnBlackboard(alias); + removeFromYamlKeysOnBlackboardRemaining(alias); fields.put(fieldName, value.get()); keysMatched++; } @@ -225,7 +233,7 @@ public void read() { keysMatched++; } } - if (keysMatched>0) { + if (fieldsCreated || keysMatched>0) { // repeat this manipulating phase if we set any keys, so that remapping can apply getTopLevelFieldsBlackboard().setFieldDone(fieldName); context.phaseInsert(StandardPhases.MANIPULATING); @@ -237,7 +245,7 @@ public void write() { if (!isYamlMap()) return; @SuppressWarnings("unchecked") - Map fields = getFromYamlMap(getKeyNameForMapOfGeneralValues(), Map.class).orNull(); + Map fields = getFromOutputYamlMap(getKeyNameForMapOfGeneralValues(), Map.class).orNull(); /* * if fields is null either we are too early (not yet set by instantiate-type / FieldsInMapUnderFields) * or too late (already read in to java), so we bail -- this yaml key cannot be handled at this time @@ -269,14 +277,14 @@ public void write() { if (valueToSet.isPresent()) { getTopLevelFieldsBlackboard().setFieldDone(fieldName); - Object oldValue = getYamlMap().put(getPreferredKeyName(), valueToSet.get()); + Object oldValue = getOutputYamlMap().put(getPreferredKeyName(), valueToSet.get()); if (oldValue!=null && !oldValue.equals(valueToSet.get())) { - throw new IllegalStateException("Conflicting values for `"+getPreferredKeyName()+"`"); + throw new YomlException("Conflicting values for `"+getPreferredKeyName()+"`: "+oldValue+" / "+valueToSet.get(), context); } // and move the `fields` object to the end - getYamlMap().remove(getKeyNameForMapOfGeneralValues()); + getOutputYamlMap().remove(getKeyNameForMapOfGeneralValues()); if (!fields.isEmpty()) - getYamlMap().put(getKeyNameForMapOfGeneralValues(), fields); + getOutputYamlMap().put(getKeyNameForMapOfGeneralValues(), fields); // rerun this phase again, as we've changed it context.phaseInsert(StandardPhases.MANIPULATING); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java index 05c741eae5..857c57982d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java @@ -19,8 +19,10 @@ package org.apache.brooklyn.util.yoml.serializers; import java.util.Map; +import java.util.Set; import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.yoml.YomlException; import org.apache.brooklyn.util.yoml.YomlRequirement; import org.apache.brooklyn.util.yoml.internal.YomlContext; @@ -40,7 +42,8 @@ public static YamlKeysOnBlackboard getOrCreate(Map blackboard, Ma if (!isPresent(blackboard)) { YamlKeysOnBlackboard ykb = new YamlKeysOnBlackboard(); blackboard.put(KEY, ykb); - ykb.yamlKeysToReadToJava = MutableMap.copyOf(keys); + ykb.yamlAllKeysEverToReadToJava = MutableMap.copyOf(keys); + ykb.yamlKeysRemainingToReadToJava = MutableMap.copyOf(keys); } return peek(blackboard); } @@ -50,19 +53,66 @@ public static YamlKeysOnBlackboard create(Map blackboard) { return peek(blackboard); } - Map yamlKeysToReadToJava; + private Map yamlAllKeysEverToReadToJava; + private Map yamlKeysRemainingToReadToJava; @Override public void checkCompletion(YomlContext context) { - if (!yamlKeysToReadToJava.isEmpty()) { + if (!yamlKeysRemainingToReadToJava.isEmpty()) { // TODO limit toString to depth 2 ? - throw new YomlException("Incomplete read of YAML keys: "+yamlKeysToReadToJava, context); + throw new YomlException("Incomplete read of YAML keys: "+yamlKeysRemainingToReadToJava, context); } } @Override public String toString() { - return super.toString()+"("+yamlKeysToReadToJava+")"; + return super.toString()+"("+yamlKeysRemainingToReadToJava.size()+" ever; remaining="+yamlKeysRemainingToReadToJava+")"; } + public void clear() { + yamlKeysRemainingToReadToJava.clear(); + } + public boolean hasKeysLeft(String ...keys) { + for (String k: keys) { + if (!yamlKeysRemainingToReadToJava.containsKey(k)) return false; + } + return true; + } + public boolean hadKeysEver(String ...keys) { + for (String k: keys) { + if (!yamlAllKeysEverToReadToJava.containsKey(k)) return false; + } + return true; + } + public int size() { + return yamlKeysRemainingToReadToJava.size(); + } + public Maybe removeKey(String k) { + if (!yamlKeysRemainingToReadToJava.containsKey(k)) return Maybe.absent(); + return Maybe.of(yamlKeysRemainingToReadToJava.remove(k)); + } + public void putNewKey(String k, Object value) { + if (yamlKeysRemainingToReadToJava.put(k, value)!=null) throw new IllegalStateException("Already had value for "+k); + yamlAllKeysEverToReadToJava.put(k, value); + } + public int addDefaults(Map defaults) { + // like YomlUtils.addDefaults(...) but only adding if never seen + int count = 0; + if (defaults!=null) for (String key: defaults.keySet()) { + if (!yamlAllKeysEverToReadToJava.containsKey(key)) { + count++; + yamlAllKeysEverToReadToJava.put(key, defaults.get(key)); + yamlKeysRemainingToReadToJava.put(key, defaults.get(key)); + } + } + return count; + } + public Maybe peekKeyLeft(String k) { + if (!yamlKeysRemainingToReadToJava.containsKey(k)) return Maybe.absent(); + return Maybe.of(yamlKeysRemainingToReadToJava.get(k)); + } + public Set keysLeft() { + return yamlKeysRemainingToReadToJava.keySet(); + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java index 0bf72e775d..df3d8c9f2b 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java @@ -114,52 +114,76 @@ public Class getExpectedTypeJava() { public Object getJavaObject() { return context.getJavaObject(); } public Object getYamlObject() { return context.getYamlObject(); } - public boolean isYamlMap() { return context.getYamlObject() instanceof Map; } + /** Reports whether the YAML object is a map + * (or on read whether it has now been transformed to a map). */ + public boolean isYamlMap() { return getYamlObject() instanceof Map; } + + protected void assertReading() { assert context instanceof YomlContextForRead; } + protected void assertWriting() { assert context instanceof YomlContextForWrite; } + + @SuppressWarnings("unchecked") + public Map getOutputYamlMap() { + assertWriting(); + return (Map)context.getYamlObject(); + } + @SuppressWarnings("unchecked") - public Map getYamlMap() { return (Map)context.getYamlObject(); } - /** Returns the value of the given key if present in the map and of the given type. - * If the YAML is not a map, or the key is not present, or the type is different, this returns null. + public Map getRawInputYamlMap() { + assertReading(); + return (Map)context.getYamlObject(); + } + + /** Returns the value of the given key if it is present in the output map and is of the given type. + * If the YAML is not a map, or the key is not present, or the type is different, this returns an absent. *

- * See also {@link #peekFromYamlKeysOnBlackboard(String, Class)} which most read serializers should use. */ + * Read serializers or anything interested in the state of the map should use + * {@link #peekFromYamlKeysOnBlackboardRemaining(String, Class)} and other methods here or + * {@link YamlKeysOnBlackboard} directly. */ @SuppressWarnings("unchecked") - public Maybe getFromYamlMap(String key, Class type) { + public Maybe getFromOutputYamlMap(String key, Class type) { if (!isYamlMap()) return Maybe.absent("not a yaml map"); - if (!getYamlMap().containsKey(key)) return Maybe.absent("key `"+key+"` not in yaml map"); - Object v = getYamlMap().get(key); + if (!getOutputYamlMap().containsKey(key)) return Maybe.absent("key `"+key+"` not in yaml map"); + Object v = getOutputYamlMap().get(key); if (v==null) return Maybe.ofAllowingNull(null); if (!type.isInstance(v)) return Maybe.absent("value of key `"+key+"` is not a "+type); return Maybe.of((T) v); } - protected void setInYamlMap(String key, Object value) { - ((Map)getYamlMap()).put(key, value); + /** Writes directly to the yaml map which will be returned from a write. + * Read serializers should not use as per the comments on {@link #getFromOutputYamlMap(String, Class)}. */ + protected void setInOutputYamlMap(String key, Object value) { + ((Map)getOutputYamlMap()).put(key, value); + } + + /** creates a YKB instance. fails if the raw yaml input is not a map. */ + protected YamlKeysOnBlackboard getYamlKeysOnBlackboardInitializedFromYamlMap() { + return YamlKeysOnBlackboard.getOrCreate(blackboard, getRawInputYamlMap()); } + @SuppressWarnings("unchecked") - protected Maybe peekFromYamlKeysOnBlackboard(String key, Class expectedType) { + protected Maybe peekFromYamlKeysOnBlackboardRemaining(String key, Class expectedType) { YamlKeysOnBlackboard ykb = YamlKeysOnBlackboard.peek(blackboard); - if (ykb==null || ykb.yamlKeysToReadToJava==null || !ykb.yamlKeysToReadToJava.containsKey(key)) { - return Maybe.absent(); - } - Object v = ykb.yamlKeysToReadToJava.get(key); - if (expectedType!=null && !expectedType.isInstance(v)) return Maybe.absent(); - return Maybe.of((T)v); + if (ykb==null) return Maybe.absent(); + Maybe v = ykb.peekKeyLeft(key); + if (v.isAbsent()) return Maybe.absent(); + if (expectedType!=null && !expectedType.isInstance(v.get())) return Maybe.absent(); + return Maybe.of((T)v.get()); } - protected boolean hasYamlKeysOnBlackboard() { + protected boolean hasYamlKeysOnBlackboardRemaining() { YamlKeysOnBlackboard ykb = YamlKeysOnBlackboard.peek(blackboard); - if (ykb==null || ykb.yamlKeysToReadToJava==null || ykb.yamlKeysToReadToJava.isEmpty()) return false; - return true; + return (ykb!=null && ykb.size()>0); } - protected void removeFromYamlKeysOnBlackboard(String ...keys) { + protected void removeFromYamlKeysOnBlackboardRemaining(String ...keys) { YamlKeysOnBlackboard ykb = YamlKeysOnBlackboard.peek(blackboard); for (String key: keys) { - ykb.yamlKeysToReadToJava.remove(key); + ykb.removeKey(key); } } /** looks for all keys in {@link YamlKeysOnBlackboard} which can be mangled/ignore-case * to match the given key */ - protected Set findAllKeyManglesYamlKeys(String targetKey) { + protected Set findAllYamlKeysOnBlackboardRemainingMangleMatching(String targetKey) { Set result = MutableSet.of(); YamlKeysOnBlackboard ykb = YamlKeysOnBlackboard.peek(blackboard); - for (Object k: ykb.yamlKeysToReadToJava.keySet()) { + for (Object k: ykb.keysLeft()) { if (k instanceof String && YomlUtils.mangleable(targetKey, (String)k)) { result.add((String)k); } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java index 3b08b41c89..ed26f46758 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java @@ -63,6 +63,13 @@ public MockConfigKey(Class type, String name) { this.typeToken = TypeToken.of(type); } + public MockConfigKey(Class type, String name, T defaultValue) { + this.name = name; + this.type = type; + this.typeToken = TypeToken.of(type); + this.defaultValue = defaultValue; + } + @SuppressWarnings("unchecked") public MockConfigKey(TypeToken typeToken, String name) { this.name = name; @@ -118,6 +125,17 @@ public void testRead() { Asserts.assertInstanceOf(y.lastReadResult, S1.class); Asserts.assertEquals(((S1)y.lastReadResult).keys.get("k1"), "foo"); } + + @Test + public void testReadExpectingType() { + YomlTestFixture y = YomlTestFixture.newInstance() + .addTypeWithAnnotationsAndConfigFieldsIgnoringInheritance("s1", S1.class, MutableMap.of("keys", "config")); + + y.read("{ k1: foo }", "s1"); + + Asserts.assertInstanceOf(y.lastReadResult, S1.class); + Asserts.assertEquals(((S1)y.lastReadResult).keys.get("k1"), "foo"); + } @Test public void testWrite() { @@ -205,19 +223,19 @@ public void testReadWriteExtraField() { @YomlConfigMapConstructor("") @YomlAllFieldsTopLevel - static class KF { + static class KeyAsField { @Alias("k") static ConfigKey K1 = new MockConfigKey(String.class, "key1"); final String key1Field; transient final Map keysSuppliedToConstructorForTestAssertions; - KF(Map keys) { + KeyAsField(Map keys) { key1Field = (String) keys.get(K1.getName()); keysSuppliedToConstructorForTestAssertions = keys; } @Override public boolean equals(Object obj) { - return (obj instanceof KF) && ((KF)obj).key1Field.equals(key1Field); + return (obj instanceof KeyAsField) && Objects.equals( ((KeyAsField)obj).key1Field, key1Field); } @Override public int hashCode() { @@ -229,23 +247,23 @@ public String toString() { } } - final KF KF_FOO = new KF(MutableMap.of("key1", "foo")); + final KeyAsField KF_FOO = new KeyAsField(MutableMap.of("key1", "foo")); @Test public void testNoConfigMapFieldCanReadKeyToMapConstructor() { - YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf", KF.class); + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf", KeyAsField.class); y.read("{ key1: foo }", "kf").assertResult(KF_FOO); - Assert.assertEquals(((KF)y.lastReadResult).keysSuppliedToConstructorForTestAssertions, KF_FOO.keysSuppliedToConstructorForTestAssertions); + Assert.assertEquals(((KeyAsField)y.lastReadResult).keysSuppliedToConstructorForTestAssertions, KF_FOO.keysSuppliedToConstructorForTestAssertions); } @Test public void testNoConfigMapFieldCanReadKeyAliasToMapConstructor() { - YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf", KF.class); + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf", KeyAsField.class); y.read("{ k: foo }", "kf").assertResult(KF_FOO); - Assert.assertEquals(((KF)y.lastReadResult).keysSuppliedToConstructorForTestAssertions, KF_FOO.keysSuppliedToConstructorForTestAssertions); + Assert.assertEquals(((KeyAsField)y.lastReadResult).keysSuppliedToConstructorForTestAssertions, KF_FOO.keysSuppliedToConstructorForTestAssertions); } @Test public void testStaticFieldNameNotRelevant() { - YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf", KF.class); + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf", KeyAsField.class); try { y.read("{ k1: foo }", "kf"); Asserts.shouldHaveFailedPreviously("Got "+y.lastReadResult); @@ -256,36 +274,36 @@ public void testStaticFieldNameNotRelevant() { @Test public void testNoConfigMapFieldCanWriteAndReadToFieldDirectly() { - YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf", KF.class); + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf", KeyAsField.class); // writing must write to the field directly because it is not defined how to reverse map to the config key y.writing(KF_FOO).reading("{ type: kf, key1Field: foo }").doReadWriteAssertingJsonMatch(); // nothing passed to constructor, but constructor is invoked - Assert.assertEquals(((KF)y.lastReadResult).keysSuppliedToConstructorForTestAssertions, MutableMap.of(), - "Constructor given unexpectedly non-empty map: "+((KF)y.lastReadResult).keysSuppliedToConstructorForTestAssertions); + Assert.assertEquals(((KeyAsField)y.lastReadResult).keysSuppliedToConstructorForTestAssertions, MutableMap.of(), + "Constructor given unexpectedly non-empty map: "+((KeyAsField)y.lastReadResult).keysSuppliedToConstructorForTestAssertions); } - static class KF2 extends KF { - KF2(Map keys) { super(keys); } + static class KfA2C extends KeyAsField { + KfA2C(Map keys) { super(keys); } @Alias("key1Field") // alias same name as field means it *is* passed to constructor static ConfigKey K1 = new MockConfigKey(String.class, "key1"); } - private static final KF2 KF2_FOO = new KF2(MutableMap.of("key1", "foo")); + private static final KfA2C KF_A2C_FOO = new KfA2C(MutableMap.of("key1", "foo")); @Test public void testConfigKeyTopLevelInherited() { - YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf2", KF2.class); - y.read("{ key1: foo }", "kf2").assertResult(KF2_FOO); + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf-a2c", KfA2C.class); + y.read("{ key1: foo }", "kf-a2c").assertResult(KF_A2C_FOO); } @Test public void testConfigKeyOverrideHidesParentAlias() { // this could be weakened to be allowed (but aliases at types must not be, for obvious reasons!) - YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf2", KF2.class); + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf-a2c", KfA2C.class); try { - y.read("{ k: foo }", "kf2"); + y.read("{ k: foo }", "kf-a2c"); Asserts.shouldHaveFailedPreviously("Got "+y.lastReadResult); } catch (Exception e) { Asserts.expectedFailureContainsIgnoreCase(e, "k", "foo"); @@ -294,11 +312,32 @@ public void testConfigKeyOverrideHidesParentAlias() { @Test public void testNoConfigMapFieldWillPreferConstructorIfKeyForFieldCanBeFound() { - YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf2", KF2.class); + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("kf-a2c", KfA2C.class); // writing must write to the field because it is not defined how to reverse map to the config key - y.writing(KF2_FOO).reading("{ type: kf2, key1Field: foo }").doReadWriteAssertingJsonMatch(); + y.writing(KF_A2C_FOO).reading("{ type: kf-a2c, key1Field: foo }").doReadWriteAssertingJsonMatch(); // is passed to constructor - Assert.assertEquals(((KF)y.lastReadResult).keysSuppliedToConstructorForTestAssertions, KF_FOO.keysSuppliedToConstructorForTestAssertions); + Assert.assertEquals(((KeyAsField)y.lastReadResult).keysSuppliedToConstructorForTestAssertions, KF_FOO.keysSuppliedToConstructorForTestAssertions); + } + + + static class SDefault extends S3 { + transient final Map keysSuppliedToConstructorForTestAssertions; + + SDefault(Map keys) { + super(keys); + keysSuppliedToConstructorForTestAssertions = keys; + } + + static ConfigKey KD = new MockConfigKey(String.class, "keyD", "default"); + } + + @Test + public void testConfigKeyDefaultsReadButNotWritten() { + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("s-default", SDefault.class); + y.reading("{}", "s-default").writing(new SDefault(MutableMap.of()), "s-default") + .doReadWriteAssertingJsonMatch(); + + Asserts.assertSize( ((SDefault)y.lastReadResult).keysSuppliedToConstructorForTestAssertions.keySet(), 0 ); } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java index 64f05c59fa..e7c2496385 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java @@ -38,14 +38,20 @@ public class YomlTestFixture { public static YomlTestFixture newInstance() { return new YomlTestFixture(); } public static YomlTestFixture newInstance(YomlConfig config) { return new YomlTestFixture(config); } - final MockYomlTypeRegistry tr = new MockYomlTypeRegistry(); + final MockYomlTypeRegistry tr; final Yoml y; public YomlTestFixture() { this(YomlConfig.Builder.builder().serializersPostAddDefaults().build()); } public YomlTestFixture(YomlConfig config) { - y = Yoml.newInstance(YomlConfig.Builder.builder(config).typeRegistry(tr).build()); + if (config.getTypeRegistry()==null) { + tr = new MockYomlTypeRegistry(); + config = YomlConfig.Builder.builder(config).typeRegistry(tr).build(); + } else { + tr = null; + } + y = Yoml.newInstance(config); } Object writeObject; @@ -130,6 +136,8 @@ public void assertLastWriteIgnoringQuotes(String expected, String message) { public void assertLastWriteIgnoringQuotes(String expected) { assertEqualish(Jsonya.newInstance().add(getLastWriteResult()).toString(), expected, "mismatch"); } + + // methods below require using the default registry, will NPE otherwise public YomlTestFixture addType(String name, Class type) { tr.put(name, type); return this; } public YomlTestFixture addType(String name, Class type, List serializers) { tr.put(name, type, serializers); return this; } From f4c41ca7c9f59301866692db1b604e62e0b6dbee Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 28 Sep 2016 15:13:22 +0100 Subject: [PATCH 56/77] improve yoml coercion to/from primitive, and use that for duration also tidy duration "practically_forever" name, now "a very long time" --- .../apache/brooklyn/util/time/Duration.java | 21 +++++- .../yoml/annotations/YomlAsPrimitive.java | 46 +++++++++++++ .../yoml/annotations/YomlFromPrimitive.java | 5 +- .../serializers/InstantiateTypePrimitive.java | 63 ++++++++++++++++-- .../brooklyn/util/time/DurationTest.java | 18 +++++- .../yoml/examples/real/DurationYomlTests.java | 64 +++++++++++++++++++ .../util/yoml/tests/YomlTestFixture.java | 27 +++++++- 7 files changed, 226 insertions(+), 18 deletions(-) create mode 100644 utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAsPrimitive.java create mode 100644 utils/common/src/test/java/org/apache/brooklyn/util/yoml/examples/real/DurationYomlTests.java diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/time/Duration.java b/utils/common/src/main/java/org/apache/brooklyn/util/time/Duration.java index 76217a0eb4..cb2d49347a 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/time/Duration.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/time/Duration.java @@ -29,12 +29,18 @@ import javax.annotation.Nullable; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yoml.annotations.Alias; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlAsPrimitive; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; /** simple class determines a length of time */ +@YomlAllFieldsTopLevel +@YomlAsPrimitive +@Alias("duration") public class Duration implements Comparable, Serializable { private static final long serialVersionUID = -2303909964519279617L; @@ -53,9 +59,13 @@ public class Duration implements Comparable, Serializable { /** longest supported duration, 2^{63}-1 nanoseconds, approx ten billion seconds, or 300 years */ public static final Duration PRACTICALLY_FOREVER = of(Long.MAX_VALUE, TimeUnit.NANOSECONDS); + public static final String PRACTICALLY_FOREVER_NAME = "a very long time"; private final long nanos; + @SuppressWarnings("unused") // for yoml creation + private Duration() { nanos=-1; } + public Duration(long value, TimeUnit unit) { if (value != 0) { Preconditions.checkNotNull(unit, "Cannot accept null timeunit (unless value is 0)"); @@ -72,6 +82,7 @@ public int compareTo(Duration o) { @Override public String toString() { + if (nanos==PRACTICALLY_FOREVER.nanos) return PRACTICALLY_FOREVER_NAME; return Time.makeTimeStringExact(this); } @@ -143,9 +154,13 @@ public static Duration parse(String textualDescription) { if (Strings.isBlank(textualDescription)) return null; if ("null".equalsIgnoreCase(textualDescription)) return null; - if ("forever".equalsIgnoreCase(textualDescription)) return Duration.PRACTICALLY_FOREVER; - if ("practicallyforever".equalsIgnoreCase(textualDescription)) return Duration.PRACTICALLY_FOREVER; - if ("practically_forever".equalsIgnoreCase(textualDescription)) return Duration.PRACTICALLY_FOREVER; + if (!textualDescription.matches(".*[0-9].*")) { + // look for text matches if there are no numbers in it + String t = textualDescription.toLowerCase(); + if (PRACTICALLY_FOREVER_NAME.equals(t)) return Duration.PRACTICALLY_FOREVER; + if (t.matches("(practically[-_\\s]*)?forever")) return Duration.PRACTICALLY_FOREVER; + if (t.matches("(a[-_\\s]+)?(very[-_,\\s]*)*(long[-_,\\s]*)+(time)?")) return Duration.PRACTICALLY_FOREVER; + } return new Duration((long) Time.parseElapsedTimeAsDouble(textualDescription), TimeUnit.MILLISECONDS); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAsPrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAsPrimitive.java new file mode 100644 index 0000000000..8baf9f7b01 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlAsPrimitive.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.annotations; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.apache.brooklyn.util.yoml.serializers.ConvertFromPrimitive; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypePrimitive; + +/** + * Indicates that a class is potentially coercible to and from a primitive. + * YOML will always try to coerce from a primitive if appropriate, + * but this indicates that it should try various strategies to coerce to a primitive. + *

+ * See {@link InstantiateTypePrimitive}. + */ +@Retention(RUNTIME) +@Target({ TYPE }) +public @interface YomlAsPrimitive { + + /** The key to insert for the given value */ + String keyToInsert() default ConvertFromPrimitive.DEFAULT_DEFAULT_KEY; + + DefaultKeyValue[] defaults() default {}; + +} diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java index 25cac5ddb4..4d9db9246f 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java @@ -29,9 +29,10 @@ /** * Indicates that a class can be yoml-serialized as a primitive - * if there is just a single key to set. + * reflecting a single field in the object which will take the primitive value. *

- * Default value .value is intended for use by other serializers. + * If no {@link #keyToInsert()} is supplied the value is set under the key + * .value for use by other serializers. *

* See {@link ConvertFromPrimitive}. */ diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java index 9aa6dea015..a222dc20d9 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java @@ -20,6 +20,7 @@ import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.yoml.annotations.YomlAsPrimitive; import org.apache.brooklyn.util.yoml.internal.YomlContext; import org.apache.brooklyn.util.yoml.internal.YomlUtils; @@ -69,11 +70,14 @@ public void read() { if (typeName==null) return; expectedJavaType = config.getTypeRegistry().getJavaTypeMaybe(typeName).orNull(); if (expectedJavaType==null) expectedJavaType = getSpecialKnownTypeName(typeName); - if (!isJsonPrimitiveType(expectedJavaType) && !isJsonMarkerType(typeName)) return; + // could restrict read coercion to basic types as follows, but no harm in trying to coerce if it's + // a value map +// if (!isJsonPrimitiveType(expectedJavaType) && !isJsonMarkerType(typeName)) return; value = readingValueFromTypeValueMap(); if (value.isAbsent()) return; - if (tryCoerceAndNoteError(value.get(), expectedJavaType).isAbsent()) return; + value = tryCoerceAndNoteError(value.get(), expectedJavaType); + if (value.isAbsent()) return; removeTypeAndValueKeys(); } } @@ -84,27 +88,72 @@ public void read() { public void write() { if (!canDoWrite()) return; - if (!YomlUtils.JsonMarker.isPureJson(getJavaObject())) return; + Object jIn = getJavaObject(); + Object jOut = null; + + if (jIn==null) return; + + if (!YomlUtils.JsonMarker.isPureJson(jIn)) { + // not json, but can we coerce to json? + if (jIn.getClass().getAnnotation(YomlAsPrimitive.class)!=null) { + Object jo; + if (jOut==null) { + jo = config.getCoercer().tryCoerce(jIn, String.class).orNull(); + if (isReverseCoercible(jo, jIn)) jOut = jo; + } + if (jOut==null) { + jo = jIn.toString(); + if (isReverseCoercible(jo, jIn)) jOut = jo; + } + // could convert to other primitives eg int + // but no good use case so far + } + if (jOut!=null) { + // check whether we'll be able to read it back without additional type information + Maybe typeNeededCheck = config.getCoercer().tryCoerce(jOut, getExpectedTypeJava()); + if (typeNeededCheck.isPresent() && jIn.equals(typeNeededCheck.get())) { + // expected type is good enough to coerce, so write without type info + storeWriteObjectAndAdvance(jOut); + return; + + } else { + // fall through to below and write as type/value map + } + } + } + + if (jOut==null) jOut = jIn; + if (!YomlUtils.JsonMarker.isPureJson(jOut)) { + // it input is not pure json at this point, we don't apply + return; + } if (isJsonPrimitiveType(getExpectedTypeJava()) || isJsonMarkerTypeExpected()) { - storeWriteObjectAndAdvance(getJavaObject()); + // store it as pure primitive + storeWriteObjectAndAdvance(jOut); return; } // not expecting a primitive/json; bail out if it's not a primitive (map/list might decide to write `json` as the type) - if (!isJsonPrimitiveObject(getJavaObject())) return; + if (!isJsonPrimitiveObject(jOut)) return; - String typeName = config.getTypeRegistry().getTypeName(getJavaObject()); + String typeName = config.getTypeRegistry().getTypeName(jIn); if (addSerializersForDiscoveredRealType(typeName)) { // if new serializers, bail out and we'll re-run context.phaseRestart(); return; } - MutableMap map = writingMapWithTypeAndLiteralValue(typeName, getJavaObject()); + MutableMap map = writingMapWithTypeAndLiteralValue(typeName, jOut); context.phaseInsert(YomlContext.StandardPhases.MANIPULATING); storeWriteObjectAndAdvance(map); } + + private boolean isReverseCoercible(Object input, Object target) { + Maybe coerced = config.getCoercer().tryCoerce(input, target.getClass()); + if (coerced.isAbsent()) return false; + return (target.equals(coerced.get())); + } } } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/time/DurationTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/time/DurationTest.java index 1296f46d1c..0030b3ef9b 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/time/DurationTest.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/time/DurationTest.java @@ -20,7 +20,6 @@ import java.util.concurrent.TimeUnit; -import org.apache.brooklyn.util.time.Duration; import org.testng.Assert; import org.testng.annotations.Test; @@ -41,8 +40,8 @@ public void testAdd() { public void testStatics() { Assert.assertEquals((((4*60+3)*60)+30)*1000, - Duration.ONE_MINUTE.times(3). - add(Duration.ONE_HOUR.times(4)). + Duration.ONE_MINUTE.multiply(3). + add(Duration.ONE_HOUR.multiply(4)). add(Duration.THIRTY_SECONDS). toMilliseconds()); } @@ -105,4 +104,17 @@ public void testComparison() { Assert.assertFalse(Duration.seconds(-1).isLongerThan(Duration.ZERO)); } + public void testForevers() { + assertForever("forever"); + assertForever("practically-forever"); + assertForever("a very long time"); + assertForever("a very, very long, long time"); + assertForever("longtime"); + assertForever("very-long"); + } + + protected void assertForever(String name) { + Assert.assertEquals(Duration.PRACTICALLY_FOREVER, Duration.of(name)); + } + } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/examples/real/DurationYomlTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/examples/real/DurationYomlTests.java new file mode 100644 index 0000000000..12e4cb3b7d --- /dev/null +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/examples/real/DurationYomlTests.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.yoml.examples.real; + +import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.yoml.tests.YomlTestFixture; +import org.testng.annotations.Test; + +public class DurationYomlTests { + + YomlTestFixture y = YomlTestFixture.newInstance(). + addTypeWithAnnotations(Duration.class); + + @Test + public void testReadBasic() { + y.read("{ nanos: 1000000000 }", "duration") + .assertResult(Duration.ONE_SECOND); + } + + @Test + public void testReadNice() { + y.read("1s", "duration") + .assertResult(Duration.ONE_SECOND); + } + + @Test + public void testOneSecond() { + System.out.println("x:"+Duration.ONE_SECOND.toString()); + y.write(Duration.ONE_SECOND, "duration") + .readLastWrite().assertLastsMatch() + .assertLastWriteIgnoringQuotes("1s"); + } + + @Test + public void testZero() { + y.write(Duration.ZERO, "duration") + .readLastWrite().assertLastsMatch() + .assertLastWriteIgnoringQuotes("0ms"); + } + + @Test + public void testForever() { + y.write(Duration.PRACTICALLY_FOREVER, "duration") + .readLastWrite().assertLastsMatch() + .assertLastWriteIgnoringQuotes(Duration.PRACTICALLY_FOREVER_NAME); + } + +} diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java index e7c2496385..ad4defc423 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/YomlTestFixture.java @@ -56,10 +56,12 @@ public YomlTestFixture(YomlConfig config) { Object writeObject; String writeObjectExpectedType; + String lastWriteExpectedType; Object lastWriteResult; String readObject; String readObjectExpectedType; Object lastReadResult; + String lastReadExpectedType; Object lastResult; public YomlTestFixture writing(Object objectToWrite) { @@ -84,21 +86,31 @@ public YomlTestFixture write(Object objectToWrite) { } public YomlTestFixture write(Object objectToWrite, String expectedType) { writing(objectToWrite, expectedType); + lastWriteExpectedType = expectedType; lastWriteResult = y.write(objectToWrite, expectedType); lastResult = lastWriteResult; return this; } public YomlTestFixture read(String objectToRead, String expectedType) { reading(objectToRead, expectedType); + lastReadExpectedType = expectedType; lastReadResult = y.read(objectToRead, expectedType); lastResult = lastReadResult; return this; } + public YomlTestFixture writeLastRead() { + write(lastReadResult, lastReadExpectedType); + return this; + } + public YomlTestFixture readLastWrite() { + read(asJson(lastWriteResult), lastWriteExpectedType); + return this; + } public YomlTestFixture assertResult(Object expectation) { if (expectation instanceof String) { if (lastResult instanceof Map || lastResult instanceof Collection) { - assertEqualish(Jsonya.newInstance().add(lastResult).toString(), expectation, "Result as JSON string does not match expectation"); + assertEqualish(asJson(lastResult), expectation, "Result as JSON string does not match expectation"); } else { assertEqualish(Strings.toString(lastResult), expectation, "Result toString does not match expectation"); } @@ -107,10 +119,19 @@ public YomlTestFixture assertResult(Object expectation) { } return this; } + + static String asJson(Object o) { + return Jsonya.newInstance().add(o).toString(); + } + public YomlTestFixture doReadWriteAssertingJsonMatch() { read(readObject, readObjectExpectedType); write(writeObject, writeObjectExpectedType); - assertEqualish(Jsonya.newInstance().add(lastWriteResult).toString(), readObject, "Write output should match read input"); + return assertLastsMatch(); + } + + public YomlTestFixture assertLastsMatch() { + assertEqualish(asJson(lastWriteResult), readObject, "Write output should match read input"); assertEqualish(lastReadResult, writeObject, "Read output should match write input"); return this; } @@ -134,7 +155,7 @@ public void assertLastWriteIgnoringQuotes(String expected, String message) { assertEqualish(Jsonya.newInstance().add(getLastWriteResult()).toString(), expected, message); } public void assertLastWriteIgnoringQuotes(String expected) { - assertEqualish(Jsonya.newInstance().add(getLastWriteResult()).toString(), expected, "mismatch"); + assertEqualish(Jsonya.newInstance().add(getLastWriteResult()).toString(), expected, "mismatch on last write"); } // methods below require using the default registry, will NPE otherwise From bb03422ef24eb0f2b7dc1e433335b04a7a1e54cb Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 29 Sep 2016 13:39:59 +0100 Subject: [PATCH 57/77] prevent primitive coercion from applying to items with generics or json complex list/map --- .../serializers/InstantiateTypePrimitive.java | 4 +- .../YomlSerializerComposition.java | 49 ++++++++++++------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java index a222dc20d9..57a0712c09 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java @@ -71,8 +71,8 @@ public void read() { expectedJavaType = config.getTypeRegistry().getJavaTypeMaybe(typeName).orNull(); if (expectedJavaType==null) expectedJavaType = getSpecialKnownTypeName(typeName); // could restrict read coercion to basic types as follows, but no harm in trying to coerce if it's - // a value map -// if (!isJsonPrimitiveType(expectedJavaType) && !isJsonMarkerType(typeName)) return; + // a value map, unless the target is a special json which will be handled by another serializer + if (isJsonComplexType(expectedJavaType) || isGeneric(typeName)) return; value = readingValueFromTypeValueMap(); if (value.isAbsent()) return; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java index df3d8c9f2b..3fd7cf7a34 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java @@ -81,7 +81,37 @@ protected Class getSpecialKnownTypeName(String typename) { if (YomlUtils.TYPE_MAP.equals(typename)) return Map.class; return Boxing.boxedType( Boxing.getPrimitiveType(typename).orNull() ); } + protected boolean isJsonComplexType(Class t) { + if (t==null) return false; + // or could be equals, used as response of the above + if (Map.class.isAssignableFrom(t)) return true; + if (Set.class.isAssignableFrom(t)) return true; + if (List.class.isAssignableFrom(t)) return true; + return false; + } + protected boolean isGeneric(String typename) { + if (typename==null) return false; + return typename.contains("<"); + } + + /** true iff the object is a string or java primitive type */ + protected boolean isJsonPrimitiveObject(Object o) { + if (o==null) return true; + if (o instanceof String) return true; + if (Boxing.isPrimitiveOrBoxedObject(o)) return true; + return false; + } + + /** true iff the object is a map or collection (not recursing; for that see {@link #isJsonPureObject(Object)} */ + protected boolean isJsonComplexObject(Object o) { + return (o instanceof Map || o instanceof Collection); + } + /** true iff the object is a primitive type or a map or collection of pure objects; + * see {@link JsonMarker#isPureJson(Object)} (which this simply proxies for convenience) */ + protected boolean isJsonPureObject(Object o) { + return YomlUtils.JsonMarker.isPureJson(o); + } private void initRead(YomlContextForRead context, YomlConverter converter) { if (this.context!=null) throw new IllegalStateException("Already initialized, for "+context); @@ -191,25 +221,6 @@ protected Set findAllYamlKeysOnBlackboardRemainingMangleMatching(String return result; } - /** true iff the object is a string or java primitive type */ - protected boolean isJsonPrimitiveObject(Object o) { - if (o==null) return true; - if (o instanceof String) return true; - if (Boxing.isPrimitiveOrBoxedObject(o)) return true; - return false; - } - - /** true iff the object is a map or collection (not recursing; for that see {@link #isJsonPureObject(Object)} */ - protected boolean isJsonComplexObject(Object o) { - return (o instanceof Map || o instanceof Collection); - } - - /** true iff the object is a primitive type or a map or collection of pure objects; - * see {@link JsonMarker#isPureJson(Object)} (which this simply proxies for convenience) */ - protected boolean isJsonPureObject(Object o) { - return YomlUtils.JsonMarker.isPureJson(o); - } - public abstract void read(); public abstract void write(); } From 2d20a52648ed5ab2bd47a7c40f290bae6146848d Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 28 Sep 2016 15:13:56 +0100 Subject: [PATCH 58/77] improve output of yoml when going via brooklyn registry tests passing for writing static sensor --- .../api/entity/EntityInitializer.java | 2 + .../camp/yoml/BrooklynYomlTypeRegistry.java | 27 +++++- .../camp/yoml/YomlTypePlanTransformer.java | 26 ++++- .../ConfigKeyConstructionInstructions.java | 18 ++++ .../camp/yoml/BrooklynYomlTestFixture.java | 4 + ...omlTypeRegistryEntityInitializersTest.java | 95 +++++++++++++------ .../brooklyn/core/effector/AddSensor.java | 6 +- 7 files changed, 136 insertions(+), 42 deletions(-) diff --git a/api/src/main/java/org/apache/brooklyn/api/entity/EntityInitializer.java b/api/src/main/java/org/apache/brooklyn/api/entity/EntityInitializer.java index fd8ab234b5..c138971a6c 100644 --- a/api/src/main/java/org/apache/brooklyn/api/entity/EntityInitializer.java +++ b/api/src/main/java/org/apache/brooklyn/api/entity/EntityInitializer.java @@ -23,6 +23,7 @@ import org.apache.brooklyn.api.objs.EntityAdjunct; import org.apache.brooklyn.api.policy.Policy; import org.apache.brooklyn.api.sensor.Feed; +import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.YomlSingletonMap; /** @@ -41,6 +42,7 @@ * which will be attached during rebind. **/ @YomlSingletonMap(keyForPrimitiveValue="type") +@Alias("entity-initializer") public interface EntityInitializer { /** Applies initialization logic to a just-built entity. diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java index fb6e99e19e..429fdcf065 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.brooklyn.api.mgmt.ManagementContext; @@ -55,7 +56,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.reflect.TypeToken; @@ -123,7 +123,7 @@ public class BrooklynYomlTypeRegistry implements YomlTypeRegistry { */ private RegisteredTypeLoadingContext defaultLoadingContext; - public BrooklynYomlTypeRegistry(ManagementContext mgmt, RegisteredTypeLoadingContext defaultLoadingContext) { + public BrooklynYomlTypeRegistry(@Nonnull ManagementContext mgmt, @Nonnull RegisteredTypeLoadingContext defaultLoadingContext) { this.mgmt = mgmt; this.defaultLoadingContext = defaultLoadingContext; @@ -324,13 +324,30 @@ public String getTypeName(Object obj) { return getTypeNameOfClass(obj.getClass()); } + public static Set WARNS = MutableSet.of(); + @Override public String getTypeNameOfClass(Class type) { if (type==null) return null; - log.warn("Returning default for type name of "+type); - // TODO reverse lookup?? + + String defaultTypeName = getDefaultTypeNameOfClass(type); + String cleanedTypeName = Strings.removeAllFromStart(getDefaultTypeNameOfClass(type), "java:"); + // look in catalog for something where plan matches and consists only of type - return getDefaultTypeNameOfClass(type); + for (RegisteredType rt: mgmt.getTypeRegistry().getAll()) { + if (!(rt.getPlan() instanceof YomlTypeImplementationPlan)) continue; + if (((YomlTypeImplementationPlan)rt.getPlan()).javaType==null) continue; + if (!((YomlTypeImplementationPlan)rt.getPlan()).javaType.equals(cleanedTypeName)) continue; + if (rt.getPlan().getPlanData()==null) return rt.getId(); + // TODO find the "best" one, not just any one (at least best version) - though it shouldn't matter + // TODO are there some plans which are permitted, eg just defining serializers? + // TODO cache or something more efficient + } + + if (WARNS.add(type.getName())) + log.warn("Returning default for type name of "+type+"; catalog entry should be supplied"); + + return defaultTypeName; } protected String getDefaultTypeNameOfClass(Class type) { diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java index d619eaf6c9..eb97474ed7 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java @@ -22,6 +22,9 @@ import java.util.Map; import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.typereg.RegisteredType; @@ -32,6 +35,7 @@ import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode; import org.apache.brooklyn.core.typereg.AbstractFormatSpecificTypeImplementationPlan; import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer; +import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.core.typereg.UnsupportedTypePlanException; import org.apache.brooklyn.util.collections.MutableList; @@ -44,8 +48,11 @@ import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions; +import org.apache.brooklyn.util.yoml.internal.YomlConfigs.Builder; import org.yaml.snakeyaml.Yaml; +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -131,20 +138,19 @@ protected double scoreForNonmatchingNonnullFormat(String planFormat, Object plan @Override protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception { - BrooklynYomlTypeRegistry tr = new BrooklynYomlTypeRegistry(mgmt, context); - Yoml y = Yoml.newInstance(newYomlConfig(mgmt, tr).build()); + Yoml y = Yoml.newInstance(newYomlConfig(mgmt, context).build()); // TODO could cache the parse, could cache the instantiation instructions Object data = type.getPlan().getPlanData(); Class expectedSuperType = context.getExpectedJavaSuperType(); - String expectedSuperTypeName = tr.getTypeNameOfClass(expectedSuperType); + String expectedSuperTypeName = y.getConfig().getTypeRegistry().getTypeNameOfClass(expectedSuperType); Object parsedInput; if (data==null || (data instanceof String)) { if (Strings.isBlank((String)data)) { // blank plan means to use the java type / construction instruction - Maybe> javaType = tr.getJavaTypeInternal(type, context); + Maybe> javaType = ((BrooklynYomlTypeRegistry) y.getConfig().getTypeRegistry()).getJavaTypeInternal(type, context); ConstructionInstruction constructor = context.getConstructorInstruction(); if (javaType.isAbsent() && constructor==null) { return Maybe.absent(new IllegalStateException("Type "+type+" has no plan YAML and error in type", ((Maybe.Absent)javaType).getException())); @@ -187,6 +193,18 @@ protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext co return y.readFromYamlObject(parsedInput, expectedSuperTypeName); } + @Beta @VisibleForTesting + public static Builder newYomlConfig(@Nonnull ManagementContext mgmt) { + return newYomlConfig(mgmt, (RegisteredTypeLoadingContext)null); + } + + @Beta @VisibleForTesting + public static Builder newYomlConfig(@Nonnull ManagementContext mgmt, @Nullable RegisteredTypeLoadingContext context) { + if (context==null) context = RegisteredTypeLoadingContexts.any(); + BrooklynYomlTypeRegistry tr = new BrooklynYomlTypeRegistry(mgmt, context); + return newYomlConfig(mgmt, tr); + } + static YomlConfig.Builder newYomlConfig(ManagementContext mgmt, BrooklynYomlTypeRegistry typeRegistry) { return YomlConfig.Builder.builder().typeRegistry(typeRegistry). serializersPostAddDefaults(). diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyConstructionInstructions.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyConstructionInstructions.java index dfeda29475..8800fdaa1c 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyConstructionInstructions.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/serializers/ConfigKeyConstructionInstructions.java @@ -1,3 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package org.apache.brooklyn.camp.yoml.serializers; import java.util.Map; diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java index 6954255276..2532044a44 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java @@ -18,6 +18,7 @@ */ package org.apache.brooklyn.camp.yoml; +import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.annotations.YomlAnnotations; import org.apache.brooklyn.util.yoml.tests.YomlTestFixture; @@ -26,6 +27,9 @@ public class BrooklynYomlTestFixture extends YomlTestFixture { public static YomlTestFixture newInstance() { return new BrooklynYomlTestFixture(); } public static YomlTestFixture newInstance(YomlConfig config) { return new BrooklynYomlTestFixture(config); } + public static YomlTestFixture newInstance(ManagementContext mgmt) { + return newInstance(YomlTypePlanTransformer.newYomlConfig(mgmt).build()); + } public BrooklynYomlTestFixture() {} public BrooklynYomlTestFixture(YomlConfig config) { diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java index db5ca547f9..1661d45687 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java @@ -30,8 +30,10 @@ import org.apache.brooklyn.core.sensor.StaticSensor; import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.yaml.Yamls; +import org.apache.brooklyn.util.yoml.tests.YomlTestFixture; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -50,22 +52,27 @@ public void setUp() throws Exception { YomlInitializers.install(mgmt()); } - @Test(enabled=false) // format still runs old camp parse, does not attempt yaml - public void testStaticSensorBasic() throws Exception { - String yaml = Joiner.on("\n").join( - "services:", - "- type: org.apache.brooklyn.core.test.entity.TestEntity", - " brooklyn.initializers:", - " - name: the-answer", - " type: static-sensor", - " sensor-type: int", - " value: 42"); - - checkStaticSensorInApp(yaml); - } + StaticSensor SS_42 = new StaticSensor(ConfigBag.newInstance() + .configure(StaticSensor.SENSOR_NAME, "the-answer") + .configure(StaticSensor.SENSOR_TYPE, "int") + .configure(StaticSensor.STATIC_VALUE, 42) ); + + // permitted anytime + final static String SS_42_YAML_SIMPLE = Joiner.on("\n").join( + "name: the-answer", + "type: static-sensor", + "sensor-type: int", + "value: 42"); + + // permitted if we know we are reading EntityInitializer instances + final String SS_42_YAML_SINGLETON_MAP = Joiner.on("\n").join( + "the-answer:", + " type: static-sensor", + " sensor-type: int", + " value: 42"); @Test - public void testReadSensor() throws Exception { + public void testYomlReadSensor() throws Exception { String yaml = Joiner.on("\n").join( "name: the-answer", "type: static-sensor", @@ -74,34 +81,62 @@ public void testReadSensor() throws Exception { Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(yaml).iterator().next(), null, null); Asserts.assertInstanceOf(ss, StaticSensor.class); + // Assert.assertEquals(ss, SS_42); // class does not support equals } @Test - public void testReadSensorWithExpectedSuperType() throws Exception { - String yaml = Joiner.on("\n").join( - "name: the-answer", - "type: static-sensor", - "sensor-type: int", - "value: 42"); - - Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(yaml).iterator().next(), null, EntityInitializer.class); + public void testYomlReadSensorWithExpectedSuperType() throws Exception { + Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(SS_42_YAML_SIMPLE).iterator().next(), null, EntityInitializer.class); Asserts.assertInstanceOf(ss, StaticSensor.class); + // Assert.assertEquals(ss, SS_42); // class does not support equals } - + @Test public void testReadSensorAsMapWithName() throws Exception { + Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(SS_42_YAML_SINGLETON_MAP).iterator().next(), null, EntityInitializer.class); + Asserts.assertInstanceOf(ss, StaticSensor.class); + } + + @Test + public void testYomlReadSensorSingletonMapWithFixture() throws Exception { + YomlTestFixture y = BrooklynYomlTestFixture.newInstance(mgmt()); + y.read(SS_42_YAML_SINGLETON_MAP, "entity-initializer"); + Asserts.assertInstanceOf(y.getLastReadResult(), StaticSensor.class); + } + + @Test + public void testYomlWriteSensorWithFixture() throws Exception { + YomlTestFixture y = BrooklynYomlTestFixture.newInstance(mgmt()); + y.write(SS_42, "entity-initializer").assertLastWriteIgnoringQuotes( + "{the-answer: { type: static-sensor:0.10.0-SNAPSHOT, period: 5m, targetType: int, timeout: a very long time, value: 42}}" + // ideally it would be simple like below but it sets all the values so it ends up looking like the above +// Jsonya.newInstance().add( Yamls.parseAll(SS_42_YAML_SINGLETON_MAP).iterator().next() ).toString() + ); + + // and another read/write cycle gives the same thing + Object fullOutput = y.getLastWriteResult(); + y.readLastWrite().writeLastRead(); + Assert.assertEquals(fullOutput, y.getLastWriteResult()); + } + + // and test in context + + @Test(enabled=false) // this format (list) still runs old camp parse, does not attempt yaml, included for comparison + public void testStaticSensorWorksAsList() throws Exception { String yaml = Joiner.on("\n").join( - "the-answer:", - " type: static-sensor", - " sensor-type: int", - " value: 42"); + "services:", + "- type: org.apache.brooklyn.core.test.entity.TestEntity", + " brooklyn.initializers:", + " - name: the-answer", + " type: static-sensor", + " sensor-type: int", + " value: 42"); - Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(yaml).iterator().next(), null, EntityInitializer.class); - Asserts.assertInstanceOf(ss, StaticSensor.class); + checkStaticSensorInApp(yaml); } @Test - public void testStaticSensorSingletonMap() throws Exception { + public void testStaticSensorWorksAsSingletonMap() throws Exception { String yaml = Joiner.on("\n").join( "services:", "- type: org.apache.brooklyn.core.test.entity.TestEntity", diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java index 85f8e5adce..3bdb74593f 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java @@ -61,7 +61,7 @@ public class AddSensor implements EntityInitializer { protected final String name; protected final Duration period; - protected final String type; + protected final String targetType; protected AttributeSensor sensor; public AddSensor(Map params) { @@ -71,7 +71,7 @@ public AddSensor(Map params) { public AddSensor(final ConfigBag params) { this.name = Preconditions.checkNotNull(params.get(SENSOR_NAME), "Name must be supplied when defining a sensor"); this.period = params.get(SENSOR_PERIOD); - this.type = params.get(SENSOR_TYPE); + this.targetType = params.get(SENSOR_TYPE); } @Override @@ -81,7 +81,7 @@ public void apply(EntityLocal entity) { } private AttributeSensor newSensor(Entity entity) { - String className = getFullClassName(type); + String className = getFullClassName(targetType); Class clazz = getType(entity, className); return Sensors.newSensor(clazz, name); } From 87b4e50724ad30a783d67ed7189d190b508d14c5 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 28 Sep 2016 16:49:35 +0100 Subject: [PATCH 59/77] tidy freemarker code --- .../util/core/text/TemplateProcessor.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/util/core/text/TemplateProcessor.java b/core/src/main/java/org/apache/brooklyn/util/core/text/TemplateProcessor.java index 923f733eeb..15d8ff95b4 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/text/TemplateProcessor.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/text/TemplateProcessor.java @@ -20,11 +20,9 @@ import static com.google.common.base.Preconditions.checkNotNull; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; +import java.io.StringWriter; import java.util.Map; import org.apache.brooklyn.api.entity.Entity; @@ -51,7 +49,7 @@ import freemarker.cache.StringTemplateLoader; import freemarker.template.Configuration; -import freemarker.template.ObjectWrapper; +import freemarker.template.DefaultObjectWrapperBuilder; import freemarker.template.Template; import freemarker.template.TemplateHashModel; import freemarker.template.TemplateModel; @@ -71,7 +69,7 @@ public class TemplateProcessor { protected static TemplateModel wrapAsTemplateModel(Object o) throws TemplateModelException { if (o instanceof Map) return new DotSplittingTemplateModel((Map)o); - return ObjectWrapper.DEFAULT_WRAPPER.wrap(o); + return new DefaultObjectWrapperBuilder(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS).build().wrap(o); } /** @deprecated since 0.7.0 use {@link #processTemplateFile(String, Map)} */ @Deprecated @@ -513,19 +511,21 @@ public static String processTemplateContents(String templateContents, final Map< /** Processes template contents against the given {@link TemplateHashModel}. */ public static String processTemplateContents(String templateContents, final TemplateHashModel substitutions) { try { - Configuration cfg = new Configuration(); + Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); + // TODO is there a locale which doesn't require ?c everywhere??? + // seems not, and looking at DecimalFormatSymbols, creating such a locale would be ugly +// cfg.setLocale(...); StringTemplateLoader templateLoader = new StringTemplateLoader(); templateLoader.putTemplate("config", templateContents); cfg.setTemplateLoader(templateLoader); Template template = cfg.getTemplate("config"); // TODO could expose CAMP '$brooklyn:' style dsl, based on template.createProcessingEnvironment - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - Writer out = new OutputStreamWriter(baos); + StringWriter out = new StringWriter(); template.process(substitutions, out); out.flush(); - return new String(baos.toByteArray()); + return out.toString(); } catch (Exception e) { log.warn("Error processing template (propagating): "+e, e); log.debug("Template which could not be parsed (causing "+e+") is:" From 47584d1cd07230be31faf18c975d74f3f483def3 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 29 Sep 2016 14:31:49 +0100 Subject: [PATCH 60/77] pass yoml context to type registry so it can do the right lookups small changes throughout, and big changes in BrooklnYomlTypeRegistry including notes now keeps construction instructions in yoml context instead of yoml config, which is cleaner --- .../camp/yoml/BrooklynYomlTypeRegistry.java | 298 +++++++++++------- .../camp/yoml/YomlTypePlanTransformer.java | 24 +- .../camp/yoml/types/YomlInitializers.java | 4 + .../camp/yoml/BrooklynYomlTestFixture.java | 2 +- ...omlTypeRegistryEntityInitializersTest.java | 12 +- .../typereg/BasicBrooklynTypeRegistry.java | 14 +- .../apache/brooklyn/util/yoml/YomlConfig.java | 2 - .../brooklyn/util/yoml/YomlTypeRegistry.java | 11 +- .../util/yoml/internal/YomlConfigs.java | 7 - .../util/yoml/internal/YomlContext.java | 10 + .../yoml/internal/YomlContextForRead.java | 11 + .../yoml/internal/YomlContextForWrite.java | 7 +- .../util/yoml/internal/YomlConverter.java | 2 +- .../ConfigInMapUnderConfigSerializer.java | 6 +- .../yoml/serializers/ConvertSingletonMap.java | 2 +- .../serializers/FieldsInMapUnderFields.java | 4 +- .../yoml/serializers/InstantiateTypeEnum.java | 4 +- .../InstantiateTypeFromRegistry.java | 12 +- ...antiateTypeFromRegistryUsingConfigMap.java | 16 +- .../yoml/serializers/InstantiateTypeList.java | 13 +- .../yoml/serializers/InstantiateTypeMap.java | 14 +- .../serializers/InstantiateTypePrimitive.java | 6 +- .../InstantiateTypeWorkerAbstract.java | 5 +- .../YomlSerializerComposition.java | 2 +- .../util/yoml/tests/MockYomlTypeRegistry.java | 24 +- 25 files changed, 313 insertions(+), 199 deletions(-) diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java index 429fdcf065..cb0946220b 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java @@ -52,80 +52,108 @@ import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.YomlTypeRegistry; import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions; +import org.apache.brooklyn.util.yoml.internal.YomlContext; import org.apache.brooklyn.util.yoml.internal.YomlUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.common.reflect.TypeToken; /** - * Provides a bridge so YOML can find things in the Brooklyn type registry. + * Provides a bridge so YOML's type registry can find things in the Brooklyn type registry. + *

+ * There are several subtleties in the loading strategy this registry should use depending + * when YOML is asking for something to be loaded. This is done through various + * {@link RegisteredTypeLoadingContext} instances. That class is able to do things including: + * + * (a) specify a supertype to use for filtering when finding a type + * (b) specify a class loading context (eg based on context's catalog item id / definition) + * (c) specify a set of encountered types to prevent looping and resolve a duplicate name + * as a class if it has already been resolved as a YOML item + * (eg yoml item java.pack.Foo declares its type as java.pack.Foo to mean to load it as a class) + * or simply bail out if there is a recursive definition (eg require java:java.pack.Foo in the above case) + * (d) specify a construction instruction specified by wrapper/subtype definitions + *

+ * Which of these should apply depends on the calling context. The following situations apply: + * + * (1) when YOML makes the first call to resolve the type at "/", we should apply (a) and (b) supplied by the user; + * (c) and (d) should be empty but no harm in applying them (and it will make recursive work easier) + * (2) if in the course of that call this registry calls to YOML to evaluate a plan definition of a supertype, + * it should act like (1) except use the supertype's loader; ie apply everything except (b) + * (3) if YOML makes a subsequent call to resolve a type at a different path, it should apply only (b), + * not any of the others + *

+ * See {@link #getTypeContextFor(YomlContext)} and usages. + * + *

+ * + * More details on library loading. Consider for instance: + * + * - id: x + * item: { type: X } + * - id: x2 + * item: { type: x } + * - id: cluster-x + * item: { type: cluster, children: [ { type: x }, { type: X } ] } + * + * And assume these items declare different libraries. + * + * We *need* libraries to be used when resolving the reference to a parent java type (e.g. x's parent type X). + * + * We *don't* want libraries to be used transitively, e.g. when x2 or cluster-x refers to x, + * only x's libraries apply to loading X, not x2's libraries. + * + * We *may* want libraries to be used when resolving references to types in a plan besides the parent type; + * e.g. cluster-x's libraries will be needed when resolving its reference to it's child of declared type X. + * But we might *NOT* want to support that, and could instead require that any type referenced elsewhere + * be defined as an explicit YAML type in the registry. This will simplify our lives as we will force all + * java objects to be explicitly defined as a type in the registry. But it means we won't be able to parse + * current plans so for the moment this is deferred, and we'd want to go through a "warning" cycle when + * applying. + * + * (In that last case we could even be stricter and say that any yaml types + * should have a simple single yaml-java bridge eg using a JavaClassNameTypeImplementationPlan + * to facilitate reverse lookup.) + * + * The defaultLoadingContext is used for the first and third cases above. + * The second case is handled by calling back to the registry with a limited context. + * (Note it will require some work to be able to distinguish between the third case and the first, + * as we don't currently have that contextual information when methods here are called.) + * + *

+ * + * One bit of ugly remains: + * + * If we install v1 then v2, cluster-x:1 will pick up x:2. + * We could change this: + * - by encouraging explicit `type: x:1` in the definition of cluster-x + * - by allowing `type: x:.` version shorthand, where `.` means the same version as the calling type + * - by recording locally-preferred types (aka "friends") on a registered type, + * and looking at those friends first; this would be nice in that we could allow private friends + * + * Yoml.read(...) can take a special "top-level type extensions" in order to find friends, + * and/or a special "top-level libraries" in order to resolve types in the first instance. + * These would *not* be passed when resolving a found type. */ public class BrooklynYomlTypeRegistry implements YomlTypeRegistry { private static final Logger log = LoggerFactory.getLogger(BrooklynYomlTypeRegistry.class); + private static final String JAVA_PREFIX = "java:"; + @SuppressWarnings("serial") static ConfigKey> CACHED_SERIALIZERS = ConfigKeys.newConfigKey(new TypeToken>() {}, "yoml.type.serializers", "Serializers found for a registered type"); private ManagementContext mgmt; - /* - * NB, there are a few subtleties here around library loading. For instance, given: - * - * - id: x - * item: { type: X } - * - id: x2 - * item: { type: x } - * - id: cluster-x - * item: { type: cluster, children: [ { type: x }, { type: X } ] } - * - * Assume these items declare different libraries. - * - * We *need* libraries to be used when resolving the reference to a parent java type (e.g. x's parent type X). - * - * We *don't* want libraries to be used transitively, e.g. when x2 or cluster-x refers to x, - * only x's libraries apply to loading X, not x2's libraries. - * - * We *may* want libraries to be used when resolving references to types in a plan besides the parent type; - * e.g. cluster-x's libraries will be needed when resolving its reference to it's child of declared type X. - * But we might *NOT* want to support that, and could instead require that any type referenced elsewhere - * be defined as an explicit YAML type in the registry. This will simplify our lives as we will force all - * java objects to be explicitly defined as a type in the registry. But it means we won't be able to parse - * current plans so for the moment this is deferred, and we'd want to go through a "warning" cycle when - * applying. - * - * (In that last case we could even be stricter and say that any yaml types - * should have a simple single yaml-java bridge eg using a JavaClassNameTypeImplementationPlan - * to facilitate reverse lookup.) - * - * The defaultLoadingContext is used for the first and third cases above. - * The second case is handled by calling back to the registry with a limited context. - * (Note it will require some work to be able to distinguish between the third case and the first, - * as we don't currently have that contextual information when methods here are called.) - * - *

- * - * One bit of ugly remains: - * - * If we install v1 then v2, cluster-x:1 will pick up x:2. - * We could change this: - * - by encouraging explicit `type: x:1` in the definition of cluster-x - * - by allowing `type: x:.` version shorthand, where `.` means the same version as the calling type - * - by recording locally-preferred types (aka "friends") on a registered type, - * and looking at those friends first; this would be nice in that we could allow private friends - * - * Yoml.read(...) can take a special "top-level type extensions" in order to find friends, - * and/or a special "top-level libraries" in order to resolve types in the first instance. - * These would *not* be passed when resolving a found type. - */ - private RegisteredTypeLoadingContext defaultLoadingContext; + private RegisteredTypeLoadingContext rootLoadingContext; - public BrooklynYomlTypeRegistry(@Nonnull ManagementContext mgmt, @Nonnull RegisteredTypeLoadingContext defaultLoadingContext) { + public BrooklynYomlTypeRegistry(@Nonnull ManagementContext mgmt, @Nonnull RegisteredTypeLoadingContext rootLoadingContext) { this.mgmt = mgmt; - this.defaultLoadingContext = defaultLoadingContext; + this.rootLoadingContext = rootLoadingContext; } @@ -135,55 +163,81 @@ protected BrooklynTypeRegistry registry() { @Override public Maybe newInstanceMaybe(String typeName, Yoml yoml) { - return newInstanceMaybe(typeName, yoml, defaultLoadingContext); + return newInstanceMaybe(typeName, yoml, rootLoadingContext); } - public Maybe newInstanceMaybe(String typeName, Yoml yoml, RegisteredTypeLoadingContext context) { + @Override + public Maybe newInstanceMaybe(String typeName, Yoml yoml, @Nonnull YomlContext yomlContextOfThisEvaluation) { + RegisteredTypeLoadingContext applicableLoadingContext = getTypeContextFor(yomlContextOfThisEvaluation); + return newInstanceMaybe(typeName, yoml, applicableLoadingContext); + } + + protected RegisteredTypeLoadingContext getTypeContextFor(YomlContext yomlContextOfThisEvaluation) { + RegisteredTypeLoadingContext applicableLoadingContext; + if (Strings.isBlank(yomlContextOfThisEvaluation.getJsonPath())) { + // at root, use everything + applicableLoadingContext = rootLoadingContext; + } else { + // elsewhere the only thing we apply is the loader + applicableLoadingContext = RegisteredTypeLoadingContexts.builder().loader(rootLoadingContext.getLoader()).build(); + } + + if (yomlContextOfThisEvaluation.getConstructionInstruction()!=null) { + // also apply any construction instruction we've been given + applicableLoadingContext = RegisteredTypeLoadingContexts.builder(applicableLoadingContext) + .constructorInstruction(yomlContextOfThisEvaluation.getConstructionInstruction()).build(); + } + return applicableLoadingContext; + } + + public Maybe newInstanceMaybe(String typeName, Yoml yoml, @Nonnull RegisteredTypeLoadingContext typeContext) { // yoml may be null, for java type lookups, but we could potentially get rid of that call path - RegisteredType typeR = registry().get(typeName, context); + RegisteredType typeR = registry().get(typeName, typeContext); if (typeR!=null) { - RegisteredTypeLoadingContext nextContext = null; - if (context==null) { - nextContext = RegisteredTypeLoadingContexts.alreadyEncountered(MutableSet.of(typeName)); - } else { - if (!context.getAlreadyEncounteredTypes().contains(typeName)) { - // we lose any other contextual information, but that seems _good_ since it was used to find the type, - // we probably now want to shift to the loading context of that type, e.g. the x2 example above; - // we just need to ensure it doesn't try to load a super which is also a sub! - nextContext = RegisteredTypeLoadingContexts.alreadyEncountered(context.getAlreadyEncounteredTypes(), typeName); - } - } - if (nextContext==null) { - // fall through to path below; we have a circular reference, so need to load java instead + boolean seenType = typeContext.getAlreadyEncounteredTypes().contains(typeName); + + if (!seenType) { + // instantiate the parent type + + // keep everything (supertype constraint, encountered types, constructor instruction) + // apart from loader info -- loader should be from the type found here + RegisteredTypeLoadingContexts.Builder nextContext = RegisteredTypeLoadingContexts.builder(typeContext); + nextContext.addEncounteredTypes(typeName); + // reset the loader (pretty sure this is right -Alex) + // the create call will attach the loader of typeR + nextContext.loader(null); + + return Maybe.of(registry().create(typeR, nextContext.build(), null)); + } else { - if (yoml!=null && yoml.getConfig().getConstructionInstruction()!=null) - nextContext = RegisteredTypeLoadingContexts.builder(nextContext).constructorInstruction(yoml.getConfig().getConstructionInstruction()).build(); - return Maybe.of(registry().create(typeR, nextContext, null)); + // circular reference means load java, below } + } else { + // type not found means load java, below } Maybe> t = null; - { - Exception e = null; - try { - t = getJavaTypeInternal(typeName, context); - } catch (Exception ee) { - Exceptions.propagateIfFatal(ee); - e = ee; - } - if (t.isAbsent()) { - // generally doesn't come here; it gets filtered by getJavaTypeMaybe - if (e==null) e = ((Maybe.Absent)t).getException(); - return Maybe.absent("Neither the type registry nor the classpath/libraries could load type "+typeName+ - (e!=null && !Exceptions.isRootBoringClassNotFound(e, typeName) ? ": "+Exceptions.collapseText(e) : "")); - } + + Exception e = null; + try { + t = getJavaTypeInternal(typeName, typeContext); + } catch (Exception ee) { + Exceptions.propagateIfFatal(ee); + e = ee; + } + if (t.isAbsent()) { + // generally doesn't come here; it gets filtered by getJavaTypeMaybe + if (e==null) e = ((Maybe.Absent)t).getException(); + return Maybe.absent("Neither the type registry nor the classpath/libraries could load type "+typeName+ + (e!=null && !Exceptions.isRootBoringClassNotFound(e, typeName) ? ": "+Exceptions.collapseText(e) : "")); } + try { - return ConstructionInstructions.Factory.newDefault(t.get(), yoml==null ? null : yoml.getConfig().getConstructionInstruction()).create(); - } catch (Exception e) { - return Maybe.absent("Error instantiating type "+typeName+": "+Exceptions.collapseText(e)); + return ConstructionInstructions.Factory.newDefault(t.get(), typeContext.getConstructorInstruction()).create(); + } catch (Exception e2) { + return Maybe.absent("Error instantiating type "+typeName+": "+Exceptions.collapseText(e2)); } } @@ -218,9 +272,9 @@ public YomlClassNotFoundException(String message, Throwable cause) { * and risks odd errors depending when the lazy evaluation occurs vis-a-vis dependent types. */ @Override - public Maybe> getJavaTypeMaybe(String typeName) { + public Maybe> getJavaTypeMaybe(String typeName, YomlContext context) { if (typeName==null) return Maybe.absent("null type"); - return getJavaTypeInternal(typeName, defaultLoadingContext); + return getJavaTypeInternal(typeName, getTypeContextFor(context)); } @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -231,9 +285,8 @@ protected static Maybe> maybeClass(Class clazz) { protected Maybe> getJavaTypeInternal(String typeName, RegisteredTypeLoadingContext context) { RegisteredType type = registry().get(typeName, context); - if (type!=null && !context.getAlreadyEncounteredTypes().contains(type.getId())) { - RegisteredTypeLoadingContext newContext = RegisteredTypeLoadingContexts.loaderAlreadyEncountered(context.getLoader(), context.getAlreadyEncounteredTypes(), typeName); - return getJavaTypeInternal(type, newContext); + if (type!=null && context!=null && !context.getAlreadyEncounteredTypes().contains(type.getId())) { + return getJavaTypeInternal(type, context); } // try to load it wrt context @@ -248,8 +301,8 @@ protected Maybe> getJavaTypeInternal(String typeName, RegisteredTypeLoa if (result!=null) return maybeClass(result); // currently we accept but don't require the 'java:' prefix - boolean isJava = typeName.startsWith("java:"); - typeName = Strings.removeFromStart(typeName, "java:"); + boolean isJava = typeName.startsWith(JAVA_PREFIX); + typeName = Strings.removeFromStart(typeName, JAVA_PREFIX); BrooklynClassLoadingContext loader = null; if (context!=null && context.getLoader()!=null) loader = context.getLoader(); @@ -291,9 +344,13 @@ protected Maybe> getJavaTypeInternal(RegisteredType type, RegisteredTyp Maybe> result = Maybe.absent("Unable to find java supertype for "+type); if (declaredPrimarySuperTypeName!=null) { - // if a supertype name was found, use it - RegisteredTypeLoadingContext newContext = RegisteredTypeLoadingContexts.loaderAlreadyEncountered( - CatalogUtils.newClassLoadingContext(mgmt, type), context.getAlreadyEncounteredTypes(), type.getId()); + // when looking at supertypes we reset the loader to use loaders for the type, + // note the traversal in encountered types, and leave the rest (eg supertype restriction) as was + RegisteredTypeLoadingContext newContext = RegisteredTypeLoadingContexts.builder(context) + .loader( CatalogUtils.newClassLoadingContext(mgmt, type) ) + .addEncounteredTypes(type.getId()) + .build(); + result = getJavaTypeInternal(declaredPrimarySuperTypeName, newContext); } @@ -324,27 +381,38 @@ public String getTypeName(Object obj) { return getTypeNameOfClass(obj.getClass()); } - public static Set WARNS = MutableSet.of(); + public static Set WARNS = MutableSet.of( + // don't warn on this base class + JAVA_PREFIX+Object.class.getName() ); @Override public String getTypeNameOfClass(Class type) { if (type==null) return null; String defaultTypeName = getDefaultTypeNameOfClass(type); - String cleanedTypeName = Strings.removeAllFromStart(getDefaultTypeNameOfClass(type), "java:"); + String cleanedTypeName = Strings.removeFromStart(getDefaultTypeNameOfClass(type), JAVA_PREFIX); + // the code below may be a bottleneck; if so, we should cache or something more efficient + + Set types = MutableSet.of(); // look in catalog for something where plan matches and consists only of type for (RegisteredType rt: mgmt.getTypeRegistry().getAll()) { if (!(rt.getPlan() instanceof YomlTypeImplementationPlan)) continue; if (((YomlTypeImplementationPlan)rt.getPlan()).javaType==null) continue; if (!((YomlTypeImplementationPlan)rt.getPlan()).javaType.equals(cleanedTypeName)) continue; - if (rt.getPlan().getPlanData()==null) return rt.getId(); - // TODO find the "best" one, not just any one (at least best version) - though it shouldn't matter - // TODO are there some plans which are permitted, eg just defining serializers? - // TODO cache or something more efficient + if (rt.getPlan().getPlanData()==null) types.add(rt); + // are there ever plans we want to permit, eg just defining serializers? + // (if so check the plan here) + } + if (types.size()==1) return Iterables.getOnlyElement(types).getSymbolicName(); + if (types.size()>1) { + if (WARNS.add(type.getName())) + log.warn("Multiple registered types for "+type+"; picking one arbitrarily"); + return types.iterator().next().getId(); } - if (WARNS.add(type.getName())) + boolean isJava = defaultTypeName.startsWith(JAVA_PREFIX); + if (isJava && WARNS.add(type.getName())) log.warn("Returning default for type name of "+type+"; catalog entry should be supplied"); return defaultTypeName; @@ -355,24 +423,24 @@ protected String getDefaultTypeNameOfClass(Class type) { if (primitive.isPresent()) return primitive.get(); if (String.class.equals(type)) return "string"; // map and list handled by those serializers - return "java:"+type.getName(); + return JAVA_PREFIX+type.getName(); } @Override - public Iterable getSerializersForType(String typeName) { + public Iterable getSerializersForType(String typeName, YomlContext yomlContext) { Set result = MutableSet.of(); - collectSerializers(typeName, result, MutableSet.of()); + // TODO add root loader? + collectSerializers(typeName, getTypeContextFor(yomlContext), result, MutableSet.of()); return result; } - protected void collectSerializers(Object type, Collection result, Set typesVisited) { + protected void collectSerializers(Object type, RegisteredTypeLoadingContext context, Collection result, Set typesVisited) { if (type==null) return; if (type instanceof String) { // convert string to registered type or class Object typeR = registry().get((String)type); if (typeR==null) { - // TODO context - typeR = getJavaTypeInternal((String)type, null).orNull(); + typeR = getJavaTypeInternal((String)type, context).orNull(); } if (typeR==null) { // will this ever happen in normal operations? @@ -409,7 +477,7 @@ protected void collectSerializers(Object type, Collection result // // could look up the type? but we should be calling this normally with the RT if we have one so probably not necessary // // and could recurse through superclasses and interfaces -- but the above is a better place to do that if needed // String name = getTypeNameOfClass((Class)type); -// if (name.startsWith("java:")) { +// if (name.startsWith(JAVA_PREFIX)) { // find... //// supers.add(((Class) type).getSuperclass()); //// supers.addAll(Arrays.asList(((Class) type).getInterfaces())); @@ -418,7 +486,9 @@ protected void collectSerializers(Object type, Collection result throw new IllegalStateException("Illegal supertype entry "+type+", visiting "+typesVisited); } for (Object s: supers) { - collectSerializers(s, result, typesVisited); + RegisteredTypeLoadingContext unconstrainedSupertypeContext = RegisteredTypeLoadingContexts.builder(context) + .expectedSuperType(null).build(); + collectSerializers(s, unconstrainedSupertypeContext, result, typesVisited); } if (canUpdateCache) { if (type instanceof RegisteredType) { diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java index eb97474ed7..09d2b0a129 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java @@ -33,6 +33,7 @@ import org.apache.brooklyn.camp.spi.resolve.PlanInterpreter; import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationContext; import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode; +import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.typereg.AbstractFormatSpecificTypeImplementationPlan; import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer; import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; @@ -138,6 +139,15 @@ protected double scoreForNonmatchingNonnullFormat(String planFormat, Object plan @Override protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception { + Preconditions.checkNotNull(type); + Preconditions.checkNotNull(context); + + // add any loaders for this type + context = RegisteredTypeLoadingContexts.builder(context) + .loader( + CatalogUtils.newClassLoadingContext(mgmt, type, context.getLoader()) ) + .build(); + Yoml y = Yoml.newInstance(newYomlConfig(mgmt, context).build()); // TODO could cache the parse, could cache the instantiation instructions @@ -193,22 +203,14 @@ protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext co return y.readFromYamlObject(parsedInput, expectedSuperTypeName); } - @Beta @VisibleForTesting - public static Builder newYomlConfig(@Nonnull ManagementContext mgmt) { - return newYomlConfig(mgmt, (RegisteredTypeLoadingContext)null); - } - @Beta @VisibleForTesting public static Builder newYomlConfig(@Nonnull ManagementContext mgmt, @Nullable RegisteredTypeLoadingContext context) { if (context==null) context = RegisteredTypeLoadingContexts.any(); BrooklynYomlTypeRegistry tr = new BrooklynYomlTypeRegistry(mgmt, context); - return newYomlConfig(mgmt, tr); - } - - static YomlConfig.Builder newYomlConfig(ManagementContext mgmt, BrooklynYomlTypeRegistry typeRegistry) { - return YomlConfig.Builder.builder().typeRegistry(typeRegistry). + return YomlConfig.Builder.builder().typeRegistry(tr). serializersPostAddDefaults(). - // TODO any custom serializers? + // could add any custom global serializers here; but so far these are all linked to types + // and collected by tr.collectSerializers(...) coercer(new ValueResolver.ResolvingTypeCoercer()); } diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java index 1cc959cbb7..1826ec236a 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/types/YomlInitializers.java @@ -28,6 +28,7 @@ import org.apache.brooklyn.core.sensor.StaticSensor; import org.apache.brooklyn.core.sensor.ssh.SshCommandSensor; import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; +import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.yoml.YomlSerializer; import com.google.common.annotations.Beta; @@ -285,6 +286,9 @@ public static void addLocalBean(ManagementContext mgmt, String symbolicName, Str /** Put here until there is a better init mechanism */ @Beta public static void install(ManagementContext mgmt) { + + addLocalBean(mgmt, Duration.class); + addLocalBean(mgmt, EntityInitializer.class); addLocalBean(mgmt, StaticSensor.class); addLocalBean(mgmt, SshCommandSensor.class); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java index 2532044a44..8cb34c6a0f 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTestFixture.java @@ -28,7 +28,7 @@ public class BrooklynYomlTestFixture extends YomlTestFixture { public static YomlTestFixture newInstance() { return new BrooklynYomlTestFixture(); } public static YomlTestFixture newInstance(YomlConfig config) { return new BrooklynYomlTestFixture(config); } public static YomlTestFixture newInstance(ManagementContext mgmt) { - return newInstance(YomlTypePlanTransformer.newYomlConfig(mgmt).build()); + return newInstance(YomlTypePlanTransformer.newYomlConfig(mgmt, null).build()); } public BrooklynYomlTestFixture() {} diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java index 1661d45687..2a57797871 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/YomlTypeRegistryEntityInitializersTest.java @@ -81,20 +81,21 @@ public void testYomlReadSensor() throws Exception { Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(yaml).iterator().next(), null, null); Asserts.assertInstanceOf(ss, StaticSensor.class); - // Assert.assertEquals(ss, SS_42); // class does not support equals + // Assert.assertEquals(ss, SS_42); // StaticSensor does not support equals } @Test public void testYomlReadSensorWithExpectedSuperType() throws Exception { Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(SS_42_YAML_SIMPLE).iterator().next(), null, EntityInitializer.class); Asserts.assertInstanceOf(ss, StaticSensor.class); - // Assert.assertEquals(ss, SS_42); // class does not support equals + // Assert.assertEquals(ss, SS_42); // StaticSensor does not support equals } @Test public void testReadSensorAsMapWithName() throws Exception { Object ss = mgmt().getTypeRegistry().createBeanFromPlan("yoml", Yamls.parseAll(SS_42_YAML_SINGLETON_MAP).iterator().next(), null, EntityInitializer.class); Asserts.assertInstanceOf(ss, StaticSensor.class); + // Assert.assertEquals(ss, SS_42); // StaticSensor does not support equals } @Test @@ -108,8 +109,9 @@ public void testYomlReadSensorSingletonMapWithFixture() throws Exception { public void testYomlWriteSensorWithFixture() throws Exception { YomlTestFixture y = BrooklynYomlTestFixture.newInstance(mgmt()); y.write(SS_42, "entity-initializer").assertLastWriteIgnoringQuotes( - "{the-answer: { type: static-sensor:0.10.0-SNAPSHOT, period: 5m, targetType: int, timeout: a very long time, value: 42}}" - // ideally it would be simple like below but it sets all the values so it ends up looking like the above + "{the-answer: { type: static-sensor, period: 5m, targetType: int, timeout: a very long time, value: 42}}" + // ideally it would be simple like below but StaticSensor sets all the values + // so it ends up looking like the above; not too bad // Jsonya.newInstance().add( Yamls.parseAll(SS_42_YAML_SINGLETON_MAP).iterator().next() ).toString() ); @@ -118,7 +120,7 @@ public void testYomlWriteSensorWithFixture() throws Exception { y.readLastWrite().writeLastRead(); Assert.assertEquals(fullOutput, y.getLastWriteResult()); } - + // and test in context @Test(enabled=false) // this format (list) still runs old camp parse, does not attempt yaml, included for comparison diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java index 75697f8835..ed422d1347 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BasicBrooklynTypeRegistry.java @@ -143,20 +143,20 @@ public RegisteredType get(String symbolicName, String version) { } @Override - public RegisteredType get(String symbolicNameWithOptionalVersion, RegisteredTypeLoadingContext context) { - return getMaybe(symbolicNameWithOptionalVersion, context).orNull(); + public RegisteredType get(String symbolicNameOrAliasWithOptionalVersion, RegisteredTypeLoadingContext context) { + return getMaybe(symbolicNameOrAliasWithOptionalVersion, context).orNull(); } @Override - public Maybe getMaybe(String symbolicNameWithOptionalVersion, RegisteredTypeLoadingContext context) { + public Maybe getMaybe(String symbolicNameOrAliasWithOptionalVersion, RegisteredTypeLoadingContext context) { Maybe r1 = null; - if (CatalogUtils.looksLikeVersionedId(symbolicNameWithOptionalVersion)) { - String symbolicName = CatalogUtils.getSymbolicNameFromVersionedId(symbolicNameWithOptionalVersion); - String version = CatalogUtils.getVersionFromVersionedId(symbolicNameWithOptionalVersion); + if (CatalogUtils.looksLikeVersionedId(symbolicNameOrAliasWithOptionalVersion)) { + String symbolicName = CatalogUtils.getSymbolicNameFromVersionedId(symbolicNameOrAliasWithOptionalVersion); + String version = CatalogUtils.getVersionFromVersionedId(symbolicNameOrAliasWithOptionalVersion); r1 = getSingle(symbolicName, version, context); if (r1.isPresent()) return r1; } - Maybe r2 = getSingle(symbolicNameWithOptionalVersion, BrooklynCatalog.DEFAULT_VERSION, context); + Maybe r2 = getSingle(symbolicNameOrAliasWithOptionalVersion, BrooklynCatalog.DEFAULT_VERSION, context); if (r2.isPresent() || r1==null) return r2; return r1; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlConfig.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlConfig.java index 0713a4d334..9d39c892bd 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlConfig.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlConfig.java @@ -21,7 +21,6 @@ import java.util.List; import org.apache.brooklyn.util.javalang.coerce.TypeCoercer; -import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; import org.apache.brooklyn.util.yoml.internal.YomlConfigs; public interface YomlConfig { @@ -29,7 +28,6 @@ public interface YomlConfig { public YomlTypeRegistry getTypeRegistry(); public TypeCoercer getCoercer(); public List getSerializersPost(); - public ConstructionInstruction getConstructionInstruction(); public static class Builder extends YomlConfigs.Builder { public static Builder builder() { return new Builder(); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java index bf21a2de52..b753bcd959 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/YomlTypeRegistry.java @@ -21,13 +21,18 @@ import javax.annotation.Nullable; import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.yoml.internal.YomlContext; public interface YomlTypeRegistry { Object newInstance(String type, Yoml yoml); /** Absent if unknown type; throws if type is ill-defined or incomplete. */ - Maybe newInstanceMaybe(String type, Yoml yoml); + Maybe newInstanceMaybe(String type, Yoml yomlToUseForSubsequentEvaluation); + + /** As {@link #newInstance(String, Yoml)} but for use when YOML is making a nested call, + * in case different resolution strategies apply inside the hierarchy. */ + Maybe newInstanceMaybe(String type, Yoml yomlToUseForSubsequentEvaluation, @Nullable YomlContext yomlContextOfThisCall); /** Returns the most-specific Java type implied by the given type in the registry, * or a maybe wrapping any explanatory error if the type is not available in the registry. @@ -35,7 +40,7 @@ public interface YomlTypeRegistry { * This is needed so that the right deserialization strategies can be applied for * things like collections and enums. */ - Maybe> getJavaTypeMaybe(@Nullable String typeName); + Maybe> getJavaTypeMaybe(@Nullable String typeName, @Nullable YomlContext yomlContextOfThisCall); /** Return the best known type name to describe the given java instance */ String getTypeName(Object obj); @@ -44,6 +49,6 @@ public interface YomlTypeRegistry { /** Return custom serializers that shoud be used when deserializing something of the given type, * typically also looking at serializers for its supertypes */ - Iterable getSerializersForType(String typeName); + Iterable getSerializersForType(String typeName, @Nullable YomlContext yomlContextOfThisCall); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfigs.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfigs.java index 0278e29fd4..7b0bda6420 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfigs.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConfigs.java @@ -45,14 +45,12 @@ private BasicYomlConfig(YomlConfig original) { this.typeRegistry = original.getTypeRegistry(); this.coercer = original.getCoercer(); this.serializersPost = original.getSerializersPost(); - this.constructionInstruction = original.getConstructionInstruction(); } } private YomlTypeRegistry typeRegistry; private TypeCoercer coercer = TypeCoercerExtensible.newDefault(); private List serializersPost = MutableList.of(); - private ConstructionInstruction constructionInstruction; public YomlTypeRegistry getTypeRegistry() { return typeRegistry; @@ -65,10 +63,6 @@ public TypeCoercer getCoercer() { public List getSerializersPost() { return ImmutableList.copyOf(serializersPost); } - - public ConstructionInstruction getConstructionInstruction() { - return constructionInstruction; - } } public static class Builder> { @@ -84,7 +78,6 @@ public static class Builder> { public T serializersPostReplace(List x) { result.serializersPost = x; return thiz; } public T serializersPostAdd(Collection x) { result.serializersPost.addAll(x); return thiz; } public T serializersPostAddDefaults() { return serializersPostAdd(getDefaultSerializers()); } - public T constructionInstruction(ConstructionInstruction x) { result.constructionInstruction = x; return thiz; } public YomlConfig build() { return new BasicYomlConfig(result); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContext.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContext.java index 576d0233b1..e4e4968332 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContext.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContext.java @@ -44,6 +44,8 @@ public abstract class YomlContext { Set phasesFollowing = MutableSet.of(StandardPhases.MANIPULATING, StandardPhases.HANDLING_TYPE, StandardPhases.HANDLING_TYPE, StandardPhases.HANDLING_FIELDS); List phasesPreceding = MutableList.of(); + ConstructionInstruction constructionInstruction; + public static interface StandardPhases { String MANIPULATING = "manipulating"; String HANDLING_TYPE = "handling-type"; @@ -59,6 +61,8 @@ public YomlContext(String jsonPath, String expectedType, YomlContext parent) { public YomlContext getParent() { return parent; } + /** empty string if the root, otherwise a path string using e.g. /foo[0][0]/bar notation + * for evaluation of baz in { foo: [ [ { bar: baz } ] ] } */ public String getJsonPath() { return jsonPath; } @@ -112,6 +116,10 @@ public void phasesFinished() { if (phaseCurrent!=null) phasesPreceding.add(phaseCurrent); phasesFollowing = MutableSet.of(); phaseAdvance(); } + + public ConstructionInstruction getConstructionInstruction() { + return constructionInstruction; + } @Override public String toString() { @@ -122,5 +130,7 @@ public Map getBlackboard() { if (blackboard==null) blackboard = MutableMap.of(); return blackboard; } + + public abstract YomlContext subpath(String subpath, Object newItem, String optionalType); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForRead.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForRead.java index ba25316024..e958fea02d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForRead.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForRead.java @@ -27,6 +27,11 @@ public YomlContextForRead(Object yamlObject, String jsonPath, String expectedTyp setYamlObject(yamlObject); } + @Override + public YomlContextForRead subpath(String subpath, Object newItem, String superType) { + return new YomlContextForRead(newItem, getJsonPath()+subpath, superType, this); + } + String origin; int offset; int length; @@ -35,4 +40,10 @@ public YomlContextForRead(Object yamlObject, String jsonPath, String expectedTyp public String toString() { return "reading"+(expectedType!=null ? " "+expectedType : "")+" at "+(Strings.isNonBlank(jsonPath) ? jsonPath : "root"); } + + public YomlContextForRead constructionInstruction(ConstructionInstruction newConstruction) { + YomlContextForRead result = new YomlContextForRead(yamlObject, jsonPath, expectedType, parent); + result.constructionInstruction = newConstruction; + return result; + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForWrite.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForWrite.java index 19fad8419c..9b89de7b5e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForWrite.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForWrite.java @@ -24,5 +24,10 @@ public YomlContextForWrite(Object javaObject, String jsonPath, String expectedTy super(jsonPath, expectedType, parent); setJavaObject(javaObject); } - + + @Override + public YomlContextForWrite subpath(String subpath, Object newItem, String optionalType) { + return new YomlContextForWrite(newItem, getJsonPath()+"/"+subpath, optionalType, this); + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java index 9de32bda0c..a756f410d6 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java @@ -63,7 +63,7 @@ protected void loopOverSerializers(YomlContext context) { // find the serializers known so far; store on blackboard so they could be edited SerializersOnBlackboard serializers = SerializersOnBlackboard.getOrCreate(blackboard); if (context.getExpectedType()!=null) { - serializers.addExpectedTypeSerializers(config.getTypeRegistry().getSerializersForType(context.getExpectedType())); + serializers.addExpectedTypeSerializers(config.getTypeRegistry().getSerializersForType(context.getExpectedType(), context)); } serializers.addPostSerializers(config.getSerializersPost()); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java index 178bb01b38..aba4898211 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java @@ -78,13 +78,13 @@ protected boolean setKeyValueForJavaObjectOnRead(String key, Object value, Strin Object v2; try { - v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, optionalType, context) ); + v2 = converter.read( ((YomlContextForRead)context).subpath("/"+key, value, optionalType) ); } catch (Exception e) { // for config we try with the optional type, but don't insist Exceptions.propagateIfFatal(e); if (optionalType!=null) optionalType = null; try { - v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, optionalType, context) ); + v2 = converter.read( ((YomlContextForRead)context).subpath("/"+key, value, optionalType) ); } catch (Exception e2) { Exceptions.propagateIfFatal(e2); throw e; @@ -131,7 +131,7 @@ protected Map writePrepareGeneralMap() { } } - Object v = converter.write(new YomlContextForWrite(entry.getValue(), context.getJsonPath()+"/"+entry.getKey(), optionalType, context) ); + Object v = converter.write( ((YomlContextForWrite)context).subpath("/"+entry.getKey(), entry.getValue(), optionalType) ); configMap.put(entry.getKey(), v); } for (String key: configMap.keySet()) fib.configToWriteFromJava.remove(key); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java index 747a8fb59b..33973770ff 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java @@ -209,7 +209,7 @@ protected List readManipulatingInList(Collection list, SingletonMapMo List result = MutableList.of(); int index = 0; for (Object item: list) { - YomlContextForRead newContext = new YomlContextForRead(item, context.getJsonPath()+"["+index+"]", genericSubType, context); + YomlContextForRead newContext = ((YomlContextForRead)context).subpath("["+index+"]", item, genericSubType); // add this serializer and set mode in the new context SerializersOnBlackboard.create(newContext.getBlackboard()).addExpectedTypeSerializers(MutableList.of((YomlSerializer) ConvertSingletonMap.this)); newContext.getBlackboard().put(ConvertSingletonMap.this, mode); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java index c9c3e1ecc2..e76f582659 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java @@ -70,7 +70,7 @@ protected boolean setKeyValueForJavaObjectOnRead(String key, Object value, Strin return false; } else { String fieldType = getFieldTypeName(ff, optionalTypeConstraint); - Object v2 = converter.read( new YomlContextForRead(value, context.getJsonPath()+"/"+key, fieldType, context) ); + Object v2 = converter.read( ((YomlContextForRead)context).subpath("/"+key, value, fieldType) ); ff.setAccessible(true); ff.set(getJavaObject(), v2); @@ -223,7 +223,7 @@ protected Map writePrepareGeneralMap() { } } - Object v2 = converter.write(new YomlContextForWrite(v.get(), context.getJsonPath()+"/"+f, fieldType, context) ); + Object v2 = converter.write( ((YomlContextForWrite)context).subpath("/"+f, v.get(), fieldType) ); fields.put(f, v2); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java index 4ff20a86cc..8d2a661348 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeEnum.java @@ -52,7 +52,7 @@ public void read() { if (type==null) { String typeName = readingTypeFromFieldOrExpected(); if (typeName==null) return; - type = config.getTypeRegistry().getJavaTypeMaybe(typeName).orNull(); + type = config.getTypeRegistry().getJavaTypeMaybe(typeName, context).orNull(); // swallow exception in this context, it isn't meant for enum to resolve if (type==null || !type.isEnum()) return; value = readingValueFromTypeValueMap(); @@ -89,7 +89,7 @@ public void write() { if (wrap) { String typeName = config.getTypeRegistry().getTypeName(getJavaObject()); - if (addSerializersForDiscoveredRealType(typeName)) { + if (addSerializersForDiscoveredRealType(typeName, true)) { // if new serializers, bail out and we'll re-run context.phaseRestart(); return; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java index cc4b8eb192..fa45fbfc87 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistry.java @@ -50,27 +50,27 @@ public void read() { if (type==null) return; - if (!readType(type)) return; + if (!readType(type, true)) return; if (isYamlMap()) { removeFromYamlKeysOnBlackboardRemaining("type"); } } - protected boolean readType(String type) { - if (addSerializersForDiscoveredRealType(type)) { + protected boolean readType(String type, boolean isRoot) { + if (addSerializersForDiscoveredRealType(type, isRoot)) { // added new serializers so restart phase // in case another serializer wants to create it context.phaseRestart(); return false; } - Maybe resultM = config.getTypeRegistry().newInstanceMaybe(type, Yoml.newInstance(config)); + Maybe resultM = config.getTypeRegistry().newInstanceMaybe(type, Yoml.newInstance(config), context); if (resultM.isAbsent()) { String message = "Unable to create type '"+type+"'"; RuntimeException exc = null; - Maybe> jt = config.getTypeRegistry().getJavaTypeMaybe(type); + Maybe> jt = config.getTypeRegistry().getJavaTypeMaybe(type, context); if (jt.isAbsent()) { exc = ((Maybe.Absent)jt).getException(); } else { @@ -97,7 +97,7 @@ public void write() { // (osgi syntax isn't supported, because we expect items to be in the registry) String typeName = getJavaObject().getClass().equals(getExpectedTypeJava()) ? null : config.getTypeRegistry().getTypeName(getJavaObject()); - if (addSerializersForDiscoveredRealType(typeName)) { + if (addSerializersForDiscoveredRealType(typeName, true)) { // if new serializers, bail out and we'll re-run context.phaseRestart(); return; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java index f566d9ad75..900a78f932 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java @@ -32,13 +32,13 @@ import org.apache.brooklyn.util.javalang.Reflections; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.Yoml; -import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions; import org.apache.brooklyn.util.yoml.internal.SerializersOnBlackboard; import org.apache.brooklyn.util.yoml.internal.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlContextForRead; import com.google.common.base.Preconditions; @@ -135,8 +135,8 @@ public void read() { } @Override - protected boolean readType(String type) { - Class clazz = config.getTypeRegistry().getJavaTypeMaybe(type).orNull(); + protected boolean readType(String type, boolean isRoot) { + Class clazz = config.getTypeRegistry().getJavaTypeMaybe(type, context).orNull(); if (!isConfigurable(clazz)) return false; // prepare blackboard, annotations, then do handling_config @@ -146,7 +146,7 @@ protected boolean readType(String type) { fib.typeFromReadToConstructJavaLater = clazz; fib.fieldsFromReadToConstructJava = MutableMap.of(); - addSerializersForDiscoveredRealType(type); + addSerializersForDiscoveredRealType(type, isRoot); addExtraTypeSerializers(clazz); context.phaseInsert(YomlContext.StandardPhases.MANIPULATING, PHASE_INSTANTIATE_TYPE_DEFERRED); @@ -180,11 +180,11 @@ protected void readFinallyCreate() { Preconditions.checkNotNull(keyNameForConfigWhenSerialized); - YomlConfig newConfig = YomlConfig.Builder.builder(config).constructionInstruction( + YomlContextForRead constructionContext = ((YomlContextForRead)context).constructionInstruction( newConstructor(type, getTopLevelFieldsBlackboard().getConfigKeys(), MutableMap.copyOf(fib.fieldsFromReadToConstructJava), - config.getConstructionInstruction())).build(); - - Maybe resultM = config.getTypeRegistry().newInstanceMaybe(fib.typeNameFromReadToConstructJavaLater, Yoml.newInstance(newConfig)); + context.getConstructionInstruction()) ); + + Maybe resultM = config.getTypeRegistry().newInstanceMaybe(fib.typeNameFromReadToConstructJavaLater, Yoml.newInstance(config), constructionContext); if (resultM.isAbsent()) { warn(new IllegalStateException("Unable to create type '"+type+"'", ((Maybe.Absent)resultM).getException())); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java index 43a0a173c2..be240be969 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeList.java @@ -138,7 +138,7 @@ public void read() { // get any new generic type set - slightly messy if (!parseExpectedTypeAndDetermineIfNoBadProblems(type)) return; - Maybe> javaTypeM = config.getTypeRegistry().getJavaTypeMaybe(type); + Maybe> javaTypeM = config.getTypeRegistry().getJavaTypeMaybe(type, context); Class javaType; if (javaTypeM.isPresent()) javaType = javaTypeM.get(); else { @@ -168,7 +168,8 @@ public void read() { // collection definitely expected but not received, schedule manipulation phase if (!context.seenPhase(MANIPULATING_TO_LIST)) { // and add converters for the generic subtype - SerializersOnBlackboard.get(blackboard).addExpectedTypeSerializers( config.getTypeRegistry().getSerializersForType(genericSubType) ); + SerializersOnBlackboard.get(blackboard).addExpectedTypeSerializers( + config.getTypeRegistry().getSerializersForType(genericSubType, context.subpath("<>", null, null)) ); context.phaseInsert(MANIPULATING_TO_LIST, YomlContext.StandardPhases.HANDLING_TYPE); context.phaseAdvance(); } else { @@ -184,7 +185,8 @@ public void read() { if (!context.seenPhase(MANIPULATING_TO_LIST)) { // first apply manipulations, // and add converters for the generic subtype - SerializersOnBlackboard.get(blackboard).addExpectedTypeSerializers( config.getTypeRegistry().getSerializersForType(genericSubType) ); + SerializersOnBlackboard.get(blackboard).addExpectedTypeSerializers( + config.getTypeRegistry().getSerializersForType(genericSubType, context.subpath("<>", null, null)) ); context.phaseInsert(MANIPULATING_TO_LIST, StandardPhases.MANIPULATING, YomlContext.StandardPhases.HANDLING_TYPE); context.phaseAdvance(); return; @@ -305,8 +307,7 @@ protected void readIterableInto(Collection joq, Iterable yo) { int index = 0; for (Object yi: yo) { - jo.add(converter.read( new YomlContextForRead(yi, context.getJsonPath()+"["+index+"]", genericSubType, context) )); - + jo.add(converter.read( ((YomlContextForRead)context).subpath("["+index+"]", yi, genericSubType) )); index++; } } @@ -407,7 +408,7 @@ public void write() { int index = 0; for (Object ji: (Iterable)getJavaObject()) { - list.add(converter.write( new YomlContextForWrite(ji, context.getJsonPath()+"["+index+"]", genericSubType, context) )); + list.add(converter.write( ((YomlContextForWrite)context).subpath("/["+index+"]", ji, genericSubType) )); index++; } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java index 3bf5f199c1..f3878a6fdd 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java @@ -121,7 +121,7 @@ public void read() { actualBaseTypeName = expectedBaseTypeName; expectedJavaType = oldExpectedJavaType; expectedBaseTypeName = oldExpectedBaseTypeName; - if (actualType==null) actualType = config.getTypeRegistry().getJavaTypeMaybe(actualBaseTypeName).orNull(); + if (actualType==null) actualType = config.getTypeRegistry().getJavaTypeMaybe(actualBaseTypeName, context).orNull(); if (actualType==null) return; //we don't recognise the type if (!Map.class.isAssignableFrom(actualType)) return; //it's not a map @@ -165,9 +165,9 @@ public void read() { ev1 = m.get("value"); } if (ek2==null) { - ek2 = converter.read(new YomlContextForRead(ek1, newPath+"/@key", genericKeySubType, context)); + ek2 = converter.read( ((YomlContextForRead)context).subpath("/@key["+i+"]", ek1, genericKeySubType) ); } - ev2 = converter.read(new YomlContextForRead(ev1, newPath+"/@value", genericValueSubType, context)); + ev2 = converter.read( ((YomlContextForRead)context).subpath("/@value["+i+"]", ev1, genericValueSubType) ); jom.put(ek2, ev2); } else { // must be an entry set, so invalid @@ -180,7 +180,7 @@ public void read() { } else if (value instanceof Map) { for (Map.Entry me: ((Map)value).entrySet()) { - Object v = converter.read(new YomlContextForRead(me.getValue(), context.getJsonPath()+"/"+me.getKey(), genericValueSubType, context)); + Object v = converter.read( ((YomlContextForRead)context).subpath("/"+me.getKey(), me.getValue(), genericValueSubType) ); jom.put(me.getKey(), v); } @@ -294,7 +294,7 @@ public void write() { MutableMap out = MutableMap.of(); for (Map.Entry me: jo.entrySet()) { - Object v = converter.write(new YomlContextForWrite(me.getValue(), context.getJsonPath()+"/"+me.getKey(), genericValueSubType, context)); + Object v = converter.write( ((YomlContextForWrite)context).subpath("/"+me.getKey(), me.getValue(), genericValueSubType) ); out.put(me.getKey(), v); } isEmpty = out.isEmpty(); @@ -304,12 +304,12 @@ public void write() { int i=0; MutableList out = MutableList.of(); for (Map.Entry me: jo.entrySet()) { - Object v = converter.write(new YomlContextForWrite(me.getValue(), context.getJsonPath()+"["+i+"]/value", genericValueSubType, context)); + Object v = converter.write( ((YomlContextForWrite)context).subpath("/@value["+i+"]", me.getValue(), genericValueSubType) ); if (me.getKey() instanceof String) { out.add(MutableMap.of(me.getKey(), v)); } else { - Object k = converter.write(new YomlContextForWrite(me.getKey(), context.getJsonPath()+"["+i+"]/key", genericValueSubType, context)); + Object k = converter.write( ((YomlContextForWrite)context).subpath("/@key["+i+"]", me.getKey(), genericKeySubType) ); out.add(MutableMap.of("key", k, "value", v)); } i++; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java index 57a0712c09..234516d56d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java @@ -60,7 +60,7 @@ public void read() { expectedJavaType = getExpectedTypeJava(); if (!isJsonComplexObject(getYamlObject()) && (expectedJavaType!=null || isJsonMarkerTypeExpected())) { - // if it's not a json map/list (and not a primitive) than try a coercion; + // try coercion as long as it's not a json map/list, and we've got an expectation // maybe a bit odd to call that "primitive" but it is primitive in the sense it is pass-through unparsed value = tryCoerceAndNoteError(getYamlObject(), expectedJavaType); } @@ -68,7 +68,7 @@ public void read() { if (value.isAbsent()) { String typeName = readingTypeFromFieldOrExpected(); if (typeName==null) return; - expectedJavaType = config.getTypeRegistry().getJavaTypeMaybe(typeName).orNull(); + expectedJavaType = config.getTypeRegistry().getJavaTypeMaybe(typeName, context).orNull(); if (expectedJavaType==null) expectedJavaType = getSpecialKnownTypeName(typeName); // could restrict read coercion to basic types as follows, but no harm in trying to coerce if it's // a value map, unless the target is a special json which will be handled by another serializer @@ -138,7 +138,7 @@ public void write() { if (!isJsonPrimitiveObject(jOut)) return; String typeName = config.getTypeRegistry().getTypeName(jIn); - if (addSerializersForDiscoveredRealType(typeName)) { + if (addSerializersForDiscoveredRealType(typeName, true)) { // if new serializers, bail out and we'll re-run context.phaseRestart(); return; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java index 40ad156574..5eed07d9bd 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeWorkerAbstract.java @@ -55,12 +55,13 @@ protected boolean canDoWrite() { /** invoked on read and write to apply the appropriate serializers one the real type is known, * e.g. by looking up in registry. name of type will not be null but if it equals the java type * that may mean that annotation-scanning is appropriate. */ - protected boolean addSerializersForDiscoveredRealType(@Nullable String type) { + protected boolean addSerializersForDiscoveredRealType(@Nullable String type, boolean isRootType) { if (type!=null) { // (if null, we were writing what was expected, and we'll have added from expected type serializers) if (!type.equals(context.getExpectedType())) { if (putLabelOnBlackboard("discovered-type="+type, true)) { - SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.getTypeRegistry().getSerializersForType(type)); + SerializersOnBlackboard.get(blackboard).addInstantiatedTypeSerializers(config.getTypeRegistry().getSerializersForType(type, + isRootType ? context : context.subpath("...", null, null) )); return true; } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java index 3fd7cf7a34..a5c405a55e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java @@ -134,7 +134,7 @@ private void initWrite(YomlContextForWrite context, YomlConverter converter) { public Class getExpectedTypeJava() { String et = context.getExpectedType(); if (Strings.isBlank(et)) return null; - Class ett = config.getTypeRegistry().getJavaTypeMaybe(et).orNull(); + Class ett = config.getTypeRegistry().getJavaTypeMaybe(et, context).orNull(); if (Object.class.equals(ett)) return null; return ett; } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java index 8b54bf029e..03c53e6be9 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/MockYomlTypeRegistry.java @@ -23,6 +23,8 @@ import java.util.Map; import java.util.Set; +import javax.annotation.Nullable; + import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; @@ -35,6 +37,9 @@ import org.apache.brooklyn.util.yoml.YomlTypeRegistry; import org.apache.brooklyn.util.yoml.internal.ConstructionInstruction; import org.apache.brooklyn.util.yoml.internal.ConstructionInstructions; +import org.apache.brooklyn.util.yoml.internal.YomlContext; +import org.apache.brooklyn.util.yoml.internal.YomlContextForRead; +import org.apache.brooklyn.util.yoml.internal.YomlConverter; import org.apache.brooklyn.util.yoml.internal.YomlUtils; import com.google.common.collect.Iterables; @@ -69,15 +74,22 @@ public Object newInstance(String typeName, Yoml yoml) { } @Override public Maybe newInstanceMaybe(String typeName, Yoml yoml) { + return newInstanceMaybe(typeName, yoml, null); + } + + @Override + public Maybe newInstanceMaybe(String typeName, Yoml yoml, @Nullable YomlContext yomlContext) { MockRegisteredType type = types.get(typeName); if (type!=null && type.yamlDefinition!=null) { String parentTypeName = type.parentType; if (type.parentType==null && type.javaType!=null) parentTypeName = getDefaultTypeNameOfClass(type.javaType); - return Maybe.of(yoml.readFromYamlObject(type.yamlDefinition, parentTypeName)); + return Maybe.of(new YomlConverter(yoml.getConfig()).read( new YomlContextForRead(type.yamlDefinition, "", parentTypeName, null).constructionInstruction(yomlContext.getConstructionInstruction()) )); + // have to do the above instead of below to ensure construction instruction is passed + // return Maybe.of(yoml.readFromYamlObject(type.yamlDefinition, parentTypeName)); } Maybe> javaType = getJavaTypeInternal(type, typeName); - ConstructionInstruction constructor = yoml.getConfig().getConstructionInstruction(); + ConstructionInstruction constructor = yomlContext==null ? null : yomlContext.getConstructionInstruction(); if (javaType.isAbsent() && constructor==null) { if (type==null) return Maybe.absent("Unknown type `"+typeName+"`"); return Maybe.absent(new IllegalStateException("Incomplete hierarchy for "+type, ((Maybe.Absent)javaType).getException())); @@ -93,7 +105,7 @@ protected static Maybe> maybeClass(Class clazz) { } @Override - public Maybe> getJavaTypeMaybe(String typeName) { + public Maybe> getJavaTypeMaybe(String typeName, @Nullable YomlContext contextIgnoredInMock) { if (typeName==null) return Maybe.absent(); // strip generics here if (typeName.indexOf('<')>0) typeName = typeName.substring(0, typeName.indexOf('<')); @@ -104,7 +116,7 @@ protected Maybe> getJavaTypeInternal(MockRegisteredType registeredType, Maybe> result = Maybe.absent(); if (result.isAbsent() && registeredType!=null) result = maybeClass(registeredType.javaType); - if (result.isAbsent() && registeredType!=null) result = getJavaTypeMaybe(registeredType.parentType); + if (result.isAbsent() && registeredType!=null) result = getJavaTypeMaybe(registeredType.parentType, null); if (result.isAbsent()) { result = Maybe.absent("Unknown type '"+typeName+"' (no match available in mock library)"); @@ -146,7 +158,7 @@ public void put(String typeName, String yamlDefinition, List)yamlObject).remove("type"); if (!(type instanceof String)) throw new IllegalArgumentException("Mock requires key `type` with string value"); - Maybe> javaType = getJavaTypeMaybe((String)type); + Maybe> javaType = getJavaTypeMaybe((String)type, null); if (javaType.isAbsent()) throw new IllegalArgumentException("Mock cannot resolve parent type `"+type+"` in definition of `"+typeName+"`: "+ ((Maybe.Absent)javaType).getException()); @@ -179,7 +191,7 @@ protected String getDefaultTypeNameOfClass(Class type) { } @Override - public Iterable getSerializersForType(String typeName) { + public Iterable getSerializersForType(String typeName, YomlContext context) { Set result = MutableSet.of(); collectSerializers(typeName, result, MutableSet.of()); return result; From 02a67a7abe5619d292e96d401a404bf9ff4c6bee Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 29 Sep 2016 17:47:46 +0100 Subject: [PATCH 61/77] simplify logging --- .../util/yoml/internal/YomlConverter.java | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java index a756f410d6..09bb5f0b7e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java @@ -24,6 +24,7 @@ import org.apache.brooklyn.util.yoml.YomlRequirement; import org.apache.brooklyn.util.yoml.YomlSerializer; import org.apache.brooklyn.util.yoml.serializers.ReadingTypeOnBlackboard; +import org.apache.brooklyn.util.yoml.serializers.YamlKeysOnBlackboard; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,6 +35,9 @@ public class YomlConverter { private static final Logger log = LoggerFactory.getLogger(YomlConverter.class); private final YomlConfig config; + /** easy way at dev time to get trace logging to stdout info level */ + private static boolean FORCE_SHOW_TRACE_LOGGING = false; + public YomlConverter(YomlConfig config) { this.config = config; } @@ -56,6 +60,17 @@ public Object write(final YomlContextForWrite context) { return context.getYamlObject(); } + protected boolean isTraceDetailWanted() { + return log.isTraceEnabled() || FORCE_SHOW_TRACE_LOGGING; + } + protected void logTrace(String message) { + if (FORCE_SHOW_TRACE_LOGGING) { + log.info(message); + } else { + log.trace(message); + } + } + protected void loopOverSerializers(YomlContext context) { // TODO refactor further so we always pass the context Map blackboard = context.getBlackboard(); @@ -72,33 +87,34 @@ protected void loopOverSerializers(YomlContext context) { ReadingTypeOnBlackboard.get(blackboard); } - if (log.isTraceEnabled()) log.trace("YOML now looking at "+context.getJsonPath()+"/ = "+context.getJavaObject()+" <-> "+context.getYamlObject()+" ("+context.getExpectedType()+")"); + if (log.isTraceEnabled()) logTrace("YOML now looking at "+context.getJsonPath()+"/ = "+context.getJavaObject()+" <-> "+context.getYamlObject()+" ("+context.getExpectedType()+")"); while (context.phaseAdvance()) { while (context.phaseStepAdvance() bb: blackboard.entrySet()) { - log.trace(" "+bb.getKey()+": "+bb.getValue()); + logTrace(" "+bb.getKey()+": "+bb.getValue()); } } } YomlSerializer s = Iterables.get(serializers.getSerializers(), context.phaseCurrentStep()); if (context instanceof YomlContextForRead) { - if (log.isTraceEnabled()) log.trace("read "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" starting ("+context.phaseCurrent()+"."+context.phaseCurrentStep()+") "); + if (isTraceDetailWanted()) logTrace("read "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" starting ("+context.phaseCurrent()+"."+context.phaseCurrentStep()+") "); s.read((YomlContextForRead)context, this); - if (log.isTraceEnabled()) log.trace("read "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" ended: "+context.getYamlObject()); + if (isTraceDetailWanted()) logTrace("read "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" ended: "+context.getYamlObject()); + if (isTraceDetailWanted()) logTrace(" YKB: "+YamlKeysOnBlackboard.peek(blackboard)); } else { - if (log.isTraceEnabled()) log.trace("write "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" starting ("+context.phaseCurrent()+"."+context.phaseCurrentStep()+") "); + if (isTraceDetailWanted()) logTrace("write "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" starting ("+context.phaseCurrent()+"."+context.phaseCurrentStep()+") "); s.write((YomlContextForWrite)context, this); if (log.isDebugEnabled()) log.debug("write "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" ended: "+context.getYamlObject()); - if (log.isTraceEnabled()) log.trace("write "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" ended: "+context.getYamlObject()); + if (isTraceDetailWanted()) logTrace("write "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" ended: "+context.getYamlObject()); } } } - if (log.isTraceEnabled()) log.trace("YOML done looking at "+context.getJsonPath()+"/ = "+context.getJavaObject()+" <-> "+context.getYamlObject()); + if (isTraceDetailWanted()) logTrace("YOML done looking at "+context.getJsonPath()+"/ = "+context.getJavaObject()+" <-> "+context.getYamlObject()); checkCompletion(context); } From d0635d7a5e83859ff9ec6809cce3fe9a2b276edc Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 29 Sep 2016 17:48:38 +0100 Subject: [PATCH 62/77] when reading yoml if we've gotten a deferred Supplier just accept it --- .../util/yoml/serializers/InstantiateTypePrimitive.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java index 234516d56d..faa0e906a3 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypePrimitive.java @@ -59,7 +59,11 @@ public void read() { // not primitive; either should be coercible or should be of {type: ..., value: ...} format with type being the primitive expectedJavaType = getExpectedTypeJava(); - if (!isJsonComplexObject(getYamlObject()) && (expectedJavaType!=null || isJsonMarkerTypeExpected())) { + if (isDeferredValue(getYamlObject())) { + value = Maybe.of(getYamlObject()); + } + + if (value.isAbsent() && !isJsonComplexObject(getYamlObject()) && (expectedJavaType!=null || isJsonMarkerTypeExpected())) { // try coercion as long as it's not a json map/list, and we've got an expectation // maybe a bit odd to call that "primitive" but it is primitive in the sense it is pass-through unparsed value = tryCoerceAndNoteError(getYamlObject(), expectedJavaType); From 677eb79c267db036e91609efa1a0bf41df456894 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 29 Sep 2016 17:55:21 +0100 Subject: [PATCH 63/77] inherit annotations for renaming keys better --- .../apache/brooklyn/util/yoml/annotations/YomlRenameKey.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java index 598537659f..1a9c128533 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlRenameKey.java @@ -48,6 +48,7 @@ /** As {@link YomlRenameKey} with {@link YomlRenameKey#oldKeyName()} equals to .key */ @Retention(RUNTIME) @Target({ TYPE }) + @Inherited public @interface YomlRenameDefaultKey { /** The key name to change to when reading */ String value(); @@ -57,6 +58,7 @@ /** As {@link YomlRenameKey} with {@link YomlRenameKey#oldKeyName()} equals to .value */ @Retention(RUNTIME) @Target({ TYPE }) + @Inherited public @interface YomlRenameDefaultValue { /** The key name to change to when reading */ String value(); From 5827fbf03b19a2104d2a6bdadab06bdd179517ed Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 29 Sep 2016 17:55:41 +0100 Subject: [PATCH 64/77] correctly set defaults for fields and config in edge cases --- .../TopLevelConfigKeySerializer.java | 7 +++++++ .../serializers/TopLevelFieldSerializer.java | 20 ++++++++++++------- .../YomlSerializerComposition.java | 7 +++++++ .../yoml/tests/TopLevelConfigKeysTests.java | 12 ++++++++++- .../util/yoml/tests/TopLevelFieldsTests.java | 8 ++++++++ 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java index 19d56c71a0..e31e78134d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelConfigKeySerializer.java @@ -19,6 +19,7 @@ package org.apache.brooklyn.util.yoml.serializers; import java.lang.reflect.Field; +import java.util.Map; import java.util.Set; import org.apache.brooklyn.config.ConfigKey; @@ -135,6 +136,12 @@ protected void prepareTopLevelFields() { super.prepareTopLevelFields(); getTopLevelFieldsBlackboard().recordConfigKey(fieldName, configKey); } + + @Override + protected boolean setDefaultValue(Map fields, int keysMatched) { + // no need to set the default for config keys + return false; + } } @Override diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java index 52c5ba852e..b30f4cdbdf 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldSerializer.java @@ -188,7 +188,6 @@ public void read() { if (!readyForMainEvent()) return; if (!canDoRead()) return; if (!isYamlMap()) return; - if (!hasYamlKeysOnBlackboardRemaining()) return; boolean fieldsCreated = false; @@ -198,6 +197,7 @@ public void read() { // create the fields if needed; FieldsInFieldsMap will remove (even if empty) fieldsCreated = true; fields = MutableMap.of(); + // fine (needed even) to write this even with empty getYamlKeysOnBlackboardInitializedFromYamlMap().putNewKey(getKeyNameForMapOfGeneralValues(), fields); } @@ -225,14 +225,11 @@ public void read() { keysMatched++; } } + if (keysMatched==0) { - // set a default if there is one - Maybe value = getTopLevelFieldsBlackboard().getDefault(fieldName); - if (value.isPresentAndNonNull()) { - fields.put(fieldName, value.get()); - keysMatched++; - } + if (setDefaultValue(fields, keysMatched)) keysMatched++; } + if (fieldsCreated || keysMatched>0) { // repeat this manipulating phase if we set any keys, so that remapping can apply getTopLevelFieldsBlackboard().setFieldDone(fieldName); @@ -240,6 +237,13 @@ public void read() { } } + protected boolean setDefaultValue(Map fields, int keysMatched) { + Maybe value = getTopLevelFieldsBlackboard().getDefault(fieldName); + if (!value.isPresentAndNonNull()) return false; + fields.put(fieldName, value.get()); + return true; + } + public void write() { if (!readyForMainEvent()) return; if (!isYamlMap()) return; @@ -287,6 +291,8 @@ public void write() { getOutputYamlMap().put(getKeyNameForMapOfGeneralValues(), fields); // rerun this phase again, as we've changed it context.phaseInsert(StandardPhases.MANIPULATING); + } else if (fields.isEmpty()) { + getOutputYamlMap().remove(getKeyNameForMapOfGeneralValues()); } } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java index a5c405a55e..fa32d63359 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Future; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.exceptions.Exceptions; @@ -37,6 +38,8 @@ import org.apache.brooklyn.util.yoml.internal.YomlUtils; import org.apache.brooklyn.util.yoml.internal.YomlUtils.JsonMarker; +import com.google.common.base.Supplier; + public abstract class YomlSerializerComposition implements YomlSerializer { protected abstract YomlSerializerWorker newWorker(); @@ -113,6 +116,10 @@ protected boolean isJsonPureObject(Object o) { return YomlUtils.JsonMarker.isPureJson(o); } + protected boolean isDeferredValue(Object o) { + return o instanceof Supplier || o instanceof Future; + } + private void initRead(YomlContextForRead context, YomlConverter converter) { if (this.context!=null) throw new IllegalStateException("Already initialized, for "+context); this.context = context; diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java index ed26f46758..460f897e2c 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java @@ -334,10 +334,20 @@ static class SDefault extends S3 { @Test public void testConfigKeyDefaultsReadButNotWritten() { YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("s-default", SDefault.class); - y.reading("{}", "s-default").writing(new SDefault(MutableMap.of()), "s-default") + y.reading("{ }", "s-default").writing(new SDefault(MutableMap.of()), "s-default") .doReadWriteAssertingJsonMatch(); Asserts.assertSize( ((SDefault)y.lastReadResult).keysSuppliedToConstructorForTestAssertions.keySet(), 0 ); } + @Test + public void testConfigKeyDefaultsReadButNotWrittenWithAtLeastOneConfigValueSupplied() { + // behaviour is different in this case + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations("s-default", SDefault.class); + y.reading("{ k2: x }", "s-default").writing(new SDefault(MutableMap.of("k2", "x")), "s-default") + .doReadWriteAssertingJsonMatch(); + + Asserts.assertSize( ((SDefault)y.lastReadResult).keysSuppliedToConstructorForTestAssertions.keySet(), 1 ); + } + } diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelFieldsTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelFieldsTests.java index 905b938703..b16bd93a08 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelFieldsTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelFieldsTests.java @@ -131,6 +131,14 @@ public void testCommonDefault() { doReadWriteAssertingJsonMatch(); } + @Test + public void testEmptyDefault() { + commonTopLevelFieldFixtureKeyNameAlias(", defaultValue: { type: string, value: bob }"). + reading( "{}", "shape" ). + writing( new Shape().name("bob"), "shape" ). + doReadWriteAssertingJsonMatch(); + } + @Test public void testNameNotRequired() { commonTopLevelFieldFixtureKeyNameAlias(). From a94ec578b59a2f674e2ff0a417bfe6b7713bddf1 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 29 Sep 2016 17:54:36 +0100 Subject: [PATCH 65/77] use type tokens for structured (map, list) config keys so yoml can infer correctly --- .../brooklyn/core/config/ListConfigKey.java | 29 ++++++++++++++++-- .../brooklyn/core/config/MapConfigKey.java | 26 +++++++++++++++- .../brooklyn/core/config/SetConfigKey.java | 30 +++++++++++++++++-- .../internal/AbstractCollectionConfigKey.java | 5 ++++ .../internal/AbstractStructuredConfigKey.java | 6 ++++ 5 files changed, 89 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/config/ListConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/config/ListConfigKey.java index 06a29bd579..ee2fd8eae3 100644 --- a/core/src/main/java/org/apache/brooklyn/core/config/ListConfigKey.java +++ b/core/src/main/java/org/apache/brooklyn/core/config/ListConfigKey.java @@ -18,6 +18,8 @@ */ package org.apache.brooklyn.core.config; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -30,6 +32,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.reflect.TypeToken; + /** A config key representing a list of values. * If a value is set on this key, it is _added_ to the list. * (With a warning is issued if a collection is passed in.) @@ -67,11 +71,30 @@ public ListConfigKey(Class subType, String name, String description) { this(subType, name, description, null); } - @SuppressWarnings({ "unchecked", "rawtypes" }) public ListConfigKey(Class subType, String name, String description, List defaultValue) { - super((Class)List.class, subType, name, description, (List)defaultValue); + super(typeTokenListWithSubtype(subType), subType, name, description, defaultValue); } + @SuppressWarnings("unchecked") + private static TypeToken> typeTokenListWithSubtype(final Class subType) { + return (TypeToken>) TypeToken.of(new ParameterizedType() { + @Override + public Type getRawType() { + return List.class; + } + + @Override + public Type getOwnerType() { + return null; + } + + @Override + public Type[] getActualTypeArguments() { + return new Type[] { subType }; + } + }); + } + @Override protected List merge(boolean unmodifiable, Iterable... sets) { MutableList result = MutableList.of(); @@ -87,7 +110,7 @@ public static class ListModifications extends StructuredModifications { /** when passed as a value to a ListConfigKey, causes each of these items to be added. * if you have just one, no need to wrap in a mod. */ // to prevent confusion (e.g. if a list is passed) we require two objects here. - public static final ListModification add(final T o1, final T o2, final T ...oo) { + public static final ListModification add(final T o1, final T o2, @SuppressWarnings("unchecked") final T ...oo) { List l = new ArrayList(); l.add(o1); l.add(o2); for (T o: oo) l.add(o); diff --git a/core/src/main/java/org/apache/brooklyn/core/config/MapConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/config/MapConfigKey.java index a0b9bd7627..5529afa268 100644 --- a/core/src/main/java/org/apache/brooklyn/core/config/MapConfigKey.java +++ b/core/src/main/java/org/apache/brooklyn/core/config/MapConfigKey.java @@ -20,6 +20,8 @@ import static com.google.common.base.Preconditions.checkNotNull; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -65,9 +67,11 @@ public static Builder builder(MapConfigKey key) { return new Builder(key); } + @SuppressWarnings("serial") public static class Builder extends BasicConfigKey.Builder,Builder> { protected Class subType; + @SuppressWarnings("unchecked") public Builder(TypeToken subType, String name) { super(new TypeToken>() {}, name); this.subType = (Class) subType.getRawType(); @@ -117,7 +121,7 @@ public String getDescription() { } protected MapConfigKey(Builder builder) { - super((Class)Map.class, + super(typeTokenMapWithSubtype(builder.subType), checkNotNull(builder.subType, "subType"), checkNotNull(builder.name, "name"), builder.description, @@ -131,6 +135,26 @@ protected MapConfigKey(Builder builder) { this.constraint = checkNotNull(builder.constraint, "constraint"); } + @SuppressWarnings("unchecked") + private static TypeToken> typeTokenMapWithSubtype(final Class subType) { + return (TypeToken>) TypeToken.of(new ParameterizedType() { + @Override + public Type getRawType() { + return Map.class; + } + + @Override + public Type getOwnerType() { + return null; + } + + @Override + public Type[] getActualTypeArguments() { + return new Type[] { String.class, subType }; + } + }); + } + public MapConfigKey(Class subType, String name) { this(subType, name, name, null); } diff --git a/core/src/main/java/org/apache/brooklyn/core/config/SetConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/config/SetConfigKey.java index f199c381de..c4354d9766 100644 --- a/core/src/main/java/org/apache/brooklyn/core/config/SetConfigKey.java +++ b/core/src/main/java/org/apache/brooklyn/core/config/SetConfigKey.java @@ -18,9 +18,12 @@ */ package org.apache.brooklyn.core.config; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -29,6 +32,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.reflect.TypeToken; + /** A config key representing a set of values. * If a value is set using this *typed* key, it is _added_ to the set * (with a warning issued if a collection is passed in). @@ -58,11 +63,30 @@ public SetConfigKey(Class subType, String name, String description) { this(subType, name, description, null); } - @SuppressWarnings({ "unchecked", "rawtypes" }) public SetConfigKey(Class subType, String name, String description, Set defaultValue) { - super((Class)Set.class, subType, name, description, defaultValue); + super(typeTokenSetWithSubtype(subType), subType, name, description, defaultValue); } + @SuppressWarnings("unchecked") + private static TypeToken> typeTokenSetWithSubtype(final Class subType) { + return (TypeToken>) TypeToken.of(new ParameterizedType() { + @Override + public Type getRawType() { + return Set.class; + } + + @Override + public Type getOwnerType() { + return null; + } + + @Override + public Type[] getActualTypeArguments() { + return new Type[] { subType }; + } + }); + } + @Override protected Set merge(boolean unmodifiable, Iterable... sets) { MutableSet result = MutableSet.of(); @@ -78,7 +102,7 @@ public static class SetModifications extends StructuredModifications { /** when passed as a value to a SetConfigKey, causes each of these items to be added. * if you have just one, no need to wrap in a mod. */ // to prevent confusion (e.g. if a set is passed) we require two objects here. - public static final SetModification add(final T o1, final T o2, final T ...oo) { + public static final SetModification add(final T o1, final T o2, @SuppressWarnings("unchecked") final T ...oo) { Set l = new LinkedHashSet(); l.add(o1); l.add(o2); for (T o: oo) l.add(o); diff --git a/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractCollectionConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractCollectionConfigKey.java index cc536e8144..45db9e4cc1 100644 --- a/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractCollectionConfigKey.java +++ b/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractCollectionConfigKey.java @@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory; import com.google.common.collect.Iterables; +import com.google.common.reflect.TypeToken; public abstract class AbstractCollectionConfigKey, V> extends AbstractStructuredConfigKey { @@ -42,6 +43,10 @@ public AbstractCollectionConfigKey(Class type, Class subType, String name, super(type, subType, name, description, defaultValue); } + public AbstractCollectionConfigKey(TypeToken type, Class subType, String name, String description, T defaultValue) { + super(type, subType, name, description, defaultValue); + } + public ConfigKey subKey() { String subName = Identifiers.makeRandomId(8); return new SubElementConfigKey(this, subType, getName()+"."+subName, "element of "+getName()+", uid "+subName, null); diff --git a/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractStructuredConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractStructuredConfigKey.java index f29c0acc37..9f47eaab0e 100644 --- a/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractStructuredConfigKey.java +++ b/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractStructuredConfigKey.java @@ -29,6 +29,7 @@ import org.apache.brooklyn.util.exceptions.Exceptions; import com.google.common.collect.Maps; +import com.google.common.reflect.TypeToken; public abstract class AbstractStructuredConfigKey extends BasicConfigKey implements StructuredConfigKey { @@ -41,6 +42,11 @@ public AbstractStructuredConfigKey(Class type, Class subType, String name, this.subType = subType; } + public AbstractStructuredConfigKey(TypeToken type, Class subType, String name, String description, T defaultValue) { + super(type, name, description, defaultValue); + this.subType = subType; + } + protected ConfigKey subKey(String subName) { return subKey(subName, "sub-element of " + getName() + ", named " + subName); } From d6555efb3c45376a13100a528864c17bca9b4efc Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 29 Sep 2016 17:55:57 +0100 Subject: [PATCH 66/77] test ssh sensors and effectors from yoml --- .../brooklyn/HttpRequestSensorYamlTest.java | 6 +- .../yoml/demos/YomlSensorEffectorTest.java | 83 +++++++++++++++++++ .../ssh-sensor-effector-yoml-demo.yaml | 41 +++++++++ .../brooklyn/core/effector/AddEffector.java | 6 ++ .../brooklyn/core/effector/AddSensor.java | 2 + .../core/effector/ssh/SshCommandEffector.java | 6 +- .../core/entity/BrooklynConfigKeys.java | 21 +++-- .../brooklyn/core/sensor/StaticSensor.java | 1 - .../core/sensor/ssh/SshCommandSensor.java | 4 +- 9 files changed, 155 insertions(+), 15 deletions(-) create mode 100644 camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/demos/YomlSensorEffectorTest.java create mode 100644 camp/camp-brooklyn/src/test/resources/ssh-sensor-effector-yoml-demo.yaml diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpRequestSensorYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpRequestSensorYamlTest.java index d8e8416853..ec900dd82f 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpRequestSensorYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/HttpRequestSensorYamlTest.java @@ -32,7 +32,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.common.collect.Iterables; @@ -41,12 +41,12 @@ public class HttpRequestSensorYamlTest extends AbstractYamlTest { private static final Logger log = LoggerFactory.getLogger(HttpRequestSensorYamlTest.class); final static AttributeSensor SENSOR_STRING = Sensors.newStringSensor("aString"); - final static String TARGET_TYPE = "java.lang.String"; + final static String TARGET_TYPE = String.class.getName(); private TestHttpServer server; private String serverUrl; - @BeforeClass(alwaysRun=true) + @BeforeMethod(alwaysRun = true) @Override public void setUp() throws Exception { super.setUp(); diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/demos/YomlSensorEffectorTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/demos/YomlSensorEffectorTest.java new file mode 100644 index 0000000000..4bd458f485 --- /dev/null +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/demos/YomlSensorEffectorTest.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.camp.yoml.demos; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; +import org.apache.brooklyn.camp.yoml.types.YomlInitializers; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.Effectors; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.EntityAsserts; +import org.apache.brooklyn.core.sensor.Sensors; +import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess; +import org.apache.brooklyn.util.collections.MutableMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class YomlSensorEffectorTest extends AbstractYamlTest { + + private static final Logger log = LoggerFactory.getLogger(YomlSensorEffectorTest.class); + + protected EmptySoftwareProcess startApp() throws Exception { + YomlInitializers.install(mgmt()); + + Entity app = createAndStartApplication(loadYaml("ssh-sensor-effector-yoml-demo.yaml")); + waitForApplicationTasks(app); + + log.info("App started:"); + Entities.dumpInfo(app); + + EmptySoftwareProcess entity = (EmptySoftwareProcess) app.getChildren().iterator().next(); + return entity; + } + + @Test(groups="Integration") + public void testBasicEffector() throws Exception { + EmptySoftwareProcess entity = startApp(); + + String name = entity.getConfig(ConfigKeys.newStringConfigKey("name")); + Assert.assertEquals(name, "bob"); + + Task hi = entity.invoke(Effectors.effector(String.class, "echo-hi").buildAbstract(), MutableMap.of()); + Assert.assertEquals(hi.get().trim(), "hi"); + } + + @Test(groups="Integration") + public void testConfigEffector() throws Exception { + EmptySoftwareProcess entity = startApp(); + + String name = entity.getConfig(ConfigKeys.newStringConfigKey("name")); + Assert.assertEquals(name, "bob"); + + Task hi = entity.invoke(Effectors.effector(String.class, "echo-hi-name-from-config").buildAbstract(), MutableMap.of()); + Assert.assertEquals(hi.get().trim(), "hi bob"); + } + + @Test(groups="Integration") + public void testSensorAndEffector() throws Exception { + EmptySoftwareProcess entity = startApp(); + + EntityAsserts.assertAttributeChangesEventually(entity, Sensors.newStringSensor("date")); + } + +} \ No newline at end of file diff --git a/camp/camp-brooklyn/src/test/resources/ssh-sensor-effector-yoml-demo.yaml b/camp/camp-brooklyn/src/test/resources/ssh-sensor-effector-yoml-demo.yaml new file mode 100644 index 0000000000..1bc996962f --- /dev/null +++ b/camp/camp-brooklyn/src/test/resources/ssh-sensor-effector-yoml-demo.yaml @@ -0,0 +1,41 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +location: localhost + +services: + +- type: org.apache.brooklyn.entity.software.base.EmptySoftwareProcess + + brooklyn.initializers: + echo-hi: + type: ssh-effector + command: echo hi + echo-hi-name-from-config: + type: ssh-effector + command: echo hi ${NAME} + env: + NAME: $brooklyn:config("name") + date: + type: ssh-sensor + command: date + period: 100ms + + brooklyn.config: + name: bob diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java index 9590bcff57..d8ed509590 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddEffector.java @@ -31,7 +31,10 @@ import org.apache.brooklyn.core.effector.Effectors.EffectorBuilder; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.yoml.YomlConfigBagConstructor; import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultKey; import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; @@ -56,6 +59,9 @@ * * @since 0.7.0 */ @Beta +@YomlConfigBagConstructor("") +@YomlAllFieldsTopLevel +@YomlRenameDefaultKey("name") public class AddEffector implements EntityInitializer { public static final ConfigKey EFFECTOR_NAME = ConfigKeys.newStringConfigKey("name"); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java index 3bdb74593f..508ca5c5f3 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java @@ -36,6 +36,7 @@ import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultKey; import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; @@ -52,6 +53,7 @@ @Beta @YomlConfigBagConstructor("") @YomlAllFieldsTopLevel +@YomlRenameDefaultKey("name") public class AddSensor implements EntityInitializer { public static final ConfigKey SENSOR_NAME = ConfigKeys.newStringConfigKey("name", "The name of the sensor to create"); diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java index 3940b504e3..2df1a51a54 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/ssh/SshCommandEffector.java @@ -61,7 +61,7 @@ public final class SshCommandEffector extends AddEffector { public static final ConfigKey EFFECTOR_COMMAND = ConfigKeys.newStringConfigKey("command"); public static final ConfigKey EFFECTOR_EXECUTION_DIR = SshCommandSensor.SENSOR_EXECUTION_DIR; @Alias(preferred="env", value={"vars","variables","environment"}) - public static final MapConfigKey EFFECTOR_SHELL_ENVIRONMENT = BrooklynConfigKeys.SHELL_ENVIRONMENT; + public static final MapConfigKey EFFECTOR_SHELL_ENVIRONMENT = BrooklynConfigKeys.SHELL_ENVIRONMENT_STRING_VALUES; public static enum ExecutionTarget { ENTITY, @@ -90,7 +90,7 @@ public static EffectorBuilder newEffectorBuilder(ConfigBag params) { protected static class Body extends EffectorBody { private final Effector effector; private final String command; - private final Map shellEnv; + private final Map shellEnv; private final String executionDir; private final ExecutionTarget executionTarget; @@ -165,7 +165,7 @@ public SshEffectorTaskFactory makePartialTaskFactory(ConfigBag params) { if (shellEnv != null) env.putAll(shellEnv); // Add the shell environment entries from our invocation - Map effectorEnv = params.get(EFFECTOR_SHELL_ENVIRONMENT); + Map effectorEnv = params.get(EFFECTOR_SHELL_ENVIRONMENT); if (effectorEnv != null) env.putAll(effectorEnv); // Try to resolve the configuration in the env Map diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java b/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java index a97a5d9906..1fa0140e93 100644 --- a/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java +++ b/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java @@ -125,12 +125,21 @@ public class BrooklynConfigKeys { .runtimeInheritance(BasicConfigInheritance.NOT_REINHERITED) .build(); - public static final MapConfigKey SHELL_ENVIRONMENT = new MapConfigKey.Builder(Object.class, "shell.env") - .description("Map of environment variables to pass to the runtime shell. Non-string values are serialized to json before passed to the shell.") - .defaultValue(ImmutableMap.of()) - .typeInheritance(BasicConfigInheritance.DEEP_MERGE) - .runtimeInheritance(BasicConfigInheritance.NOT_REINHERITED_ELSE_DEEP_MERGE) - .build(); + public static final MapConfigKey SHELL_ENVIRONMENT_STRING_VALUES = new MapConfigKey.Builder(String.class, "shell.env") + .description("Map of environment variables to pass to the runtime shell") + .defaultValue(ImmutableMap.of()) + .typeInheritance(BasicConfigInheritance.DEEP_MERGE) + .runtimeInheritance(BasicConfigInheritance.NOT_REINHERITED_ELSE_DEEP_MERGE) + .build(); + + public static final MapConfigKey SHELL_ENVIRONMENT_OBJECT_VALUE = new MapConfigKey.Builder(Object.class, "shell.env") + .description("Map of environment variables to pass to the runtime shell. Non-string values are serialized to json before passed to the shell.") + .defaultValue(ImmutableMap.of()) + .typeInheritance(BasicConfigInheritance.DEEP_MERGE) + .runtimeInheritance(BasicConfigInheritance.NOT_REINHERITED_ELSE_DEEP_MERGE) + .build(); + + public static final MapConfigKey SHELL_ENVIRONMENT = SHELL_ENVIRONMENT_OBJECT_VALUE; // TODO these dirs should also not be reinherited at runtime public static final AttributeSensorAndConfigKey INSTALL_DIR = new TemplatedStringAttributeSensorAndConfigKey("install.dir", "Directory for this software to be installed in", diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java index 935db529d3..64fa47155b 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java @@ -50,7 +50,6 @@ * However when the source is another sensor, * consider using {@link Propagator} which listens for changes instead. */ @Alias("static-sensor") -@YomlRenameDefaultKey("name") public class StaticSensor extends AddSensor { private static final Logger log = LoggerFactory.getLogger(StaticSensor.class); diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java index 891f12264a..9b91573f65 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java @@ -69,11 +69,11 @@ public final class SshCommandSensor extends AddSensor { + "if not supplied, executes in the entity's run dir (or home dir if no run dir is defined); " + "use '~' to always execute in the home dir, or 'custom-feed/' to execute in a custom-feed dir relative to the run dir"); @Alias(preferred="env", value={"vars","variables","environment"}) - public static final MapConfigKey SENSOR_SHELL_ENVIRONMENT = BrooklynConfigKeys.SHELL_ENVIRONMENT; + public static final MapConfigKey SENSOR_SHELL_ENVIRONMENT = BrooklynConfigKeys.SHELL_ENVIRONMENT_STRING_VALUES; protected final String command; protected final String executionDir; - protected final Map sensorEnv; + protected final Map sensorEnv; public SshCommandSensor(final ConfigBag params) { super(params); From 3b39790f7297b316c7375236492d4010f2b70040 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 30 Sep 2016 11:08:09 +0100 Subject: [PATCH 67/77] support lists better in yoml, and better logging output --- .../yoml/annotations/YomlFromPrimitive.java | 2 +- .../internal/SerializersOnBlackboard.java | 4 +- .../yoml/internal/YomlContextForWrite.java | 2 +- .../util/yoml/internal/YomlConverter.java | 78 +++++++++++++++---- .../serializers/ConvertFromPrimitive.java | 6 +- .../yoml/serializers/ConvertSingletonMap.java | 4 +- ...antiateTypeFromRegistryUsingConfigMap.java | 1 + .../yoml/serializers/InstantiateTypeMap.java | 4 +- .../yoml/serializers/RenameKeySerializer.java | 3 +- .../serializers/TopLevelFieldsBlackboard.java | 6 +- .../serializers/YamlKeysOnBlackboard.java | 9 ++- .../YomlSerializerComposition.java | 6 +- .../yoml/tests/ConvertSingletonMapTests.java | 48 ++++++++++-- 13 files changed, 139 insertions(+), 34 deletions(-) diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java index 4d9db9246f..2d627bc7f0 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/annotations/YomlFromPrimitive.java @@ -28,7 +28,7 @@ import org.apache.brooklyn.util.yoml.serializers.ConvertFromPrimitive; /** - * Indicates that a class can be yoml-serialized as a primitive + * Indicates that a class can be yoml-serialized as a primitive or list * reflecting a single field in the object which will take the primitive value. *

* If no {@link #keyToInsert()} is supplied the value is set under the key diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java index 40696e1bbe..6183bff940 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/SerializersOnBlackboard.java @@ -96,7 +96,7 @@ public static boolean isAddedByTypeInstantiation(Map blackboard, } public String toString() { - return super.toString()+"["+preSerializers.size()+"@pre,"+instantiatedTypeSerializers.size()+"@inst,"+ - expectedTypeSerializers.size()+"@exp,"+postSerializers.size()+"@post]"; + return super.toString()+"["+preSerializers.size()+" pre,"+instantiatedTypeSerializers.size()+" inst,"+ + expectedTypeSerializers.size()+" exp,"+postSerializers.size()+" post]"; } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForWrite.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForWrite.java index 9b89de7b5e..8762506f4f 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForWrite.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlContextForWrite.java @@ -27,7 +27,7 @@ public YomlContextForWrite(Object javaObject, String jsonPath, String expectedTy @Override public YomlContextForWrite subpath(String subpath, Object newItem, String optionalType) { - return new YomlContextForWrite(newItem, getJsonPath()+"/"+subpath, optionalType, this); + return new YomlContextForWrite(newItem, getJsonPath()+subpath, optionalType, this); } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java index 09bb5f0b7e..93ad113406 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/internal/YomlConverter.java @@ -19,12 +19,15 @@ package org.apache.brooklyn.util.yoml.internal; import java.util.Map; +import java.util.Objects; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.yoml.YomlConfig; import org.apache.brooklyn.util.yoml.YomlRequirement; import org.apache.brooklyn.util.yoml.YomlSerializer; +import org.apache.brooklyn.util.yoml.serializers.InstantiateTypeFromRegistry; import org.apache.brooklyn.util.yoml.serializers.ReadingTypeOnBlackboard; -import org.apache.brooklyn.util.yoml.serializers.YamlKeysOnBlackboard; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -87,29 +90,41 @@ protected void loopOverSerializers(YomlContext context) { ReadingTypeOnBlackboard.get(blackboard); } - if (log.isTraceEnabled()) logTrace("YOML now looking at "+context.getJsonPath()+"/ = "+context.getJavaObject()+" <-> "+context.getYamlObject()+" ("+context.getExpectedType()+")"); + String lastYamlObject = ""+context.getYamlObject(); + String lastJavaObject = ""+context.getJavaObject(); + Map lastBlackboardOutput = MutableMap.of(); + if (isTraceDetailWanted()) { + logTrace("YOML "+contextMode(context)+" "+contextSummary(context)+" (expecting "+context.getExpectedType()+")"); + showBlackboard(blackboard, lastBlackboardOutput, false); + } + while (context.phaseAdvance()) { while (context.phaseStepAdvance() bb: blackboard.entrySet()) { - logTrace(" "+bb.getKey()+": "+bb.getValue()); - } + logTrace("yoml "+contextMode(context)+" "+contextSummary(context)+" entering phase "+context.phaseCurrent()+", blackboard size "+blackboard.size()); } } YomlSerializer s = Iterables.get(serializers.getSerializers(), context.phaseCurrentStep()); + if (isTraceDetailWanted()) logTrace("yoml "+contextMode(context)+" "+context.phaseCurrent()+" "+context.phaseCurrentStep()+": "+cleanName(s)); if (context instanceof YomlContextForRead) { - if (isTraceDetailWanted()) logTrace("read "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" starting ("+context.phaseCurrent()+"."+context.phaseCurrentStep()+") "); s.read((YomlContextForRead)context, this); - if (isTraceDetailWanted()) logTrace("read "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" ended: "+context.getYamlObject()); - if (isTraceDetailWanted()) logTrace(" YKB: "+YamlKeysOnBlackboard.peek(blackboard)); } else { - if (isTraceDetailWanted()) logTrace("write "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" starting ("+context.phaseCurrent()+"."+context.phaseCurrentStep()+") "); s.write((YomlContextForWrite)context, this); - if (log.isDebugEnabled()) - log.debug("write "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" ended: "+context.getYamlObject()); - if (isTraceDetailWanted()) logTrace("write "+context.getJsonPath()+"/ = "+context.getJavaObject()+" serializer "+s+" ended: "+context.getYamlObject()); + } + + if (isTraceDetailWanted()) { + String nowYamlObject = ""+context.getYamlObject(); + if (!Objects.equals(lastYamlObject, nowYamlObject)) { + logTrace(" yaml obj now: "+nowYamlObject); + lastYamlObject = nowYamlObject; + } + String nowJavaObject = ""+context.getJavaObject(); + if (!Objects.equals(lastJavaObject, nowJavaObject)) { + logTrace(" java obj now: "+nowJavaObject); + lastJavaObject = nowJavaObject; + } + showBlackboard(blackboard, lastBlackboardOutput, true); } } } @@ -118,6 +133,43 @@ protected void loopOverSerializers(YomlContext context) { checkCompletion(context); } + protected String contextMode(YomlContext context) { + return context instanceof YomlContextForWrite ? "write" : "read"; + } + + private void showBlackboard(Map blackboard, Map lastBlackboardOutput, boolean justDelta) { + Map newBlackboardOutput = MutableMap.copyOf(lastBlackboardOutput); + if (!justDelta) lastBlackboardOutput.clear(); + + for (Map.Entry bb: blackboard.entrySet()) { + String k = cleanName(bb.getKey()); + String v = cleanName(bb.getValue()); + newBlackboardOutput.put(k, v); + String last = lastBlackboardOutput.remove(k); + if (!justDelta) logTrace(" "+k+": "+v); + else if (!Objects.equals(last, v)) logTrace(" "+k+" "+(last==null ? "added" : "now")+": "+v); + } + + for (String k: lastBlackboardOutput.keySet()) { + logTrace(" "+k+" removed"); + } + + lastBlackboardOutput.putAll(newBlackboardOutput); + } + + protected String cleanName(Object s) { + String out = Strings.toString(s); + out = Strings.removeFromStart(out, YomlConverter.class.getPackage().getName()); + out = Strings.removeFromStart(out, InstantiateTypeFromRegistry.class.getPackage().getName()); + out = Strings.removeFromStart(out, "."); + return out; + } + + protected String contextSummary(YomlContext context) { + return (Strings.isBlank(context.getJsonPath()) ? "/" : context.getJsonPath()) + " = " + + (context instanceof YomlContextForWrite ? context.getJavaObject() : context.getYamlObject()); + } + protected void checkCompletion(YomlContext context) { for (Object bo: context.getBlackboard().values()) { if (bo instanceof YomlRequirement) { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java index e59b770825..d70cdd7fc5 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertFromPrimitive.java @@ -59,8 +59,8 @@ public void read() { if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return; // runs before type instantiated if (hasJavaObject()) return; - // only runs on primitives - if (!isJsonPrimitiveObject(getYamlObject())) return; + // only runs on primitives/lists + if (!isJsonPrimitiveObject(getYamlObject()) && !isJsonList(getYamlObject())) return; Map newYamlMap = MutableMap.of(keyToInsert, getYamlObject()); @@ -78,7 +78,7 @@ public void write() { Object value = getOutputYamlMap().get(keyToInsert); if (value==null) return; - if (!isJsonPrimitiveObject(value)) return; + if (!isJsonPrimitiveObject(value) && !isJsonList(value)) return; Map yamlMap = MutableMap.copyOf(getOutputYamlMap()); yamlMap.remove(keyToInsert); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java index 33973770ff..80dfb7d375 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConvertSingletonMap.java @@ -156,7 +156,7 @@ public void read() { YomlUtils.addDefaults(defaults, newYamlMap); context.setYamlObject(newYamlMap); - // TODO should the above apply to YamlKeysOnBlackboard? Or clear it? Or does this happen early enough that isn't an issue? + YamlKeysOnBlackboard.delete(blackboard); context.phaseRestart(); } @@ -231,7 +231,7 @@ protected void readManipulatingMapToList() { if (result==null) return; context.setYamlObject(result); - YamlKeysOnBlackboard.getOrCreate(blackboard, null).clear(); + YamlKeysOnBlackboard.delete(blackboard); context.phaseAdvance(); } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java index 900a78f932..5f608b1fe4 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeFromRegistryUsingConfigMap.java @@ -46,6 +46,7 @@ /** Special instantiator for when the class's constructor takes a Map of config */ public class InstantiateTypeFromRegistryUsingConfigMap extends InstantiateTypeFromRegistry { + // for config keys we need an extra "manipulating" phase public static final String PHASE_INSTANTIATE_TYPE_DEFERRED = "handling-type-deferred-after-config"; protected String keyNameForConfigWhenSerialized = null; diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java index f3878a6fdd..c4bc13e105 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/InstantiateTypeMap.java @@ -88,7 +88,7 @@ public void read() { // json is pass-through context.setJavaObject( context.getYamlObject() ); context.phaseAdvance(); - YamlKeysOnBlackboard.getOrCreate(blackboard, null).clear(); + YamlKeysOnBlackboard.getOrCreate(blackboard, null).clearRemaining(); return; } @@ -191,7 +191,7 @@ public void read() { context.setJavaObject(jo); context.phaseAdvance(); - YamlKeysOnBlackboard.getOrCreate(blackboard, null).clear(); + YamlKeysOnBlackboard.getOrCreate(blackboard, null).clearRemaining(); } private String getAlias(Class type) { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java index a7baa0bc7f..a9237984fa 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/RenameKeySerializer.java @@ -63,9 +63,8 @@ protected YomlSerializerWorker newWorker() { public class Worker extends YomlSerializerWorker { public void read() { if (!context.isPhase(YomlContext.StandardPhases.MANIPULATING)) return; - // runs before type instantiated - if (hasJavaObject()) return; if (!isYamlMap()) return; + // this can run after type instantiation for the purpose of setting fields YamlKeysOnBlackboard ym = getYamlKeysOnBlackboardInitializedFromYamlMap(); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java index db7a063587..55b5a25020 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/TopLevelFieldsBlackboard.java @@ -160,5 +160,9 @@ public ConfigKey getConfigKey(String fieldNameOrAlias) { public Map> getConfigKeys() { return MutableMap.copyOf(keyForFieldsAndAliases); } - + + @Override + public String toString() { + return super.toString()+"[keys "+keyNames.keySet()+",done "+fieldsDone+"]"; + } } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java index 857c57982d..f4dab84504 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YamlKeysOnBlackboard.java @@ -35,9 +35,15 @@ public class YamlKeysOnBlackboard implements YomlRequirement { public static boolean isPresent(Map blackboard) { return blackboard.containsKey(KEY); } + /** returns the {@link YamlKeysOnBlackboard} or null if not yet initialized */ public static YamlKeysOnBlackboard peek(Map blackboard) { return (YamlKeysOnBlackboard) blackboard.get(KEY); } + /** deletes the {@link YamlKeysOnBlackboard} on the blackboard, so that it will be re-initialized from the YAML object */ + public static void delete(Map blackboard) { + blackboard.remove(KEY); + } + /** returns the {@link YamlKeysOnBlackboard}, creating from the given keys map if not yet present */ public static YamlKeysOnBlackboard getOrCreate(Map blackboard, Map keys) { if (!isPresent(blackboard)) { YamlKeysOnBlackboard ykb = new YamlKeysOnBlackboard(); @@ -69,7 +75,8 @@ public String toString() { return super.toString()+"("+yamlKeysRemainingToReadToJava.size()+" ever; remaining="+yamlKeysRemainingToReadToJava+")"; } - public void clear() { + /** clears keys remaining, normally indicating that work is done */ + public void clearRemaining() { yamlKeysRemainingToReadToJava.clear(); } public boolean hasKeysLeft(String ...keys) { diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java index fa32d63359..f1a4ad83ab 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/YomlSerializerComposition.java @@ -105,9 +105,13 @@ protected boolean isJsonPrimitiveObject(Object o) { return false; } + /** true iff the object is a collection */ + protected boolean isJsonList(Object o) { + return (o instanceof Collection); + } /** true iff the object is a map or collection (not recursing; for that see {@link #isJsonPureObject(Object)} */ protected boolean isJsonComplexObject(Object o) { - return (o instanceof Map || o instanceof Collection); + return (o instanceof Map || isJsonList(o)); } /** true iff the object is a primitive type or a map or collection of pure objects; diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java index 18c48d2ee4..b02f370bae 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/ConvertSingletonMapTests.java @@ -25,8 +25,12 @@ import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.yoml.annotations.Alias; import org.apache.brooklyn.util.yoml.annotations.DefaultKeyValue; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; +import org.apache.brooklyn.util.yoml.annotations.YomlFromPrimitive; +import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultKey; +import org.apache.brooklyn.util.yoml.annotations.YomlRenameKey.YomlRenameDefaultValue; import org.apache.brooklyn.util.yoml.annotations.YomlSingletonMap; import org.apache.brooklyn.util.yoml.serializers.AllFieldsTopLevel; import org.apache.brooklyn.util.yoml.serializers.ConvertSingletonMap; @@ -162,19 +166,20 @@ static class ShapeAnn extends ShapeWithTags {} MutableList.of(SingletonMapMode.LIST_AS_LIST), null, MutableMap.of("size", 0)) ) ); + List LIST_OF_BLUE_SHAPE = MutableList.of(new ShapeAnn().name("blue_shape").color("blue")); + @Test public void testAnnListPerverseOrders() { // read list-as-map, will write out as list-as-list, and can read that back too - List obj = MutableList.of(new ShapeAnn().name("blue").color("bleu")); - String listJson = "[ { bleu: blue } ]"; + String listJson = "[ { blue: blue_shape } ]"; - y4.read("{ blue: bleu }", "list").assertResult(obj); + y4.read("{ blue_shape: blue }", "list").assertResult(LIST_OF_BLUE_SHAPE); y4.write(y4.lastReadResult, "list").assertResult(listJson); - y4.read(listJson, "list").assertResult(obj); + y4.read(listJson, "list").assertResult(LIST_OF_BLUE_SHAPE); } @Test public void testAnnDisallowedAtRoot() { try { - y4.read("{ blue: bleu }", "shape"); + y4.read("{ blue: blue_shape }", "shape"); Asserts.shouldHaveFailedPreviously("but got "+y4.lastReadResult); } catch (Exception e) { log.info("got expected error: "+e); @@ -182,5 +187,38 @@ static class ShapeAnn extends ShapeWithTags {} } } + @YomlSingletonMap + @YomlRenameDefaultKey("type") + public static class ShapesAbstract { + } + + @Alias("shapes") + @YomlAllFieldsTopLevel + @YomlFromPrimitive + @YomlRenameDefaultValue("shapes") + public static class Shapes { + List shapes; + } + + @Test public void testComplexListValue() { + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations(Shapes.class) + .addTypeWithAnnotations(ShapeAnn.class); + y.read("[ {blue_shape: blue} ]", "shapes"); + + Asserts.assertInstanceOf(y.lastReadResult, Shapes.class); + Asserts.assertEquals( ((Shapes)y.lastReadResult).shapes, LIST_OF_BLUE_SHAPE ); + + y.writeLastRead().assertLastsMatch(); + } + + @Test public void testComplexListValueSingletonMapWithType() { + YomlTestFixture y = YomlTestFixture.newInstance().addTypeWithAnnotations(Shapes.class) + .addTypeWithAnnotations(ShapeAnn.class) + .addTypeWithAnnotations(ShapesAbstract.class); + y.read("shapes: [ {blue_shape: blue} ]", ShapesAbstract.class.getName()); + + Asserts.assertInstanceOf(y.lastReadResult, Shapes.class); + Asserts.assertEquals( ((Shapes)y.lastReadResult).shapes, LIST_OF_BLUE_SHAPE ); + } } From 70bf9555130e0bc2c7efa51b09c186fe3c36d3e9 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Fri, 30 Sep 2016 12:12:07 +0100 Subject: [PATCH 68/77] resolve deferred values at the right time in yoml (ie as late as possible) --- .../yoml/BrooklynDslInYomlStringPlanTest.java | 37 +++++++++++++++++++ .../ConfigInMapUnderConfigSerializer.java | 3 +- .../serializers/FieldsInMapUnderFields.java | 10 +++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java index cb96ee7597..281d363e95 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java @@ -22,9 +22,16 @@ import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; +import org.apache.brooklyn.camp.yoml.types.YomlInitializers; import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.EntityAsserts; +import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; +import org.apache.brooklyn.test.Asserts; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.time.Time; import org.apache.brooklyn.util.yoml.annotations.YomlAllFieldsTopLevel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +39,7 @@ import org.testng.annotations.Test; import com.google.api.client.repackaged.com.google.common.base.Joiner; +import com.google.common.base.Supplier; import com.google.common.collect.Iterables; public class BrooklynDslInYomlStringPlanTest extends AbstractYamlTest { @@ -73,14 +81,43 @@ public void testYomlParserRespectsDsl() throws Exception { "- type: org.apache.brooklyn.core.test.entity.TestEntity", " brooklyn.config:", " test.obj:", + // with this, the yoml is resolved at retrieval time " $brooklyn:object-yoml: item-w-dsl"); Entity app = createStartWaitAndLogApplication(yaml); Entity entity = Iterables.getOnlyElement( app.getChildren() ); entity.sensors().set(Sensors.newStringSensor("test.sensor"), "bob"); + Maybe raw = ((EntityInternal)entity).config().getRaw(ConfigKeys.newConfigKey(Object.class, "test.obj")); + Asserts.assertPresent(raw); + Asserts.assertInstanceOf(raw.get(), Supplier.class); Object obj = entity.config().get(ConfigKeys.newConfigKey(Object.class, "test.obj")); Assert.assertEquals(((ItemA)obj).name, "bob"); } + @Test + public void testYomlDefersDslEvaluationForConfig() throws Exception { + add(SAMPLE_TYPE_BASE); + add(SAMPLE_TYPE_TEST); + YomlInitializers.install(mgmt()); + + String yaml = Joiner.on("\n").join( + "services:", + "- type: org.apache.brooklyn.core.test.entity.TestEntity", + " brooklyn.initializers:", + " a-sensor:", + " type: static-sensor", + " value: '$brooklyn:self().attributeWhenReady(\"test.sensor\")'", + " period: 100ms"); + + Entity app = createStartWaitAndLogApplication(yaml); + Entity entity = Iterables.getOnlyElement( app.getChildren() ); + + entity.sensors().set(Sensors.newStringSensor("test.sensor"), "bob"); +// EntityAsserts.assertAttributeEqualsEventually(entity, attribute, expected); + System.out.println(entity.getAttribute(Sensors.newStringSensor("a-sensor"))); + Time.sleep(Duration.ONE_SECOND); + System.out.println(entity.getAttribute(Sensors.newStringSensor("a-sensor"))); + } + } diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java index aba4898211..e10a3f9d7d 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/ConfigInMapUnderConfigSerializer.java @@ -78,7 +78,8 @@ protected boolean setKeyValueForJavaObjectOnRead(String key, Object value, Strin Object v2; try { - v2 = converter.read( ((YomlContextForRead)context).subpath("/"+key, value, optionalType) ); + if (isDeferredValue(value)) v2 = value; + else v2 = converter.read( ((YomlContextForRead)context).subpath("/"+key, value, optionalType) ); } catch (Exception e) { // for config we try with the optional type, but don't insist Exceptions.propagateIfFatal(e); diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java index e76f582659..9a5ce805bc 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/yoml/serializers/FieldsInMapUnderFields.java @@ -72,6 +72,16 @@ protected boolean setKeyValueForJavaObjectOnRead(String key, Object value, Strin String fieldType = getFieldTypeName(ff, optionalTypeConstraint); Object v2 = converter.read( ((YomlContextForRead)context).subpath("/"+key, value, fieldType) ); + if (isDeferredValue(v2)) { + Maybe coerced = config.getCoercer().tryCoerce(v2, ff.getType()); + if (coerced.isAbsent()) { + // couldn't coerce or resolve, and this is needed for fields of course + throw new YomlException("Cannot interpret or coerce '"+v2+"' as "+fieldType+" for field "+ff.getName(), + Maybe.getException(coerced)); + } + v2 = coerced.get(); + } + ff.setAccessible(true); ff.set(getJavaObject(), v2); return true; From a3833cfdc52248b3fbd3daf5adf5fa95ddd3aed3 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 26 Oct 2016 14:05:40 +0100 Subject: [PATCH 69/77] change google client api joiner to base guava one --- .../brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java | 3 +-- .../apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java index 281d363e95..c4f7fc3080 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/BrooklynDslInYomlStringPlanTest.java @@ -24,7 +24,6 @@ import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest; import org.apache.brooklyn.camp.yoml.types.YomlInitializers; import org.apache.brooklyn.core.config.ConfigKeys; -import org.apache.brooklyn.core.entity.EntityAsserts; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry; @@ -38,7 +37,7 @@ import org.testng.Assert; import org.testng.annotations.Test; -import com.google.api.client.repackaged.com.google.common.base.Joiner; +import com.google.common.base.Joiner; import com.google.common.base.Supplier; import com.google.common.collect.Iterables; diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java index ea998cdb9c..e580852c23 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/yoml/ObjectYomlInBrooklynDslTest.java @@ -33,7 +33,7 @@ import org.testng.Assert; import org.testng.annotations.Test; -import com.google.api.client.repackaged.com.google.common.base.Joiner; +import com.google.common.base.Joiner; import com.google.common.collect.Iterables; public class ObjectYomlInBrooklynDslTest extends AbstractYamlTest { From 0d73e76b6fea18d587972162eb0502c5168b314d Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 27 Feb 2017 14:37:55 +0000 Subject: [PATCH 70/77] remove generic params from `AddSensor`, just put it in classes where needed --- .../org/apache/brooklyn/core/effector/AddSensor.java | 4 +--- .../brooklyn/core/sensor/http/HttpRequestSensor.java | 11 +++++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java index 44d1a6a01d..db3b6bd54a 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java @@ -65,7 +65,6 @@ public class AddSensor implements EntityInitializer { protected final Duration period; protected final String targetType; protected AttributeSensor sensor; - protected final ConfigBag params; public AddSensor(Map params) { this(ConfigBag.newInstance(params)); @@ -75,9 +74,8 @@ public AddSensor(final ConfigBag params) { this.name = Preconditions.checkNotNull(params.get(SENSOR_NAME), "Name must be supplied when defining a sensor"); this.period = params.get(SENSOR_PERIOD); this.targetType = params.get(SENSOR_TYPE); - this.params = params; } - + @Override public void apply(EntityLocal entity) { sensor = newSensor(entity); diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java index 966a88cdbb..cd6aaf89c4 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/http/HttpRequestSensor.java @@ -31,6 +31,7 @@ import org.apache.brooklyn.feed.http.HttpFeed; import org.apache.brooklyn.feed.http.HttpPollConfig; import org.apache.brooklyn.feed.http.HttpValueFunctions; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.config.ConfigBag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,10 +57,16 @@ public final class HttpRequestSensor extends AddSensor { public static final ConfigKey JSON_PATH = ConfigKeys.newStringConfigKey("jsonPath", "JSON path to select in HTTP response; default $", "$"); public static final ConfigKey USERNAME = ConfigKeys.newStringConfigKey("username", "Username for HTTP request, if required"); public static final ConfigKey PASSWORD = ConfigKeys.newStringConfigKey("password", "Password for HTTP request, if required"); - public static final ConfigKey> HEADERS = new MapConfigKey(String.class, "headers"); + public static final ConfigKey> HEADERS = new MapConfigKey(String.class, "headers"); + + private final Map extraParams = MutableMap.of(); public HttpRequestSensor(final ConfigBag params) { super(params); + // TODO yoml serialization of this needs some attention; probably better to use a pure + // config bag approach (as in this class) rather than an "extract-in-constructor" (as in parent) + // so that there are no serialized fields, just serialized config + this.extraParams.putAll(params.getUnusedConfig()); } @Override @@ -70,7 +77,7 @@ public void apply(final EntityLocal entity) { LOG.debug("Adding HTTP JSON sensor {} to {}", name, entity); } - final ConfigBag allConfig = ConfigBag.newInstanceCopying(this.params).putAll(params); + final ConfigBag allConfig = ConfigBag.newInstance().putAll(extraParams); final Supplier uri = new Supplier() { @Override public URI get() { From 26a4c5751c8f9e9e51a5be65b1ffdbf519526078 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 27 Feb 2017 16:04:31 +0000 Subject: [PATCH 71/77] Revert "bump httpcomponents versions" This reverts commit 1108d8f22bd348c57cda2382ec65bed54fdc90d0. Does not belong in this PR. Included in https://github.com/apache/brooklyn-server/pull/547 . --- parent/pom.xml | 6 +++--- pom.xml | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/parent/pom.xml b/parent/pom.xml index b7e970388d..7f671c7341 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -281,7 +281,7 @@ org.apache.httpcomponents httpcore - ${httpcomponents.httpcore.version} + ${httpclient.version} xml-apis @@ -311,13 +311,13 @@ org.apache.httpcomponents httpclient - ${httpcomponents.httpclient.version} + ${httpclient.version} org.apache.httpcomponents httpclient tests - ${httpcomponents.httpclient.version} + ${httpclient.version} aopalliance diff --git a/pom.xml b/pom.xml index 4b6f1a0604..60e0de53dc 100644 --- a/pom.xml +++ b/pom.xml @@ -118,9 +118,7 @@ 2.7.5 3.1.4 - 4.5.2 - 4.4.5 - 4.4.1 + 4.4.1 3.3.2 2.3.7 2.0.1 From b40942324774589533e1348c3730704be1011129 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Mon, 5 Jun 2017 14:54:17 +0100 Subject: [PATCH 72/77] fix ambiguous/generics references w move to Java 8 --- .../org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java | 2 +- .../apache/brooklyn/util/yoml/tests/TopLevelFieldsTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java index cb0946220b..291e27764a 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/BrooklynYomlTypeRegistry.java @@ -209,7 +209,7 @@ public Maybe newInstanceMaybe(String typeName, Yoml yoml, @Nonnull Regis // the create call will attach the loader of typeR nextContext.loader(null); - return Maybe.of(registry().create(typeR, nextContext.build(), null)); + return Maybe.of((Object) registry().create(typeR, nextContext.build(), null)); } else { // circular reference means load java, below diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelFieldsTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelFieldsTests.java index b16bd93a08..fcfbfa6e50 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelFieldsTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelFieldsTests.java @@ -172,7 +172,7 @@ public void testAliasConflictNiceError() { protected static YomlTestFixture extended0TopLevelFieldFixture(List extras) { return commonTopLevelFieldFixtureKeyNameAlias(", defaultValue: { type: string, value: bob }"). addType("shape-with-size", "{ type: \"java:"+ShapeWithSize.class.getName()+"\", interfaceTypes: [ shape ] }", - MutableList.copyOf(extras).append(topLevelFieldSerializer("{ fieldName: size, alias: shape-size }")) ); + MutableList.copyOf(extras).append(topLevelFieldSerializer("{ fieldName: size, alias: shape-size }")) ); } protected static YomlTestFixture extended1TopLevelFieldFixture() { From 779095e69134dd90c7e1faa527805f439e965bfb Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Sat, 22 Jul 2017 02:37:04 +0100 Subject: [PATCH 73/77] update mock to compile with master --- .../apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java index 460f897e2c..f3226b6c89 100644 --- a/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java +++ b/utils/common/src/test/java/org/apache/brooklyn/util/yoml/tests/TopLevelConfigKeysTests.java @@ -94,6 +94,7 @@ public MockConfigKey(TypeToken typeToken, String name) { @Override public boolean isValueValid(T value) { return true; } @Override public ConfigInheritance getInheritanceByContext(ConfigInheritanceContext context) { return null; } @Override public Map getInheritanceByContext() { return MutableMap.of(); } + @Override public Collection getDeprecatedNames() { return MutableList.of(); } } static class S1 { From 45fe58a4be8ef11489c5e993121dc597427571be Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Tue, 29 Aug 2017 11:16:23 +0100 Subject: [PATCH 74/77] fixes for visibility/changes to `params` field --- .../main/java/org/apache/brooklyn/core/effector/AddSensor.java | 1 + .../org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java | 3 ++- .../org/apache/brooklyn/entity/java/JmxAttributeSensor.java | 2 +- .../brooklyn/core/sensor/windows/WinRmCommandSensor.java | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java index a1012bb6bd..6312fd9682 100644 --- a/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/effector/AddSensor.java @@ -121,6 +121,7 @@ public void apply(EntityLocal entity) { // old names, for XML deserializaton compatiblity private final String type; + /** @deprecated since 0.9.0 and semantics slightly different; accessors should use {@link #getRememberedParams()} */ private ConfigBag params; private Object readResolve() { try { diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java index ad8e7142d9..d58e3e40d7 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/ssh/SshCommandSensor.java @@ -99,9 +99,10 @@ public void apply(final EntityLocal entity) { LOG.debug("Adding SSH sensor {} to {}", name, entity); } - final Boolean suppressDuplicates = EntityInitializers.resolve(params, SUPPRESS_DUPLICATES); + final Boolean suppressDuplicates = EntityInitializers.resolve(getRememberedParams(), SUPPRESS_DUPLICATES); Supplier> envSupplier = new Supplier>() { + @SuppressWarnings("unchecked") @Override public Map get() { Map env = MutableMap.copyOf(entity.getConfig(BrooklynConfigKeys.SHELL_ENVIRONMENT)); diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java b/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java index 21046add67..7453edb2ff 100644 --- a/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java +++ b/software/base/src/main/java/org/apache/brooklyn/entity/java/JmxAttributeSensor.java @@ -88,7 +88,7 @@ public JmxAttributeSensor(final ConfigBag params) { public void apply(final EntityLocal entity) { super.apply(entity); - final Boolean suppressDuplicates = EntityInitializers.resolve(params, SUPPRESS_DUPLICATES); + final Boolean suppressDuplicates = EntityInitializers.resolve(getRememberedParams(), SUPPRESS_DUPLICATES); if (entity instanceof UsesJmx) { if (LOG.isDebugEnabled()) { diff --git a/software/winrm/src/main/java/org/apache/brooklyn/core/sensor/windows/WinRmCommandSensor.java b/software/winrm/src/main/java/org/apache/brooklyn/core/sensor/windows/WinRmCommandSensor.java index 2fcc31502e..2a5e565ff8 100644 --- a/software/winrm/src/main/java/org/apache/brooklyn/core/sensor/windows/WinRmCommandSensor.java +++ b/software/winrm/src/main/java/org/apache/brooklyn/core/sensor/windows/WinRmCommandSensor.java @@ -96,7 +96,7 @@ public void apply(final EntityLocal entity) { LOG.debug("Adding WinRM sensor {} to {}", name, entity); } - final Boolean suppressDuplicates = EntityInitializers.resolve(params, SUPPRESS_DUPLICATES); + final Boolean suppressDuplicates = EntityInitializers.resolve(getRememberedParams(), SUPPRESS_DUPLICATES); Supplier> envSupplier = new Supplier>() { @Override From 14bcad387a293dec46856b668f8aab23d32288f7 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Wed, 17 Jan 2018 17:02:13 +0000 Subject: [PATCH 75/77] placeholders for getting RegisteredType supertypes from a RT needs YOML to go any further however --- .../spi/creation/CampTypePlanTransformer.java | 24 ++++--- .../internal/BasicBrooklynCatalog.java | 18 ++++- .../typereg/BrooklynTypePlanTransformer.java | 21 +++--- .../JavaClassNameTypePlanTransformer.java | 15 +---- .../core/typereg/RegisteredTypeInfo.java | 65 +++++++++++++++++++ .../core/typereg/TypePlanTransformers.java | 16 ++++- .../internal/StaticTypePlanTransformer.java | 13 ++-- .../ExampleXmlTypePlanTransformer.java | 14 +--- 8 files changed, 127 insertions(+), 59 deletions(-) create mode 100644 core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeInfo.java diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java index eaa68b898c..2cf3285dcb 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampTypePlanTransformer.java @@ -22,14 +22,16 @@ import java.util.Map; import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; +import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.api.typereg.RegisteredType.TypeImplementationPlan; import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; -import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry.RegisteredTypeKind; import org.apache.brooklyn.core.typereg.AbstractFormatSpecificTypeImplementationPlan; import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer; import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan; +import org.apache.brooklyn.core.typereg.RegisteredTypeInfo; import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.guava.Maybe; import com.google.common.collect.ImmutableList; @@ -84,28 +86,24 @@ protected double scoreForNonmatchingNonnullFormat(String planFormat, Object plan @Override protected AbstractBrooklynObjectSpec createSpec(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception { - // TODO cache + // could cache and copy each time, if this bit is slow + // (but don't think it is now) return new CampResolver(mgmt, type, context).createSpec(); } @Override protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception { - // beans not supported by this? + // beans not supported by this? - want YOML for this throw new IllegalStateException("beans not supported here"); } @Override - public double scoreForTypeDefinition(String formatCode, Object catalogData) { - // TODO catalog parsing - return 0; - } - - @Override - public List createFromTypeDefinition(String formatCode, Object catalogData) { - // TODO catalog parsing - return null; + public RegisteredTypeInfo getTypeInfo(RegisteredType type) { + // TODO collect immediate supertypes, as RegisteredType and Class instances + // we really want YOML for this however + return RegisteredTypeInfo.create(type, this, null, MutableSet.of()); } - + public static class CampTypeImplementationPlan extends AbstractFormatSpecificTypeImplementationPlan { public CampTypeImplementationPlan(TypeImplementationPlan otherPlan) { super(FORMATS.get(0), String.class, otherPlan); diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java index 636742b3a3..0b01419be3 100644 --- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java +++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java @@ -69,8 +69,10 @@ import org.apache.brooklyn.core.typereg.BasicRegisteredType; import org.apache.brooklyn.core.typereg.BasicTypeImplementationPlan; import org.apache.brooklyn.core.typereg.BrooklynTypePlanTransformer; +import org.apache.brooklyn.core.typereg.RegisteredTypeInfo; import org.apache.brooklyn.core.typereg.RegisteredTypeNaming; import org.apache.brooklyn.core.typereg.RegisteredTypes; +import org.apache.brooklyn.core.typereg.TypePlanTransformers; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; @@ -1799,11 +1801,21 @@ public ReferenceWithError resolve(RegisteredType typeToValidate, } RegisteredTypes.cacheActualJavaType(resultT, resultS); - Set newSupers = MutableSet.of(); - // TODO collect registered type name supertypes, as strings + MutableSet newSupers = MutableSet.of(); newSupers.add(resultS); + + Maybe typeInfo = TypePlanTransformers.getTypeInfo(mgmt, resultT, constraint); + if (typeInfo.isPresent()) { + Set supersTI = typeInfo.get().getSupertypes(); + if (supersTI!=null) { + newSupers.addAll(supersTI); + } + } + + // following should be redundant if the above worked, but the + // above might not have worked, and no harm in any case newSupers.addAll(supers); - newSupers.add(BrooklynObjectType.of(resultO.getClass()).getInterfaceType()); + newSupers.addIfNotNull(BrooklynObjectType.of(resultO.getClass()).getInterfaceType()); collectSupers(newSupers); RegisteredTypes.addSuperTypes(resultT, newSupers); diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/BrooklynTypePlanTransformer.java b/core/src/main/java/org/apache/brooklyn/core/typereg/BrooklynTypePlanTransformer.java index 1001268a74..cbe77f572c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/BrooklynTypePlanTransformer.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/BrooklynTypePlanTransformer.java @@ -18,7 +18,6 @@ */ package org.apache.brooklyn.core.typereg; -import java.util.List; import java.util.ServiceLoader; import javax.annotation.Nonnull; @@ -30,8 +29,6 @@ import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; import org.apache.brooklyn.core.mgmt.ManagementContextInjectable; -import com.google.common.annotations.Beta; - /** * Interface for use by schemes which provide the capability to transform plans * (serialized descriptions) to brooklyn objecs and specs. @@ -69,6 +66,7 @@ public interface BrooklynTypePlanTransformer extends ManagementContextInjectable *

* */ double scoreForType(@Nonnull RegisteredType type, @Nonnull RegisteredTypeLoadingContext context); + /** Creates a new instance of the indicated type, or throws if not supported; * this method is used by the {@link BrooklynTypeRegistry} when it creates instances, * so implementations must respect the {@link RegisteredTypeKind} semantics and the {@link RegisteredTypeLoadingContext} @@ -81,11 +79,14 @@ public interface BrooklynTypePlanTransformer extends ManagementContextInjectable * if they cannot instantiate the given {@link RegisteredType#getPlan()}. */ @Nullable Object create(@Nonnull RegisteredType type, @Nonnull RegisteredTypeLoadingContext context); - // TODO sketch methods for loading *catalog* definitions. note some potential overlap - // with BrooklynTypeRegistery.createXxxFromPlan - @Beta - double scoreForTypeDefinition(String formatCode, Object catalogData); - @Beta - List createFromTypeDefinition(String formatCode, Object catalogData); - + /** Returns extended info for the given type, as it would be understood by this + * transformer. This may be incomplete, empty or even null if the transformer does not support type info. + *

+ * The framework guarantees this will only be invoked when {@link #scoreForType(RegisteredType, RegisteredTypeLoadingContext)} + * has returned a positive value, and the same constraints on the inputs as for that method apply. + *

+ * Implementations should either return null or throw {@link UnsupportedTypePlanException} + * if they cannot instantiate the given {@link RegisteredType#getPlan()}. */ + @Nullable RegisteredTypeInfo getTypeInfo(RegisteredType type); + } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/JavaClassNameTypePlanTransformer.java b/core/src/main/java/org/apache/brooklyn/core/typereg/JavaClassNameTypePlanTransformer.java index 29a4ec181c..496b150789 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/JavaClassNameTypePlanTransformer.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/JavaClassNameTypePlanTransformer.java @@ -18,12 +18,11 @@ */ package org.apache.brooklyn.core.typereg; -import java.util.List; - import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec; import org.apache.brooklyn.api.objs.BrooklynObject; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.text.Identifiers; /** @@ -74,18 +73,10 @@ protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext co private Class getType(RegisteredType type, RegisteredTypeLoadingContext context) throws Exception { return RegisteredTypes.loadActualJavaType((String)type.getPlan().getPlanData(), mgmt, type, context); } - - - // not supported as a catalog format (yet? should we?) - - @Override - public double scoreForTypeDefinition(String formatCode, Object catalogData) { - return 0; - } @Override - public List createFromTypeDefinition(String formatCode, Object catalogData) { - throw new UnsupportedTypePlanException("this transformer does not support YAML catalog additions"); + public RegisteredTypeInfo getTypeInfo(RegisteredType type) { + return RegisteredTypeInfo.create(type, this, null, MutableSet.of()); } } diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeInfo.java b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeInfo.java new file mode 100644 index 0000000000..381e3d1004 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/RegisteredTypeInfo.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.core.typereg; + +import java.util.Set; + +import org.apache.brooklyn.api.typereg.RegisteredType; + +/** Includes metadata for types, if available. Varies by {@link BrooklynTypePlanTransformer}, + * with the main one (CAMP) being quite complete. */ +public class RegisteredTypeInfo { + + private final RegisteredType type; + + private String planText; + private Set supertypes; + + private RegisteredTypeInfo(RegisteredType type) { + this.type = type; + } + + public static RegisteredTypeInfo create(RegisteredType type, BrooklynTypePlanTransformer transformer, + String planText, Set someSupertypes) { + + return new RegisteredTypeInfo(type); + } + + public RegisteredType getType() { + return type; + } + + public String getPlanText() { + return planText; + } + + void setPlanText(String planText) { + this.planText = planText; + } + + // list of supertypes, as RegisteredType and Class instances + public Set getSupertypes() { + return supertypes; + } + + void setSupertypes(Set supertypes) { + this.supertypes = supertypes; + } + +} diff --git a/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java b/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java index cc4845c286..699ec345ae 100644 --- a/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java +++ b/core/src/main/java/org/apache/brooklyn/core/typereg/TypePlanTransformers.java @@ -41,6 +41,7 @@ import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @@ -106,6 +107,19 @@ public static List forType(ManagementContext mgmt, * callers should generally use one of the create methods on {@link BrooklynTypeRegistry} rather than using this method directly. */ @Beta public static Maybe transform(ManagementContext mgmt, RegisteredType type, RegisteredTypeLoadingContext constraint) { + return applyAtTransformers(mgmt, type, constraint, (t) -> t.create(type, constraint)); + } + + /** transforms the given type to an instance, if possible + *

+ * callers should generally use one of the create methods on {@link BrooklynTypeRegistry} rather than using this method directly. */ + @Beta + public static Maybe getTypeInfo(ManagementContext mgmt, RegisteredType type, RegisteredTypeLoadingContext constraint) { + return applyAtTransformers(mgmt, type, constraint, (t) -> t.getTypeInfo(type)); + } + + private static Maybe applyAtTransformers(ManagementContext mgmt, RegisteredType type, RegisteredTypeLoadingContext constraint, + Function fn) { if (type==null) return Maybe.absent("type cannot be null"); if (type.getPlan()==null) return Maybe.absent("type plan cannot be null, when instantiating "+type); @@ -114,7 +128,7 @@ public static Maybe transform(ManagementContext mgmt, RegisteredType typ Collection failuresFromTransformers = new ArrayList(); for (BrooklynTypePlanTransformer t: transformers) { try { - Object result = t.create(type, constraint); + T result = fn.apply(t); if (result==null) { transformersWhoDontSupport.add(t.getFormatCode() + " (returned null)"); continue; diff --git a/core/src/test/java/org/apache/brooklyn/core/catalog/internal/StaticTypePlanTransformer.java b/core/src/test/java/org/apache/brooklyn/core/catalog/internal/StaticTypePlanTransformer.java index a1bfa9804d..ae99312cee 100644 --- a/core/src/test/java/org/apache/brooklyn/core/catalog/internal/StaticTypePlanTransformer.java +++ b/core/src/test/java/org/apache/brooklyn/core/catalog/internal/StaticTypePlanTransformer.java @@ -27,7 +27,9 @@ import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer; import org.apache.brooklyn.core.typereg.JavaClassNameTypePlanTransformer; +import org.apache.brooklyn.core.typereg.RegisteredTypeInfo; import org.apache.brooklyn.core.typereg.TypePlanTransformers; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.text.Identifiers; /** @@ -78,15 +80,8 @@ public static String registerSpec(AbstractBrooklynObjectSpec spec) { } @Override - public double scoreForTypeDefinition(String formatCode, Object catalogData) { - // not supported - return 0; - } - - @Override - public List createFromTypeDefinition(String formatCode, Object catalogData) { - // not supported - return null; + public RegisteredTypeInfo getTypeInfo(RegisteredType type) { + return RegisteredTypeInfo.create(type, this, null, MutableSet.of()); } @Override diff --git a/core/src/test/java/org/apache/brooklyn/core/typereg/ExampleXmlTypePlanTransformer.java b/core/src/test/java/org/apache/brooklyn/core/typereg/ExampleXmlTypePlanTransformer.java index 76570c8c10..065e917ca3 100644 --- a/core/src/test/java/org/apache/brooklyn/core/typereg/ExampleXmlTypePlanTransformer.java +++ b/core/src/test/java/org/apache/brooklyn/core/typereg/ExampleXmlTypePlanTransformer.java @@ -19,7 +19,6 @@ package org.apache.brooklyn.core.typereg; import java.io.StringReader; -import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -31,6 +30,7 @@ import org.apache.brooklyn.api.typereg.RegisteredTypeLoadingContext; import org.apache.brooklyn.entity.stock.BasicApplication; import org.apache.brooklyn.entity.stock.BasicEntity; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.core.xstream.XmlSerializer; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.stream.ReaderInputStream; @@ -80,17 +80,9 @@ protected Object createBean(RegisteredType type, RegisteredTypeLoadingContext co return new XmlSerializer().fromString((String)type.getPlan().getPlanData()); } - - @Override - public double scoreForTypeDefinition(String formatCode, Object catalogData) { - // defining types not supported - return 0; - } - @Override - public List createFromTypeDefinition(String formatCode, Object catalogData) { - // defining types not supported - return null; + public RegisteredTypeInfo getTypeInfo(RegisteredType type) { + return RegisteredTypeInfo.create(type, this, null, MutableSet.of()); } private Document parseXml(String plan) { From 79174c969cf8358d2890260646832367d9a18ec4 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 25 Jan 2018 10:43:58 +0000 Subject: [PATCH 76/77] fix compile/runtime errors due to incompatible changes --- .../apache/brooklyn/core/sensor/function/FunctionSensor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/function/FunctionSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/function/FunctionSensor.java index 4b324511b3..605621183d 100644 --- a/core/src/main/java/org/apache/brooklyn/core/sensor/function/FunctionSensor.java +++ b/core/src/main/java/org/apache/brooklyn/core/sensor/function/FunctionSensor.java @@ -59,6 +59,7 @@ public final class FunctionSensor extends AddSensor { public FunctionSensor(final ConfigBag params) { super(params); + rememberUnusedParams(params); } @Override @@ -69,7 +70,7 @@ public void apply(final EntityLocal entity) { LOG.debug("Adding HTTP JSON sensor {} to {}", name, entity); } - final ConfigBag allConfig = ConfigBag.newInstanceCopying(this.params).putAll(params); + final ConfigBag allConfig = ConfigBag.newInstanceCopying(getRememberedParams()); final Callable function = EntityInitializers.resolve(allConfig, FUNCTION); final Boolean suppressDuplicates = EntityInitializers.resolve(allConfig, SUPPRESS_DUPLICATES); From 802666a0ab926f1a33babfbb0f4a71c3c3c5ceb7 Mon Sep 17 00:00:00 2001 From: Alex Heneveld Date: Thu, 25 Jan 2018 10:47:50 +0000 Subject: [PATCH 77/77] make YOML code compatible with supertype/typeinfo API extension --- .../camp/yoml/YomlTypePlanTransformer.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java index 09d2b0a129..0e4ee33119 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/yoml/YomlTypePlanTransformer.java @@ -36,10 +36,12 @@ import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.typereg.AbstractFormatSpecificTypeImplementationPlan; import org.apache.brooklyn.core.typereg.AbstractTypePlanTransformer; +import org.apache.brooklyn.core.typereg.RegisteredTypeInfo; import org.apache.brooklyn.core.typereg.RegisteredTypeLoadingContexts; import org.apache.brooklyn.core.typereg.RegisteredTypes; import org.apache.brooklyn.core.typereg.UnsupportedTypePlanException; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.core.task.ValueResolver; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; @@ -213,19 +215,13 @@ public static Builder newYomlConfig(@Nonnull ManagementConte // and collected by tr.collectSerializers(...) coercer(new ValueResolver.ResolvingTypeCoercer()); } - - @Override - public double scoreForTypeDefinition(String formatCode, Object catalogData) { - // TODO catalog parsing - return 0; - } @Override - public List createFromTypeDefinition(String formatCode, Object catalogData) { - // TODO catalog parsing - return null; + public RegisteredTypeInfo getTypeInfo(RegisteredType type) { + // TODO + return RegisteredTypeInfo.create(type, this, null, MutableSet.of()); } - + static class YomlTypeImplementationPlan extends AbstractFormatSpecificTypeImplementationPlan { final String javaType; final List serializers;