Wie in einem früheren Blog versprochen, widme ich mich in diesem Blog der Erweiterung des RESTEasy-Frameworks um die Unterstützung für das Mapping von Formularfeldern auf Objektgraphen mit komplexen Assoziationen, wie Listen und Maps. Diese Erweiterungen wurden RESTEasy als zwei Probleme mit Patches gemeldet. Wenn Sie diese Funktionen mögen, stimmen Sie bitte für diese Themen.
Die Erweiterung des bestehenden Frameworks (mit den Erweiterungen aus dem vorigen Blog) ist gar nicht so schwer und könnte viel einfacher sein, als Sie denken. Der Knackpunkt ist, die richtige Zuordnung von Namen der Formularfelder zu den komplexen Strukturen in den Java-Objekten zu finden. Ein Formular mit diesen Feldern und Werten: [code] firstName=Maarten lastName=Winkels emailAddresses[0].emailAddress=me@xebia.com emailAddresses[1].emailAddress=me@gmail.com phoneNumbers[home].number=0306324178 phoneNumbers[work].number=+31 35 2356785 [/code] Könnte leicht auf die folgende Java-Klasse abgebildet werden. [java] public class Person { @FormParam("Vorname") private String firstName; @FormParam("Nachname") private String lastName; @NestedFormParams("emailAddresses") private Liste<EmailAdresse> emailAdressen; @NestedFormParams("phoneNumbers") private Karte<String, TelefonNummer> phoneNumbers; } public class EmailAddress { @FormParam("emailAddress") private String emailAddress; } public class PhoneNumber { @FormParam("Zahl") private String Nummer; } [/java] Die Zuordnung ist ganz einfach. Eine Liste wird zugeordnet, indem ihr Präfix mit einer Indexnummer zwischen eckigen Klammern angehängt wird. Ein Präfix einer Karte wird mit dem Schlüssel zwischen eckigen Klammern angehängt. Sobald ein Präfix gefunden wurde, ist der Rest der "Deserialisierung" derselbe wie bei der Adresse im vorherigen Blog: Eigenschaften können mit einem Punkt als Trennzeichen an das Präfix angehängt werden. Wenn das RESTEasy-Framework nun auf eine @NestedFormParam stößt, kann es sich den Typ der Eigenschaft ansehen, um herauszufinden, wie die Formularfelder zu interpretieren sind. Außerdem muss es die Typen der Elemente in der Sammlung herausfinden. Dies geschieht in der InjectorFactory oder ihrer Erweiterung. [java] public class ExtendedInjectorFactory extends InjectorFactoryImpl { private final ResteasyProviderFactory providerFactory; public ExtendedInjectorFactory(ResteasyProviderFactory factory) { super(factory); this.providerFactory = factory; } public ValueInjector createParameterExtractor(Class injectTargetClass, AccessibleObject injectTarget, Class type, Type genericType, Annotation[] annotations, boolean useDefault) { NestedFormParams param = FindAnnotation.findAnnotation(annotations, NestedFormParams.class); if (param != null) { String prefix = param.value(); if (genericType instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) genericType; if (isA(List.class, pType)) { return new ListFormInjector(type, getTypeArgument(pType, 0), prefix, providerFactory); } if (isA(Map.class, pType)) { return new MapFormInjector(type, getArgumentType(pType, 0), getTypeArgument(pType, 1), prefix, providerFactory); } } return new NestedFormInjector(type, prefix, providerFactory); } return super.createParameterExtractor(injectTargetClass, injectTarget, type, genericType, annotations, useDefault); } private boolean isA(Class clazz, ParameterizedType pType) { return clazz.isAssignableFrom((Class) pType.getRawType()); } private Class getTypeArgument(ParameterizedType pType, int index) { return (Klasse) pType.getActualTypeArguments()[index]; } } [/java] Durch die Prüfung des Parameters genericType findet die InjectorFactory heraus, welcher Injector verwendet werden muss, um die richtige Klasse zu instanziieren und zu füllen. Die isA-Methode ist eine Hilfsmethode, mit der Sie herausfinden können, ob ein Sammlungstyp angetroffen wurde. Um den Typ der Elemente in der Sammlung herauszufinden, wird die Methode getTypeArgument verwendet. Für eine Eigenschaft vom Typ Map wird auch der Schlüsseltyp benötigt. Beachten Sie, dass bei dieser Implementierung die Sammlung mit dem konkreten Typ ihrer Elemente parametrisiert werden muss! Die Implementierungen von ListFormInjector und MapFormInjector sind sehr ähnlich und haben eine gemeinsame Oberklasse. [java] public abstract class AbstractCollectionFormInjector<T> erweitert NestedFormInjector { private final Class collectionType; private final Pattern Muster; protected AbstractCollectionFormInjector(Class collectionType, Class genericType, String prefix, Pattern pattern, ResteasyProviderFactory factory) { super(genericType, Präfix, Fabrik); this.collectionType = collectionType; this.pattern = pattern; } public Object inject(HttpRequest req, HttpResponse res) { T result = createInstance(collectionType); for (String prefix : findPrefixes(req.getDecodedFormParameters())) { Matcher matcher = pattern.matcher(prefix); matcher.matches(); String key = matcher.group(1); addTo(result, key, super.doInject(prefix, req, res)); } if (leer(Ergebnis)) { null zurückgeben; } Ergebnis zurückgeben; } privat Set<Zeichenfolge> findPrefixes(MultivaluedMap<String, String> Parameter) { final HashSet<Zeichenfolge> Ergebnis = new HashSet<Zeichenfolge>(); for (String parameterName : parameters.keySet()) { final Matcher matcher = pattern.matcher(parameterName); if (matcher.lookingAt() && hasValue(parameters.get(parameterName)))) { result.add(matcher.group(0)); } } Ergebnis zurückgeben; } protected abstract T createInstance(Class collectionType); protected abstract void addTo(T collection, String key, Object value); protected abstract boolean empty(T Ergebnis); } [/java] Die Idee dieses Kurses ist ganz einfach:
- Zunächst werden alle Übereinstimmungen in den Feldnamen des Formulars für den Matcher gesammelt. Wenn das Formular Felder wie 'phoneNumbers[home].number', 'phoneNumbers[home].extension' und 'phoneNumbers[work].number' enthält und das Muster 'phoneNumbers[(.*)]' lautet, dann werden die Präfixe 'phoneNumbers[home]' und 'phoneNumbers[work]' gefunden. Dies geschieht mit der Methode findPrefixes.
- Nun wird für jedes Präfix der Schlüssel extrahiert. Dies ist der Teil des Präfixes, der mit der ersten Gruppe übereinstimmt. In dem obigen Beispiel wäre der erste Schlüssel " home" und der zweite " work".
- Für jedes Präfix wird eine Instanz des gewünschten Elementtyps mit Hilfe der Funktionen des NestedFormInjector erstellt.
- Alle Instanzen werden in einer Sammlung gesammelt, die das Endergebnis dieses Injektors ist.
Es gibt drei Teile dieser Prozedur, die für den jeweiligen Sammlungstyp angepasst werden müssen. Schauen wir uns den ListFormInjector an.
[java]
public class ListFormInjector extends AbstractCollectionFormInjector<List> {
public ListFormInjector(Class collectionType, Class genericType,
String prefix, ResteasyProviderFactory factory) {
super(collectionType, genericType, prefix,
Pattern.compile("^" + prefix + "[(d+)]"), factory);
}
protected List createInstance(Class collectionType) {
return new ArrayList();
}
protected void addTo(List collection, String key, Object value) {
collection.add(Integer.parseInt(key), value);
}
protected boolean empty(List result) {
return result.isEmpty();
}
}
[/java]
Dies ist eine sehr einfache Implementierung. Der wichtigste Teil ist wahrscheinlich die Konstruktion des Musters in Zeile 6. Das Muster besteht aus dem Präfix und einer Zahl zwischen eckigen Klammern. Diese Zahl wird später als Index in der Liste in Zeile 14 verwendet, wo ein neues Element zur Liste hinzugefügt wird.
Fazit
Mit ein paar Klassen haben wir das RESTEasy-Framework so erweitert, dass es die Zuordnung von Formularfeldern zu Sammlungen unterstützen kann. Die erforderlichen Klassen sind diesem Blog in einer
Verfasst von
Maarten Winkels
Contact