1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.rest.client.app.ui;
35
36 import info.magnolia.context.Context;
37 import info.magnolia.context.MgnlContext;
38 import info.magnolia.i18nsystem.SimpleTranslator;
39 import info.magnolia.resteasy.client.RestEasyClient;
40
41 import java.io.EOFException;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.lang.annotation.Annotation;
45 import java.lang.reflect.InvocationTargetException;
46 import java.lang.reflect.Method;
47 import java.net.URLDecoder;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.List;
51 import java.util.Map;
52
53 import javax.inject.Inject;
54 import javax.jcr.RepositoryException;
55
56 import org.apache.commons.lang3.ClassUtils;
57 import org.apache.commons.lang3.StringUtils;
58 import org.codehaus.jackson.JsonNode;
59 import org.codehaus.jackson.map.ObjectMapper;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 import com.vaadin.data.util.ObjectProperty;
64 import com.vaadin.data.util.PropertysetItem;
65 import com.vaadin.server.Page;
66 import com.vaadin.server.StreamResource;
67
68
69
70
71 public class RestClientAppPresenter implements RestClientAppView.Listener {
72
73 protected static final String RESPONSE = "Response";
74 protected static final String PARAMETERS = "Parameters";
75 protected static final String REQUEST_BODY = "Body";
76 protected static final String REST_CLIENT = "Client";
77 protected static final String REST_SERVICE = "Service";
78 protected static final String SERVICE_METHOD = "Method";
79 protected static final String METHOD_ANNOTATIONS = "Annotations";
80
81 private static final Logger log = LoggerFactory.getLogger(RestClientAppPresenter.class);
82
83 private final RestClientAppView view;
84 private final SimpleTranslator i18n;
85
86 private ObjectMapper mapper = new ObjectMapper();
87 private PropertysetItem item;
88
89 @Inject
90 public RestClientAppPresenter(RestClientAppView view, SimpleTranslator i18n) {
91 this.view = view;
92 this.i18n = i18n;
93 }
94
95 public RestClientAppView start() {
96 view.setListener(this);
97 try {
98
99 view.setDataSource(createDataSource());
100 } catch (RepositoryException e) {
101 log.warn("An error occured while creating the app view", e);
102 }
103 return view;
104 }
105
106 @Override
107 public String onRequest(RestEasyClient client, String serviceClass, final Method methodInstance, final String parameters, final String body, String contextAttributes) throws Throwable {
108 setMgnlContextParams(contextAttributes);
109 final Object service = client.getClientService(this.getClass(serviceClass));
110 Object result = MgnlContext.doInSystemContext(new MgnlContext.Op<Object, Throwable>() {
111 @Override
112 public Object exec() throws Throwable {
113 try {
114 return methodInstance.invoke(service, parseParameters(methodInstance, parameters, body));
115 } catch (InvocationTargetException e) {
116 throw e.getTargetException();
117 }
118 }
119 });
120 return processOutput(result);
121 }
122
123 @Override
124 public String onBodyFormat(Method methodValue, String body) throws IOException {
125 if (StringUtils.isBlank(body) || methodValue == null) {
126 return body;
127 }
128 Class bodyClass = this.getBodyClass(methodValue.getParameterTypes(), methodValue.getParameterAnnotations());
129 if (bodyClass != null && JsonNode.class.isAssignableFrom(bodyClass)) {
130 return body;
131 }
132 return body;
133 }
134
135 @Override
136 public String[] onMethodChange(Method method) {
137 final StringBuilder methodDescription = new StringBuilder();
138 final StringBuilder paramsNames = new StringBuilder();
139
140 if (method != null) {
141 for (Annotation annotation : method.getAnnotations()) {
142 methodDescription.append(annotation);
143 methodDescription.append("\n");
144 }
145 methodDescription.append(i18n.translate("restclient.app.parameters.methodParameter"));
146
147 Annotation[][] annotations = method.getParameterAnnotations();
148 Class[] methodParameters = method.getParameterTypes();
149 method.getTypeParameters();
150 for (int i = 0; i < methodParameters.length; i++) {
151 methodDescription.append(i + 1);
152 methodDescription.append(". ");
153 if (annotations[i].length == 0) {
154 methodDescription.append(i18n.translate("restclient.app.parameters.body"));
155 } else {
156 methodDescription.append(Arrays.asList(annotations[i]));
157 paramsNames.append(this.getAnnotationValue(annotations[i]));
158 paramsNames.append("=\n");
159 }
160 methodDescription.append(" (");
161 methodDescription.append(methodParameters[i].getSimpleName());
162 methodDescription.append(")");
163 methodDescription.append("\n");
164 }
165 }
166 final String paramsNamesStr = paramsNames.length() == 0 ? StringUtils.EMPTY : paramsNames.substring(0, paramsNames.length() - 1);
167 return new String[]{methodDescription.toString(), paramsNamesStr};
168 }
169
170 @Override
171 public List<Method> onRestClientChange(String className) throws ClassNotFoundException {
172 if (StringUtils.isNotEmpty(className)) {
173 Class serviceClass = this.getClass(className);
174 return Arrays.asList(serviceClass.getMethods());
175 }
176 return null;
177 }
178
179 private String getAnnotationValue(Annotation[] annotations) {
180 final StringBuilder value = new StringBuilder();
181 for (Annotation annotation : annotations) {
182 value.append(StringUtils.substringBetween(annotation.toString(), "=", ")"));
183 }
184 return value.toString();
185 }
186
187 private Class getClass(String className) throws ClassNotFoundException {
188 return Class.forName(className);
189 }
190
191 private Object[] parseParameters(Method method, String parametersAsString, String body) throws IOException {
192 List<Object> parameters = new ArrayList<Object>();
193
194 if (StringUtils.isNotEmpty(parametersAsString)) {
195 Map<String, String> parametersMap = com.google.common.base.Splitter.on("\n").withKeyValueSeparator("=").split(StringUtils.trimToEmpty(parametersAsString));
196 Class[] types = method.getParameterTypes();
197 Annotation[][] annotations = method.getParameterAnnotations();
198 if (types.length != parametersMap.size() + (this.getBodyClass(types, annotations) != null ? 1 : 0)) {
199 throw new IllegalArgumentException("Wrong number of arguments");
200 }
201 for (int i = 0; i < types.length; i++) {
202 if (annotations[i].length == 0) {
203 parameters.add(corvertValue(body, types[i]));
204 } else {
205 parameters.add(corvertValue(parametersMap.get(this.getAnnotationValue(annotations[i])), types[i]));
206 }
207 }
208 }
209 return parameters.toArray();
210 }
211
212 private Class getBodyClass(Class[] types, Annotation[][] annotations) {
213 for (int i = 0; i < types.length; i++) {
214 if (annotations[i].length == 0) {
215 return types[i];
216 }
217 }
218 return null;
219 }
220
221 protected Object corvertValue(String input, Class clazz) throws IOException {
222 if (StringUtils.EMPTY.equals(input)) {
223 return null;
224 }
225 Object convertedValue = null;
226 Class wrapperClass = clazz;
227 if (clazz.isPrimitive()) {
228 wrapperClass = ClassUtils.primitiveToWrapper(clazz);
229 }
230
231 try {
232 if (JsonNode.class.isAssignableFrom(wrapperClass)) {
233 convertedValue = mapper.readTree(input);
234 } else {
235 convertedValue = wrapperClass.getConstructor(String.class).newInstance(input);
236 }
237 } catch (EOFException e) {
238 throw new IllegalArgumentException(String.format("Illegal value '%s' for class '%s'.", input, wrapperClass), e);
239 } catch (Exception e) {
240 throw new IllegalArgumentException(String.format("Don't know how to convert '%s' to an instance of '%s'. " +
241 "Override method '%s' if you need to handle this type.", input, clazz, "info.magnolia.rest.client.app.RestClientAppViewImpl.corvertValue"), e);
242 }
243 if (clazz.isPrimitive()) {
244 return convertedValue.hashCode();
245 }
246 return convertedValue;
247 }
248
249 protected String processOutput(Object output) throws IOException {
250 String result = String.valueOf(output);
251 if (output instanceof InputStream) {
252 this.download("restCallResponse", StringUtils.EMPTY, (InputStream) output);
253 }
254 return result;
255 }
256
257 private void setMgnlContextParams(String params) {
258 if (StringUtils.isBlank(params)) {
259 return;
260 }
261 Map<String, String> parametersMap = com.google.common.base.Splitter.on("\n").withKeyValueSeparator("=").split(StringUtils.trimToEmpty(params));
262 for (Map.Entry<String, String> entry : parametersMap.entrySet()) {
263 String value = StringUtils.isEmpty(entry.getValue()) ? null : URLDecoder.decode(entry.getValue());
264 MgnlContext.setAttribute(entry.getKey(), value, Context.SESSION_SCOPE);
265 }
266 }
267
268 private PropertysetItem createDataSource() throws RepositoryException {
269 item = new PropertysetItem();
270 item.addItemProperty(REST_CLIENT, new ObjectProperty<RestEasyClient>(null, RestEasyClient.class));
271 item.addItemProperty(REST_SERVICE, new ObjectProperty<String>(StringUtils.EMPTY));
272 item.addItemProperty(METHOD_ANNOTATIONS, new ObjectProperty<String>(StringUtils.EMPTY));
273 item.addItemProperty(SERVICE_METHOD, new ObjectProperty<Method>(null, Method.class));
274 item.addItemProperty(PARAMETERS, new ObjectProperty<String>(StringUtils.EMPTY));
275 item.addItemProperty(REQUEST_BODY, new ObjectProperty<String>(StringUtils.EMPTY));
276 item.addItemProperty(RESPONSE, new ObjectProperty<String>(StringUtils.EMPTY));
277 return item;
278 }
279
280 private void download(String fileName, String mimeType, final InputStream output) {
281 StreamResource.StreamSource source = new StreamResource.StreamSource() {
282 @Override
283 public InputStream getStream() {
284 return output;
285 }
286 };
287 StreamResource resource = new StreamResource(source, fileName);
288
289
290
291 resource.setCacheTime(-1);
292 resource.getStream().setParameter("Content-Disposition", "attachment; filename=" + fileName + "\"");
293 resource.setMIMEType(mimeType);
294 Page.getCurrent().open(resource, StringUtils.EMPTY, true);
295 }
296
297 }