diff --git a/src/main/java/org/springframework/hateoas/forms/AbstractSuggest.java b/src/main/java/org/springframework/hateoas/forms/AbstractSuggest.java new file mode 100644 index 000000000..656127297 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/AbstractSuggest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * @see Suggest + */ +@JsonInclude(Include.NON_EMPTY) +@JsonIgnoreProperties({ "type" }) +public class AbstractSuggest implements Suggest { + + private final String textField; + + private final String valueField; + + public AbstractSuggest(String textField, String valueField) { + this.textField = textField; + this.valueField = valueField; + } + + @Override + @JsonProperty("value-field") + public String getValueField() { + return valueField; + } + + @Override + @JsonProperty("prompt-field") + public String getTextField() { + return textField; + } + +} diff --git a/src/main/java/org/springframework/hateoas/forms/AbstractSuggestBuilder.java b/src/main/java/org/springframework/hateoas/forms/AbstractSuggestBuilder.java new file mode 100644 index 000000000..37e6bbc80 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/AbstractSuggestBuilder.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +/** + * Abstract class that helps to construct {@link Suggest} + * @see PropertyBuilder + * @see TemplateBuilder + */ +public abstract class AbstractSuggestBuilder implements SuggestBuilder { + + String textFieldName; + + String valueFieldName; + + @Override + public AbstractSuggestBuilder textField(String textFieldName) { + this.textFieldName = textFieldName; + return this; + } + + @Override + public AbstractSuggestBuilder valueField(String valueFieldName) { + this.valueFieldName = valueFieldName; + return this; + } + + @Override + public abstract Suggest build(); +} diff --git a/src/main/java/org/springframework/hateoas/forms/EmbeddedSuggestBuilder.java b/src/main/java/org/springframework/hateoas/forms/EmbeddedSuggestBuilder.java new file mode 100644 index 000000000..5dc48ca18 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/EmbeddedSuggestBuilder.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import org.springframework.hateoas.forms.ValueSuggest.ValueSuggestType; + +/** + * Builds {@link ValueSuggest} of type {@link ValueSuggestType#EMBEDDED} + * + */ +public class EmbeddedSuggestBuilder extends ValueSuggestBuilder { + + public EmbeddedSuggestBuilder(Iterable values) { + super(values); + } + + @Override + public Suggest build() { + return new ValueSuggest(values, textFieldName, valueFieldName, ValueSuggestType.EMBEDDED); + } +} diff --git a/src/main/java/org/springframework/hateoas/forms/FieldNotFoundException.java b/src/main/java/org/springframework/hateoas/forms/FieldNotFoundException.java new file mode 100644 index 000000000..7ba98120f --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/FieldNotFoundException.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +/** + * Exception fired by {@link FieldUtils} when a class doesn't have a field of a specified name. + * + */ +public class FieldNotFoundException extends RuntimeException { + + private static final long serialVersionUID = 2591233443652872298L; + + private final Class targetClass; + + private final String field; + + public FieldNotFoundException(Class targetClass, String field) { + this.targetClass = targetClass; + this.field = field; + } + + public Class getTargetClass() { + return targetClass; + } + + public String getField() { + return field; + } + +} diff --git a/src/main/java/org/springframework/hateoas/forms/FieldUtils.java b/src/main/java/org/springframework/hateoas/forms/FieldUtils.java new file mode 100644 index 000000000..1f4216d68 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/FieldUtils.java @@ -0,0 +1,112 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Map; + +import org.springframework.core.ResolvableType; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * Utility class for searching fields of a class + * + */ +public class FieldUtils { + private static final char DOT = '.'; + + /** + * Attempt to find the {@link Class} of the {@link Field} on the supplied class by the especified path. + * + * @param type + * @param path may be a nested path dot separated + * @return + */ + public static Class findFieldClass(Class type, String path) { + int firstDot = path.indexOf(DOT); + if (firstDot!=-1) { + String propertyName = path.substring(0, firstDot); + Class childType = findSimpleFieldClass(type, propertyName); + if (childType == null) { + throw new FieldNotFoundException(type, propertyName); + } + return findFieldClass(childType, path.substring(firstDot + 1, path.length())); + } + else { + return findSimpleFieldClass(type, path); + } + } + + /** + * Attempt to find a {@link Field} declared in a {@link Class}. In first instance tries to find the getter + * {@link Method} of supplied fieldName. If getter is not present attempts to find for declared field. + * @param type + * @param fieldName + * @return + */ + private static Class findSimpleFieldClass(Class type, String fieldName) { + + Method method = ReflectionUtils.findMethod(type, "get" + StringUtils.capitalize(fieldName)); + ResolvableType resolvableType = null; + if (null != method) { + resolvableType = ResolvableType.forMethodReturnType(method); + } + else { + try { + resolvableType = ResolvableType.forField(type.getDeclaredField(fieldName)); + } + catch (SecurityException e) { + throw new FieldNotFoundException(type, fieldName); + } + catch (NoSuchFieldException e) { + throw new FieldNotFoundException(type, fieldName); + } + } + + return getActualType(resolvableType); + } + + /** + * Returns the {@link Class} of supplied {@link ResolvableType} considering that: + * + *
    + *
  • - if resolvableType is an array, the component type is returned
  • + *
  • - if resolvableType is a {@link Collection}, generic parameter class is returned
  • + *
  • - if resolvableType is a {@link Map}, {@link NotSupportedException} is returned
  • + *
  • - otherwise resolvableType raw class is returned
  • + *
+ * + * @param resolvableType + * @return + */ + private static Class getActualType(ResolvableType resolvableType) { + if (resolvableType.isArray()) { + return resolvableType.getComponentType().resolve(); + } + else if (resolvableType.asCollection() != ResolvableType.NONE) { + return resolvableType.getGeneric(0).resolve(); + } + else if (resolvableType.asMap() != ResolvableType.NONE) { + throw new NotSupportedException(resolvableType.resolve() + " is not supported"); + } + else { + return resolvableType.resolve(); + } + } +} diff --git a/src/main/java/org/springframework/hateoas/forms/Form.java b/src/main/java/org/springframework/hateoas/forms/Form.java new file mode 100644 index 000000000..e15c12ba0 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/Form.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import org.springframework.web.bind.annotation.RequestBody; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * HAL-FORMS {@link Template} that contains the argument marked as {@link RequestBody} + */ +@JsonIgnoreProperties({ "body", "href", "rel" }) +@JsonInclude(Include.NON_EMPTY) +public class Form extends Template { + + private static final long serialVersionUID = -933494757445089955L; + + private Object body; + + public Form() { + } + + public Form(String href, String rel) { + super(href, rel); + } + + public void setBody(Object body) { + this.body = body; + } + + public Object getBody() { + return body; + } +} diff --git a/src/main/java/org/springframework/hateoas/forms/FormBuilderFactory.java b/src/main/java/org/springframework/hateoas/forms/FormBuilderFactory.java new file mode 100644 index 000000000..18e2b6674 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/FormBuilderFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import java.lang.reflect.Method; + +import org.springframework.hateoas.core.DummyInvocationUtils; +import org.springframework.hateoas.mvc.ControllerFormBuilder; + +public interface FormBuilderFactory { + /** + * Returns a {@link ControllerFormBuilder} pointing to the URI mapped to the given {@link Method} and expanding this + * mapping using the given parameters. + * + * @param method must not be {@literal null}. + * @param parameters + * @return + */ + T formTo(Method method, Object... parameters); + + /** + * Returns a {@link ControllerFormBuilder} pointing to the URI mapped to the given {@link Method} assuming it was + * invoked on an object of the given type. + * + * @param type must not be {@literal null}. + * @param method must not be {@literal null}. + * @param parameters + * @return + */ + T formTo(Class type, Method method, Object... parameters); + + /** + * Returns a {@link ControllerFormBuilder} pointing to the URI mapped to the method the result is handed into this + * method. Use {@link DummyInvocationUtils#methodOn(Class, Object...)} to obtain a dummy instance of a controller to + * record a dummy method invocation on. See {@link HalFormsLinkBuilder#linkTo(Object)} for an example. + * + * @see ControllerLinkBuilder#linkTo(Object) + * @param methodInvocationResult must not be {@literal null}. + * @return + */ + T formTo(Object methodInvocationResult); +} diff --git a/src/main/java/org/springframework/hateoas/forms/FormBuilderSupport.java b/src/main/java/org/springframework/hateoas/forms/FormBuilderSupport.java new file mode 100644 index 000000000..b17cd87be --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/FormBuilderSupport.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import org.springframework.web.util.UriComponentsBuilder; + +public abstract class FormBuilderSupport extends TemplateBuilderSupport { + + public FormBuilderSupport(UriComponentsBuilder builder) { + super(builder); + } + + @Override + public Form withKey(String key) { + Form form = new Form(toString(), key); + form.setBody(getBody()); + form.setProperties(getProperties()); + form.setMethod(getMethod()); + form.setContentType(getContentType()); + return form; + } + + @Override + public Form withDefaultKey() { + return withKey(Template.DEFAULT_KEY); + } + + public abstract Object getBody(); + + public abstract String getContentType(); + +} diff --git a/src/main/java/org/springframework/hateoas/forms/LinkSuggest.java b/src/main/java/org/springframework/hateoas/forms/LinkSuggest.java new file mode 100644 index 000000000..d1cd7e5e9 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/LinkSuggest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import org.springframework.hateoas.Link; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +/** + * Suggested values of a {@link Property} that are loaded by a url returning a list of values. + */ +@JsonPropertyOrder({ "link", "valueField", "textField" }) +public class LinkSuggest extends AbstractSuggest { + + @JsonSerialize(using = LinkSuggestSerializer.class) + @JsonProperty("href") + private Link link; + + public LinkSuggest(Link link, String textFieldName, String valueFieldName) { + super(textFieldName, valueFieldName); + this.link = link; + } + + public Link getLink() { + return link; + } + + public void setLink(Link link) { + this.link = link; + } + +} diff --git a/src/main/java/org/springframework/hateoas/forms/LinkSuggestBuilder.java b/src/main/java/org/springframework/hateoas/forms/LinkSuggestBuilder.java new file mode 100644 index 000000000..993223b94 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/LinkSuggestBuilder.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import org.springframework.hateoas.Link; + +/** + * Creates instances of {@link LinkSuggest} + * + */ +public class LinkSuggestBuilder extends AbstractSuggestBuilder { + + private final Link link; + + public LinkSuggestBuilder(Link link) { + this.link = link; + } + + @Override + public Suggest build() { + return new LinkSuggest(link, textFieldName, valueFieldName); + } + +} diff --git a/src/main/java/org/springframework/hateoas/forms/LinkSuggestSerializer.java b/src/main/java/org/springframework/hateoas/forms/LinkSuggestSerializer.java new file mode 100644 index 000000000..18fce9ab3 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/LinkSuggestSerializer.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import java.io.IOException; + +import org.springframework.hateoas.Link; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +public class LinkSuggestSerializer extends JsonSerializer { + @Override + public void serialize(Link value, JsonGenerator jgen, SerializerProvider provider) throws IOException, + JsonProcessingException { + jgen.writeString(value.getHref()); + } + +} diff --git a/src/main/java/org/springframework/hateoas/forms/NotSupportedException.java b/src/main/java/org/springframework/hateoas/forms/NotSupportedException.java new file mode 100644 index 000000000..479d7995d --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/NotSupportedException.java @@ -0,0 +1,26 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +public class NotSupportedException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public NotSupportedException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/springframework/hateoas/forms/Property.java b/src/main/java/org/springframework/hateoas/forms/Property.java new file mode 100644 index 000000000..3c86327fd --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/Property.java @@ -0,0 +1,93 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * Describe a parameter for the associated state transition in a HAL-FORMS document. A {@link Template} may contain a + * list of {@link Property} + * + * @see http://mamund.site44.com/misc/hal-forms/ + * + */ +@JsonInclude(Include.NON_DEFAULT) +public class Property { + + private String name; + + private Boolean readOnly; + + private String value; + + private String prompt; + + private String regex; + + private boolean templated; + + private boolean required; + + private Suggest suggest; + + public Property() { + } + + public Property(String name, Boolean readOnly, boolean templated, String value, String prompt, String regex, + boolean required, Suggest suggest) { + this.name = name; + this.readOnly = readOnly; + this.templated = templated; + this.value = value; + this.prompt = prompt; + this.regex = regex; + this.required = required; + this.suggest = suggest; + } + + public String getName() { + return name; + } + + public Boolean isReadOnly() { + return readOnly; + } + + public String getValue() { + return value; + } + + public String getPrompt() { + return prompt; + } + + public String getRegex() { + return regex; + } + + public boolean isTemplated() { + return templated; + } + + public boolean isRequired() { + return required; + } + + public Suggest getSuggest() { + return suggest; + } +} diff --git a/src/main/java/org/springframework/hateoas/forms/PropertyBuilder.java b/src/main/java/org/springframework/hateoas/forms/PropertyBuilder.java new file mode 100644 index 000000000..66b7d3de6 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/PropertyBuilder.java @@ -0,0 +1,96 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import org.springframework.hateoas.mvc.ControllerFormBuilder; + +/** + * Builder used by {@link ControllerFormBuilder} to create instances of {@link Property} + * + */ +public class PropertyBuilder { + + private String name; + + // we want to idenfify if user has setted the readonly value (default value is not false) + private Boolean readonly; + + private boolean templated; + + private String value; + + private String prompt; + + private String regex; + + private boolean required; + + private Class declaringClass; + + protected TemplateBuilder formBuilder; + + private SuggestBuilderFactory suggestBuilderFactory; + + public PropertyBuilder(String name, Class declaringClass, TemplateBuilder formBuilder) { + this.name = name; + this.declaringClass = declaringClass; + this.formBuilder = formBuilder; + } + + public PropertyBuilder readonly(boolean readonly) { + this.readonly = readonly; + return this; + } + + public SuggestBuilderFactory suggest() { + this.suggestBuilderFactory = new SuggestBuilderFactory(); + return this.suggestBuilderFactory; + } + + public Property build() { + return new Property(name, readonly, templated, value, prompt, regex, required, + suggestBuilderFactory != null ? suggestBuilderFactory.build() : null); + } + + public Class getDeclaringClass() { + return declaringClass; + } + + public PropertyBuilder regex(String regex) { + this.regex = regex; + return this; + } + + public PropertyBuilder prompt(String prompt) { + this.prompt = prompt; + return this; + } + + public PropertyBuilder required(boolean required) { + this.required = required; + return this; + } + + public PropertyBuilder value(String value) { + this.value = value; + this.templated = isTemplatedValue(value); + return this; + } + + private boolean isTemplatedValue(String value) { + return value.startsWith("{") && value.endsWith("}"); + } +} diff --git a/src/main/java/org/springframework/hateoas/forms/Suggest.java b/src/main/java/org/springframework/hateoas/forms/Suggest.java new file mode 100644 index 000000000..6e3f3ab6f --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/Suggest.java @@ -0,0 +1,26 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +/** + * Define the "value" and "text" fields of an object included in a {@link Property} of a HAL-FORMS {@link Template} + * + */ +public interface Suggest { + String getValueField(); + + String getTextField(); +} diff --git a/src/main/java/org/springframework/hateoas/forms/SuggestBuilder.java b/src/main/java/org/springframework/hateoas/forms/SuggestBuilder.java new file mode 100644 index 000000000..ee97fbab0 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/SuggestBuilder.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +/** + * Builder for creating instances of {@link Suggest} + * @see PropertyBuilder + * @see TemplateBuilder + */ +public interface SuggestBuilder { + SuggestBuilder textField(String textFieldName); + + SuggestBuilder valueField(String valueFieldName); + + Suggest build(); +} diff --git a/src/main/java/org/springframework/hateoas/forms/SuggestBuilderFactory.java b/src/main/java/org/springframework/hateoas/forms/SuggestBuilderFactory.java new file mode 100644 index 000000000..81ffdafb3 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/SuggestBuilderFactory.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import java.util.Arrays; + +import org.springframework.hateoas.Link; +import org.springframework.hateoas.forms.ValueSuggest.ValueSuggestType; + +/** + * Builder returned by {@link PropertyBuilder#suggest()} that provides different types of {@link SuggestBuilder} + * + */ +public class SuggestBuilderFactory { + + private SuggestBuilder suggestBuilder; + + /** + * Returns a {@link SuggestBuilder} to construct a {@link ValueSuggest} of type {@link ValueSuggestType#DIRECT} + * @param values + * @return + */ + public SuggestBuilder values(Iterable values) { + this.suggestBuilder = new ValueSuggestBuilder(values); + return suggestBuilder; + } + + public SuggestBuilder values(D[] values) { + this.suggestBuilder = new ValueSuggestBuilder(Arrays.asList(values)); + return suggestBuilder; + } + + /** + * Returns a {@link SuggestBuilder} to construct a {@link ValueSuggest} of type {@link ValueSuggestType#EMBEDDED} + * @param values + * @return + */ + public SuggestBuilder embedded(Iterable values) { + this.suggestBuilder = new EmbeddedSuggestBuilder(values); + return suggestBuilder; + } + + /** + * Returns a {@link SuggestBuilder} to construct a {@link ValueSuggest} of type {@link ValueSuggestType#EMBEDDED} + * @param values + * @return + */ + public SuggestBuilder embedded(D[] values) { + this.suggestBuilder = new EmbeddedSuggestBuilder(Arrays.asList(values)); + return suggestBuilder; + } + + /** + * Returns a {@link SuggestBuilder} to construct a {@link LinkSuggest} + * @param link + * @return + */ + public SuggestBuilder link(Link link) { + this.suggestBuilder = new LinkSuggestBuilder(link); + return suggestBuilder; + } + + public SuggestBuilder getSuggestBuilder() { + return suggestBuilder; + } + + public Suggest build() { + return suggestBuilder != null ? suggestBuilder.build() : null; + } +} diff --git a/src/main/java/org/springframework/hateoas/forms/Template.java b/src/main/java/org/springframework/hateoas/forms/Template.java new file mode 100644 index 000000000..ed81b2aac --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/Template.java @@ -0,0 +1,114 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import java.util.List; + +import org.springframework.hateoas.Link; +import org.springframework.web.bind.annotation.RequestMethod; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +/** + * Value object for a HAL-FORMS template. Describes the available state transition details. + * @see http://mamund.site44.com/misc/hal-forms/ + */ +@JsonInclude(Include.NON_DEFAULT) +@JsonPropertyOrder({ "title", "method", "contentType", "properties" }) +@JsonIgnoreProperties({ "href", "rel" }) +public class Template extends Link { + + private static final long serialVersionUID = 2593020248152501268L; + + public static final String DEFAULT_KEY = "default"; + + private List properties; + + private RequestMethod[] method; + + private String contentType; + + private String title; + + public Template() { + } + + public Template(String href) { + this(href, Template.DEFAULT_KEY); + } + + public Template(String href, String key) { + super(href, key); + } + + public void setProperties(List properties) { + this.properties = properties; + } + + public void setMethod(RequestMethod[] method) { + this.method = method; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getContentType() { + return contentType; + } + + public String getTitle() { + return title; + } + + public Property getProperty(String propertyName) { + for (Property property : properties) { + if (property.getName().equals(propertyName)) { + return property; + } + } + return null; + } + + public List getProperties() { + return properties; + } + + @JsonIgnore + public RequestMethod[] getMethod() { + return method; + } + + @JsonProperty("method") + public String getMethodStr() { + if (method == null) { + return null; + } + + StringBuilder sb = new StringBuilder(); + for (RequestMethod rm : method) { + sb.append(rm.toString()).append(','); + } + sb.setLength(sb.length() - 1); + return sb.toString(); + } + +} diff --git a/src/main/java/org/springframework/hateoas/forms/TemplateBuilder.java b/src/main/java/org/springframework/hateoas/forms/TemplateBuilder.java new file mode 100644 index 000000000..5d3d9b3a4 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/TemplateBuilder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import java.net.URI; + +import org.springframework.hateoas.Identifiable; +import org.springframework.hateoas.LinkBuilder; + +/** + * Adds possibility of building {@link Template} instances + * + * FIXME: duplicated code from {@link LinkBuilder} + * + */ +public interface TemplateBuilder { + + /** + * Adds the given object's {@link String} representation as sub-resource to the current URI. Will unwrap + * {@link Identifiable}s to their id value (see {@link Identifiable#getId()}). + * + * @param object + * @return + */ + TemplateBuilder slash(Object object); + + /** + * Adds the given {@link Identifiable}'s id as sub-resource. Will simply return the {@link LinkBuilder} as is if the + * given entity is {@literal null}. + * + * @param identifiable + * @return + */ + TemplateBuilder slash(Identifiable identifiable); + + /** + * Creates a URI of the link built by the current builder instance. + * + * @return + */ + URI toUri(); + + /** + * Creates the {@link Template} using the given key + * @param key + * @return + */ + Template withKey(String key); + + /** + * Creates the {@link Template} using the default key {@link Template#DEFAULT_KEY} + * @return + */ + Template withDefaultKey(); + +} diff --git a/src/main/java/org/springframework/hateoas/forms/TemplateBuilderSupport.java b/src/main/java/org/springframework/hateoas/forms/TemplateBuilderSupport.java new file mode 100644 index 000000000..320cc532d --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/TemplateBuilderSupport.java @@ -0,0 +1,172 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import static org.springframework.web.util.UriComponentsBuilder.fromUri; +import static org.springframework.web.util.UriComponentsBuilder.fromUriString; + +import java.net.URI; +import java.util.List; + +import org.springframework.hateoas.Identifiable; +import org.springframework.hateoas.core.LinkBuilderSupport; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * Base class to implement {@link TemplateBuilder}s based on a Spring MVC {@link UriComponentsBuilder}. + * + */ +public abstract class TemplateBuilderSupport implements TemplateBuilder { + + private final UriComponents uriComponents; + + /** + * Creates a new {@link LinkBuilderSupport} using the given {@link UriComponentsBuilder}. + * + * @param builder must not be {@literal null}. + */ + public TemplateBuilderSupport(UriComponentsBuilder builder) { + + Assert.notNull(builder, "UriComponentsBuilder must not be null!"); + this.uriComponents = builder.build(); + } + + /** + * Creates a new {@link LinkBuilderSupport} using the given {@link UriComponents}. + * + * @param uriComponents must not be {@literal null}. + */ + public TemplateBuilderSupport(UriComponents uriComponents) { + + Assert.notNull(uriComponents, "UriComponents must not be null!"); + this.uriComponents = uriComponents; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.hateoas.LinkBuilder#slash(java.lang.Object) + */ + @Override + public T slash(Object object) { + + if (object == null) { + return getThis(); + } + + if (object instanceof Identifiable) { + return slash((Identifiable) object); + } + + String path = object.toString(); + + if (path.endsWith("#")) { + path = path.substring(0, path.length() - 1); + } + + if (!StringUtils.hasText(path)) { + return getThis(); + } + + String uriString = uriComponents.toUriString(); + UriComponentsBuilder builder = uriString.isEmpty() ? fromUri(uriComponents.toUri()) : fromUriString(uriString); + + UriComponents components = UriComponentsBuilder.fromUriString(path).build(); + + for (String pathSegment : components.getPathSegments()) { + builder.pathSegment(pathSegment); + } + + String fragment = components.getFragment(); + if (StringUtils.hasText(fragment)) { + builder.fragment(fragment); + } + + return createNewInstance(builder.query(components.getQuery())); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.hateoas.LinkBuilder#slash(org.springframework.hateoas.Identifiable) + */ + @Override + public T slash(Identifiable identifyable) { + + if (identifyable == null) { + return getThis(); + } + + return slash(identifyable.getId()); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.hateoas.LinkBuilder#toUri() + */ + @Override + public URI toUri() { + return uriComponents.encode().toUri(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return toUri().normalize().toASCIIString(); + } + + /** + * Returns the current concrete instance. + * + * @return + */ + protected abstract T getThis(); + + /** + * Creates a new instance of the sub-class. + * + * @param builder will never be {@literal null}. + * @return + */ + protected abstract T createNewInstance(UriComponentsBuilder builder); + + @Override + public Template withKey(String key) { + Form form = new Form(toString(), key); + form.setProperties(getProperties()); + form.setMethod(getMethod()); + return form; + } + + @Override + public Template withDefaultKey() { + return withKey(Template.DEFAULT_KEY); + } + + public abstract List getProperties(); + + public abstract RequestMethod[] getMethod(); + +} diff --git a/src/main/java/org/springframework/hateoas/forms/TemplatedResource.java b/src/main/java/org/springframework/hateoas/forms/TemplatedResource.java new file mode 100644 index 000000000..43c381754 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/forms/TemplatedResource.java @@ -0,0 +1,149 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed 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.springframework.hateoas.forms; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.springframework.hateoas.Link; +import org.springframework.hateoas.Resource; +import org.springframework.hateoas.Resources; +import org.springframework.hateoas.core.EmbeddedWrapper; +import org.springframework.hateoas.core.EmbeddedWrappers; +import org.springframework.hateoas.forms.ValueSuggest.ValueSuggestType; +import org.springframework.hateoas.hal.Jackson2HalFormsModule; +import org.springframework.util.Assert; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +public class TemplatedResource extends Resource implements TemplatesSupport { + + private final List