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.i18nsystem.SimpleTranslator;
37 import info.magnolia.rest.client.app.RestClientAppModule;
38 import info.magnolia.rest.client.registry.RestClientRegistry;
39 import info.magnolia.resteasy.client.RestEasyClient;
40 import info.magnolia.ui.api.context.UiContext;
41 import info.magnolia.ui.api.overlay.OverlayCloser;
42 import info.magnolia.ui.api.overlay.OverlayLayer;
43 import info.magnolia.ui.api.view.View;
44 import info.magnolia.ui.vaadin.layout.SmallAppLayout;
45 import info.magnolia.ui.vaadin.overlay.MessageStyleTypeEnum;
46
47 import java.io.IOException;
48 import java.lang.reflect.Method;
49 import java.util.Collection;
50 import java.util.List;
51
52 import javax.inject.Inject;
53 import javax.ws.rs.ClientErrorException;
54
55 import org.apache.commons.lang3.StringUtils;
56 import org.apache.commons.lang3.exception.ExceptionUtils;
57 import org.codehaus.jackson.JsonNode;
58 import org.codehaus.jackson.map.ObjectMapper;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61 import org.vaadin.aceeditor.AceEditor;
62 import org.vaadin.aceeditor.AceMode;
63
64 import com.vaadin.data.Item;
65 import com.vaadin.data.Property;
66 import com.vaadin.data.fieldgroup.FieldGroup;
67 import com.vaadin.event.FieldEvents;
68 import com.vaadin.event.ShortcutAction.KeyCode;
69 import com.vaadin.server.Sizeable;
70 import com.vaadin.ui.AbstractField;
71 import com.vaadin.ui.AbstractOrderedLayout;
72 import com.vaadin.ui.Alignment;
73 import com.vaadin.ui.Button;
74 import com.vaadin.ui.Button.ClickEvent;
75 import com.vaadin.ui.ComboBox;
76 import com.vaadin.ui.Component;
77 import com.vaadin.ui.FormLayout;
78 import com.vaadin.ui.NativeButton;
79 import com.vaadin.ui.TextArea;
80 import com.vaadin.ui.VerticalLayout;
81
82
83
84
85 public class RestClientAppViewImpl implements RestClientAppView {
86
87 private static final Logger log = LoggerFactory.getLogger(RestClientAppViewImpl.class);
88
89 private final UiContext uiContext;
90 private final SimpleTranslator i18n;
91 private final RestClientRegistry restClientRegistry;
92 private final RestClientAppModule module;
93 private Listener listener;
94
95 private final SmallAppLayout root = new SmallAppLayout();
96 private Item dataSource;
97 private ComboBox restClient;
98 private ComboBox restService;
99 private ComboBox method;
100 private TextArea methodAnnotations;
101 private AceEditor methodParameters;
102 private AceEditor body;
103 private AceEditor response;
104 private OverlayCloser overlayCloser;
105 private FieldEvents.FocusListener overlay = new FieldEvents.FocusListener() {
106 @Override
107 public void focus(FieldEvents.FocusEvent event) {
108 overlayCloser = uiContext.openOverlay(getOverlayView(((AbstractField<String>)event.getSource()).getValue()), OverlayLayer.ModalityLevel.STRONG);
109 }
110 };
111
112 @Inject
113 public RestClientAppViewImpl(SimpleTranslator i18n, UiContext uiContext, RestClientRegistry restClientRegistry, RestClientAppModule module) {
114 this.i18n = i18n;
115 this.uiContext = uiContext;
116 this.restClientRegistry = restClientRegistry;
117 this.module = module;
118 }
119
120 private Component createView() {
121
122 final FieldGroup form = new FieldGroup();
123 form.setItemDataSource(dataSource);
124
125
126 restClient = this.createComboBox(i18n.translate("restclient.app.restclient.label"), restClientRegistry.getRestClientsNames());
127 final String restClientDescription = i18n.translate("restclient.app.restclient.description");
128 restClient.setInputPrompt(restClientDescription);
129 restClient.setDescription(restClientDescription);
130
131 methodAnnotations = new TextArea(i18n.translate("restclient.app.annotations.label"));
132 methodAnnotations.setWidth(100, Sizeable.Unit.PERCENTAGE);
133 final String methodAnnotationsDescription = i18n.translate("restclient.app.annotations.description");
134 methodAnnotations.setInputPrompt(methodAnnotationsDescription);
135 methodAnnotations.setDescription(methodAnnotationsDescription);
136 methodAnnotations.addFocusListener(overlay);
137
138 methodParameters = createAceEditor(i18n.translate("restclient.app.parameters.label"));
139 methodParameters.setDescription(i18n.translate("restclient.app.parameters.description"));
140
141 method = this.createComboBox(i18n.translate("restclient.app.method.label"), null);
142 method.setInputPrompt(i18n.translate("restclient.app.method.description"));
143 method.addValueChangeListener(new Property.ValueChangeListener() {
144 @Override
145 public void valueChange(Property.ValueChangeEvent event) {
146 Method methodValue = (Method) method.getValue();
147 String[] descriptionAndParameters = listener.onMethodChange(methodValue);
148 setValue(methodAnnotations, descriptionAndParameters[0], true);
149 setValue(methodParameters, descriptionAndParameters[1], false);
150 }
151 });
152
153 body = createAceEditor(i18n.translate("restclient.app.body.label"));
154 body.setDescription(i18n.translate("restclient.app.body.description"));
155
156 restService = this.createComboBox(i18n.translate("restclient.app.service.label"), module.getServiceClasses());
157 restService.setTextInputAllowed(true);
158 restService.addValueChangeListener(new Property.ValueChangeListener() {
159 @Override
160 public void valueChange(Property.ValueChangeEvent event) {
161 method.setReadOnly(false);
162 method.removeAllItems();
163 addItems(method, getMethods(((String) restService.getValue())));
164 }
165 });
166 final String restServiceDescription = i18n.translate("restclient.app.service.description");
167 restService.setInputPrompt(restServiceDescription);
168 restService.setDescription(restServiceDescription);
169
170 response = this.createAceEditor(i18n.translate("restclient.app.response.label"));
171 response.setDescription(i18n.translate("restclient.app.response.description"));
172 response.addFocusListener(overlay);
173
174 form.bind(restClient, RestClientAppPresenter.REST_CLIENT);
175 form.bind(restService, RestClientAppPresenter.REST_SERVICE);
176 form.bind(methodAnnotations, RestClientAppPresenter.METHOD_ANNOTATIONS);
177 form.bind(method, RestClientAppPresenter.SERVICE_METHOD);
178 form.bind(methodParameters, RestClientAppPresenter.PARAMETERS);
179 form.bind(body, RestClientAppPresenter.REQUEST_BODY);
180 form.bind(response, RestClientAppPresenter.RESPONSE);
181
182 AbstractOrderedLayout layout = new FormLayout();
183 layout.setSpacing(true);
184 layout.setMargin(false);
185 layout.setWidth(100, Sizeable.Unit.PERCENTAGE);
186
187 final Button sendButton = new NativeButton(i18n.translate("restclient.app.send.label"), new Button.ClickListener() {
188
189 @Override
190 public void buttonClick(ClickEvent event) {
191 try {
192 if (!form.isValid()) {
193 uiContext.openNotification(MessageStyleTypeEnum.WARNING, true, i18n.translate("restclient.app.validation.message"));
194 return;
195 }
196 event.getButton().setEnabled(false);
197 String content = formatJson(listener.onRequest(
198 (RestEasyClient) restClientRegistry.getRestClient((String) restClient.getValue()),
199 (String) restService.getValue(),
200 (Method) method.getValue(),
201 methodParameters.getValue(),
202 body.getValue(),
203 null
204 ));
205 setValue(response, content, true);
206 } catch (ClientErrorException e) {
207 Object errorMessage = e.getMessage();
208 try {
209 errorMessage = e.getResponse().readEntity(JsonNode.class);
210 errorMessage = formatJson(errorMessage);
211 } catch (Exception e1) {
212 log.warn("Cannot retrieve error message description from response: {}", errorMessage);
213 }
214 e.getResponse().close();
215 setValue(response, String.valueOf(errorMessage), true);
216 } catch (Throwable e) {
217 setValue(response, ExceptionUtils.getMessage(e) + "\n" + ExceptionUtils.getStackTrace(e), true);
218 } finally {
219 event.getButton().setEnabled(true);
220 }
221 }
222 });
223 sendButton.addStyleName("btn-dialog");
224 sendButton.addStyleName("commit");
225 sendButton.setClickShortcut(KeyCode.SPACEBAR);
226
227 final Button confirmButton = new NativeButton(i18n.translate("restclient.app.confirm.label"), new Button.ClickListener() {
228 @Override
229 public void buttonClick(ClickEvent event) {
230 try {
231 String formatted = formatJson(listener.onBodyFormat((Method) method.getValue(), body.getValue()));
232 setValue(body, formatted, false);
233 } catch (IOException e) {
234 uiContext.openNotification(MessageStyleTypeEnum.WARNING, true, ExceptionUtils.getMessage(e));
235 }
236 }
237 });
238 confirmButton.addStyleName("btn-dialog");
239 confirmButton.addStyleName("commit");
240
241 layout.addComponent(restClient);
242 layout.addComponent(restService);
243 layout.addComponent(method);
244 layout.addComponent(confirmButton);
245 layout.addComponent(methodAnnotations);
246 layout.addComponent(methodParameters);
247 layout.addComponent(body);
248 layout.addComponent(response);
249 layout.addComponent(sendButton);
250
251 return layout;
252 }
253
254 private View getOverlayView(final String content) {
255 return new View() {
256 @Override
257 public Component asVaadinComponent() {
258 VerticalLayout layout = new VerticalLayout();
259 layout.setWidth(60, Sizeable.Unit.PERCENTAGE);
260 layout.setHeight(100, Sizeable.Unit.PERCENTAGE);
261 layout.setSpacing(true);
262
263 final AceEditor field = new AceEditor();
264 field.setImmediate(true);
265 field.setWordWrap(false);
266 field.setWidth(90, Sizeable.Unit.PERCENTAGE);
267 field.setStyleName("textcodefield");
268 field.setMode(AceMode.valueOf("json").name());
269 field.setWordWrap(false);
270 field.setSizeFull();
271 setValue(field, content, true);
272
273
274 final Button closeButton = new Button("Close");
275 closeButton.addStyleName("btn-dialog");
276 closeButton.addStyleName("commit");
277 closeButton.addClickListener(new Button.ClickListener() {
278 @Override
279 public void buttonClick(ClickEvent event) {
280 if (overlayCloser != null) {
281 overlayCloser.close();
282 overlayCloser = null;
283 }
284 }
285 });
286
287 layout.addComponent(field);
288 layout.addComponent(closeButton);
289
290 layout.setExpandRatio(field, 2);
291 layout.setExpandRatio(closeButton, 1);
292
293 layout.setComponentAlignment(closeButton, Alignment.TOP_RIGHT);
294
295 return layout;
296 }
297 };
298 }
299
300 @Override
301 public void setDataSource(Item item) {
302 this.dataSource = item;
303 for (Component section : root.getSections()) {
304 root.removeSection(section);
305 }
306 root.addSection(createView());
307 }
308
309 @Override
310 public Component asVaadinComponent() {
311 return root;
312 }
313
314 @Override
315 public void setListener(Listener listener) {
316 this.listener = listener;
317 }
318
319 private void setValue(AbstractField field, String value, boolean readOnly) {
320 if (readOnly) field.setReadOnly(false);
321 field.setValue(value);
322 if (readOnly) field.setReadOnly(true);
323 }
324
325 private List<Method> getMethods(String className) {
326 try {
327 return listener.onRestClientChange(className);
328 } catch (ClassNotFoundException e) {
329 uiContext.openNotification(MessageStyleTypeEnum.WARNING, true, ExceptionUtils.getMessage(e));
330 }
331 return null;
332 }
333
334 private ComboBox createComboBox(String caption, Collection<? extends Object> fieldNames) {
335 final ComboBox field = new ComboBox(caption);
336 this.addItems(field, fieldNames);
337 field.setRequired(true);
338 field.setNewItemsAllowed(true);
339 field.setWidth(100, Sizeable.Unit.PERCENTAGE);
340 field.setNullSelectionAllowed(false);
341 field.setTextInputAllowed(false);
342 return field;
343 }
344
345 private void addItems(ComboBox field, Collection<? extends Object> fieldNames) {
346 if (fieldNames != null) {
347 for (Object name : fieldNames) {
348 field.addItem(name);
349 }
350 }
351 }
352
353 private AceEditor createAceEditor(String caption) {
354 final AceEditor field = new AceEditor();
355 field.setCaption(caption);
356 field.setWordWrap(true);
357 field.setWidth(100, Sizeable.Unit.PERCENTAGE);
358 field.setHeight(7, Sizeable.Unit.REM);
359 field.setStyleName("textcodefield");
360 field.setMode(AceMode.valueOf("json").name());
361 field.setWordWrap(false);
362 return field;
363 }
364
365 private String formatJson(Object input) throws IOException {
366 String indented = String.valueOf(input);
367 if (StringUtils.isBlank(indented)) {
368 return indented;
369 }
370 ObjectMapper mapper = new ObjectMapper();
371 Object json = mapper.readValue(indented, Object.class);
372 indented = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(json);
373 return indented;
374 }
375
376 }