1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.onehippo.forge.exdocpicker.impl.field;
17
18 import java.io.Serializable;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.NoSuchElementException;
22
23 import javax.jcr.Node;
24
25 import org.apache.commons.lang3.StringUtils;
26 import org.apache.wicket.AttributeModifier;
27 import org.apache.wicket.MarkupContainer;
28 import org.apache.wicket.ajax.AjaxRequestTarget;
29 import org.apache.wicket.ajax.markup.html.AjaxLink;
30 import org.apache.wicket.behavior.AttributeAppender;
31 import org.apache.wicket.markup.head.CssHeaderItem;
32 import org.apache.wicket.markup.head.IHeaderResponse;
33 import org.apache.wicket.markup.html.WebMarkupContainer;
34 import org.apache.wicket.markup.html.basic.Label;
35 import org.apache.wicket.markup.html.list.ListItem;
36 import org.apache.wicket.markup.repeater.Item;
37 import org.apache.wicket.markup.repeater.RefreshingView;
38 import org.apache.wicket.markup.repeater.data.IDataProvider;
39 import org.apache.wicket.model.IModel;
40 import org.apache.wicket.model.Model;
41 import org.apache.wicket.model.StringResourceModel;
42 import org.apache.wicket.request.resource.PackageResourceReference;
43 import org.hippoecm.frontend.dialog.AbstractDialog;
44 import org.hippoecm.frontend.dialog.DialogLink;
45 import org.hippoecm.frontend.dialog.IDialogFactory;
46 import org.hippoecm.frontend.model.IModelReference;
47 import org.hippoecm.frontend.model.JcrNodeModel;
48 import org.hippoecm.frontend.model.event.IObservable;
49 import org.hippoecm.frontend.model.event.IObserver;
50 import org.hippoecm.frontend.plugin.IPluginContext;
51 import org.hippoecm.frontend.plugin.config.IPluginConfig;
52 import org.hippoecm.frontend.plugins.standards.diff.LCS;
53 import org.hippoecm.frontend.plugins.standards.diff.LCS.Change;
54 import org.hippoecm.frontend.plugins.standards.diff.LCS.ChangeType;
55 import org.hippoecm.frontend.plugins.standards.icon.HippoIcon;
56 import org.hippoecm.frontend.service.IEditor;
57 import org.hippoecm.frontend.service.render.RenderPlugin;
58 import org.hippoecm.frontend.skin.Icon;
59 import org.onehippo.forge.exdocpicker.api.ExternalDocumentCollection;
60 import org.onehippo.forge.exdocpicker.api.ExternalDocumentServiceContext;
61 import org.onehippo.forge.exdocpicker.api.ExternalDocumentServiceFacade;
62 import org.onehippo.forge.exdocpicker.api.PluginConstants;
63 import org.onehippo.forge.exdocpicker.impl.SimpleExternalDocumentCollectionDataProvider;
64 import org.onehippo.forge.exdocpicker.impl.SimpleExternalDocumentServiceContext;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
67
68
69
70
71 @SuppressWarnings({ "unused", "rawtypes" })
72 public class ExternalDocumentFieldSelectorPlugin extends RenderPlugin<Node> implements IObserver {
73
74 private static final long serialVersionUID = 1L;
75
76 private static final Logger log = LoggerFactory.getLogger(ExternalDocumentFieldSelectorPlugin.class);
77
78 private final JcrNodeModel documentModel;
79
80 private ExternalDocumentServiceFacade<Serializable> exdocService;
81 private ExternalDocumentCollection<Serializable> curDocCollection;
82 private ExternalDocumentServiceContext extDocServiceContext;
83
84
85
86
87
88
89 @SuppressWarnings({ "unchecked" })
90 public ExternalDocumentFieldSelectorPlugin(final IPluginContext context, IPluginConfig config) {
91 super(context, config);
92
93 setOutputMarkupId(true);
94
95 documentModel = (JcrNodeModel)getModel();
96
97 setExternalDocumentServiceFacade((ExternalDocumentServiceFacade<Serializable>) createExternalDocumentService());
98 setExternalDocumentServiceContext(new SimpleExternalDocumentServiceContext(this, config, context,
99 documentModel));
100
101 add(new Label("exdocfield-relateddocs-caption", getCaptionModel()));
102
103 if (isEditMode() && documentModel != null) {
104 context.registerService(new IObserver() {
105 public IObservable getObservable() {
106 return documentModel;
107 }
108
109 public void onEvent(Iterator events) {
110 redraw();
111 }
112 }, IObserver.class.getName());
113 }
114
115 MarkupContainer exdocsContainer = new WebMarkupContainer("exdocs-container");
116
117 boolean exdocsContainerVisible = getPluginConfig()
118 .getAsBoolean(PluginConstants.PARAM_EXTERNAL_DOCUMENTS_CONTAINER_VISIBLE, true);
119
120 exdocsContainer.setVisible(exdocsContainerVisible);
121
122 if (exdocsContainerVisible) {
123 if (!isCompareMode()) {
124 exdocsContainer.add(createRefreshingView(getCurrentExternalDocumentCollection()));
125 } else {
126 if (!getPluginConfig().containsKey("model.compareTo")) {
127 log.warn("no base model service configured");
128 } else {
129 JcrNodeModel compareBaseDocumentModel = null;
130 IModelReference compareBaseRef = getPluginContext()
131 .getService(getPluginConfig().getString("model.compareTo"), IModelReference.class);
132
133 if (compareBaseRef == null) {
134 log.warn("no base model service available");
135 } else {
136 compareBaseDocumentModel = new JcrNodeModel(((JcrNodeModel) compareBaseRef.getModel()).getItemModel().getPath());
137 ExternalDocumentServiceContext comparingContext =
138 new SimpleExternalDocumentServiceContext(this,
139 getPluginConfig(),
140 getPluginContext(),
141 compareBaseDocumentModel);
142 ExternalDocumentCollection<Serializable> baseDocCollection = getExternalDocumentServiceFacade()
143 .getFieldExternalDocuments(comparingContext);
144 exdocsContainer.add(createCompareView(getCurrentExternalDocumentCollection(),
145 baseDocCollection));
146 }
147 }
148 }
149 }
150
151 add(exdocsContainer);
152
153 WebMarkupContainer actionButtonsContainer = new WebMarkupContainer("action-buttons");
154
155 DialogLink browseButton = new DialogLink("browse-button",
156 new StringResourceModel("picker.browse", this, null),
157 createDialogFactory(),
158 getDialogService());
159 actionButtonsContainer.add(browseButton);
160
161 actionButtonsContainer.setVisible(isEditMode());
162 add(actionButtonsContainer);
163 }
164
165
166
167
168 @Override
169 public void renderHead(IHeaderResponse response) {
170 super.renderHead(response);
171 response.render(CssHeaderItem
172 .forReference(new PackageResourceReference(ExternalDocumentFieldSelectorPlugin.class,
173 ExternalDocumentFieldSelectorPlugin.class
174 .getSimpleName() + ".css")));
175 }
176
177
178
179
180
181 protected boolean isEditMode() {
182 return IEditor.Mode.EDIT.equals(IEditor.Mode.fromString(getPluginConfig().getString("mode", "view")));
183 }
184
185
186
187
188
189 protected boolean isCompareMode() {
190 return IEditor.Mode.COMPARE.equals(IEditor.Mode.fromString(getPluginConfig()
191 .getString("mode", "view")));
192 }
193
194
195
196
197
198
199
200 @SuppressWarnings({ "unchecked" })
201 protected ExternalDocumentServiceFacade<? extends Serializable> createExternalDocumentService() {
202 ExternalDocumentServiceFacade<? extends Serializable> service = null;
203 String serviceFacadeClassName = null;
204
205 try {
206 serviceFacadeClassName = getPluginConfig()
207 .getString(PluginConstants.PARAM_EXTERNAL_DOCUMENT_SERVICE_FACADE);
208 Class<? extends ExternalDocumentServiceFacade> serviceClass = (Class<? extends ExternalDocumentServiceFacade>)Class
209 .forName(serviceFacadeClassName);
210 service = serviceClass.getDeclaredConstructor().newInstance();
211 } catch (Exception e) {
212 log.error("Failed to create external document service facade from class name, '{}'.",
213 serviceFacadeClassName, e);
214 }
215
216 return service;
217 }
218
219
220
221
222
223 protected IModel<String> getCaptionModel() {
224 final String defaultCaption = new StringResourceModel("exdocfield.caption", this, null)
225 .setDefaultValue(PluginConstants.DEFAULT_FIELD_CAPTION).getString();
226 String caption = getPluginConfig().getString("caption", defaultCaption);
227 return new StringResourceModel(caption, this, null).setDefaultValue(caption);
228 }
229
230 @SuppressWarnings("unchecked")
231 private RefreshingView<? extends Serializable> createRefreshingView(final ExternalDocumentCollection<Serializable> docCollection) {
232 return new RefreshingView<Serializable>("view") {
233
234 private static final long serialVersionUID = 1L;
235
236 private final IDataProvider<Serializable> dataProvider =
237 new SimpleExternalDocumentCollectionDataProvider<>(docCollection);
238
239
240 @Override
241 protected Iterator getItemModels() {
242
243 final Iterator<? extends Serializable> baseIt = dataProvider
244 .iterator(0, docCollection.getSize());
245
246 return new Iterator<IModel<? extends Serializable>>() {
247 public boolean hasNext() {
248 return baseIt.hasNext();
249 }
250
251 public IModel<? extends Serializable> next() {
252 return dataProvider.model(baseIt.next());
253 }
254
255 public void remove() {
256 baseIt.remove();
257 }
258 };
259 }
260
261 @Override
262 protected void populateItem(Item item) {
263 final Serializable doc = (Serializable)item.getModelObject();
264
265 final Label label = new Label("link-text", new Model<String>() {
266 private static final long serialVersionUID = 1L;
267
268 @Override
269 public String getObject() {
270 return getExternalDocumentServiceFacade()
271 .getDocumentTitle(getExternalDocumentServiceContext(), doc,
272 getRequest().getLocale());
273 }
274 });
275 final String description = getExternalDocumentServiceFacade()
276 .getDocumentDescription(getExternalDocumentServiceContext(), doc, getRequest().getLocale());
277 if (StringUtils.isNotBlank(description)) {
278 label.add(new AttributeModifier("title", description));
279 }
280 item.add(label);
281
282 if (item.getIndex() == docCollection.getSize() - 1) {
283 item.add(new AttributeAppender("class", new Model<>("last"), " "));
284 }
285
286 addControlsToListItem(docCollection, item);
287 }
288 };
289 }
290
291 private RefreshingView createCompareView(final ExternalDocumentCollection<Serializable> docCollection,
292 final ExternalDocumentCollection<Serializable> baseDocCollection) {
293
294 return new RefreshingView("view") {
295
296 @SuppressWarnings("unchecked")
297 @Override
298 protected Iterator getItemModels() {
299 Serializable[] baseDocs = baseDocCollection.toArray(new Serializable[baseDocCollection
300 .getSize()]);
301 Serializable[] currentDocs = docCollection.toArray(new Serializable[docCollection.getSize()]);
302
303 List<Change<Serializable>> changeSet = LCS.getChangeSet(baseDocs, currentDocs);
304 final Change<Serializable>[] changes = changeSet.toArray(new Change[changeSet.size()]);
305
306 return new Iterator<IModel<Change<? extends Serializable>>>() {
307
308 private int changeSetIndex = 0;
309
310 public boolean hasNext() {
311 return changeSetIndex < changes.length;
312 }
313
314 public IModel<Change<? extends Serializable>> next() {
315
316 if (changeSetIndex >= changes.length) {
317 throw new NoSuchElementException();
318 }
319
320 final Change<? extends Serializable> change = changes[changeSetIndex++];
321
322 return new Model<>(change);
323 }
324
325 public void remove() {
326 throw new UnsupportedOperationException();
327 }
328 };
329 }
330
331 @SuppressWarnings("unchecked")
332 @Override
333 protected void populateItem(Item item) {
334 Change<? extends Serializable> change = (Change<? extends Serializable>)item.getModelObject();
335 final Serializable searchDoc = change.getValue();
336
337 final Label label = new Label("link-text", new Model<String>() {
338 private static final long serialVersionUID = 1L;
339
340 @Override
341 public String getObject() {
342 return getExternalDocumentServiceFacade()
343 .getDocumentTitle(getExternalDocumentServiceContext(), searchDoc,
344 getRequest().getLocale());
345 }
346 });
347
348 final String description = getExternalDocumentServiceFacade().getDocumentDescription(
349 getExternalDocumentServiceContext(), searchDoc, getRequest().getLocale());
350 if (StringUtils.isNotBlank(description)) {
351 label.add(new AttributeModifier("title", description));
352 }
353
354 if (change.getType() == ChangeType.ADDED) {
355 label.add(new AttributeAppender("class", new Model<>("hippo-diff-added"), " "));
356 } else if (change.getType() == ChangeType.REMOVED) {
357 label.add(new AttributeAppender("class", new Model<>("hippo-diff-removed"), " "));
358 }
359
360 item.add(label);
361
362 addControlsToListItem(docCollection, item);
363 }
364 };
365 }
366
367
368
369
370
371 protected ExternalDocumentServiceFacade<Serializable> getExternalDocumentServiceFacade() {
372 return exdocService;
373 }
374
375
376
377
378
379 protected void setExternalDocumentServiceFacade(ExternalDocumentServiceFacade<Serializable> exdocService) {
380 this.exdocService = exdocService;
381 }
382
383
384
385
386
387 protected ExternalDocumentCollection<Serializable> getCurrentExternalDocumentCollection() {
388 if (curDocCollection == null) {
389 curDocCollection = getExternalDocumentServiceFacade()
390 .getFieldExternalDocuments(getExternalDocumentServiceContext());
391 }
392
393 return curDocCollection;
394 }
395
396
397
398
399
400 protected void setCurrentExternalDocumentCollection(ExternalDocumentCollection<Serializable> curDocCollection) {
401 this.curDocCollection = curDocCollection;
402 }
403
404
405
406
407
408 protected ExternalDocumentServiceContext getExternalDocumentServiceContext() {
409 return extDocServiceContext;
410 }
411
412
413
414
415
416 protected void setExternalDocumentServiceContext(ExternalDocumentServiceContext extDocServiceContext) {
417 this.extDocServiceContext = extDocServiceContext;
418 }
419
420
421
422
423
424 protected AbstractDialog<ExternalDocumentCollection<Serializable>> createDialogInstance() {
425 return new TextSearchExternalDocumentFieldBrowserDialog(
426 getCaptionModel(),
427 getExternalDocumentServiceContext(),
428 getExternalDocumentServiceFacade(),
429 new Model<ExternalDocumentCollection<Serializable>>(getCurrentExternalDocumentCollection()));
430 }
431
432
433
434
435
436 protected IDialogFactory createDialogFactory() {
437 return new IDialogFactory() {
438 private static final long serialVersionUID = 1L;
439
440 public AbstractDialog<ExternalDocumentCollection<Serializable>> createDialog() {
441 return createDialogInstance();
442 }
443 };
444 }
445
446 private void addControlsToListItem(final ExternalDocumentCollection<Serializable> docCollection, final ListItem<?> item) {
447 final Serializable doc = (Serializable)item.getModelObject();
448
449 final boolean isEditMode = isEditMode();
450 final int itemCount = docCollection.getSize();
451 final int itemIndex = item.getIndex();
452
453 final WebMarkupContainer controls = new WebMarkupContainer("controls");
454 controls.setVisible(isEditMode);
455
456 final MarkupContainer upLink = new AjaxLink<Void>("up") {
457 @Override
458 public void onClick(AjaxRequestTarget target) {
459 boolean removed = docCollection.remove(doc);
460 if (removed) {
461 docCollection.add(itemIndex - 1, doc);
462 exdocService.setFieldExternalDocuments(extDocServiceContext, docCollection);
463 target.add(ExternalDocumentFieldSelectorPlugin.this);
464 }
465 }
466 };
467 upLink.setEnabled(isEditMode && itemIndex > 0);
468 upLink.setVisible(isEditMode);
469 final HippoIcon upIcon = HippoIcon.fromSprite("up-icon", Icon.ARROW_UP);
470 upLink.add(upIcon);
471 controls.add(upLink);
472
473 final MarkupContainer downLink = new AjaxLink<Void>("down") {
474 @Override
475 public void onClick(AjaxRequestTarget target) {
476 boolean removed = docCollection.remove(doc);
477 if (removed) {
478 docCollection.add(itemIndex + 1, doc);
479 exdocService.setFieldExternalDocuments(extDocServiceContext, docCollection);
480 target.add(ExternalDocumentFieldSelectorPlugin.this);
481 }
482 }
483 };
484 downLink.setEnabled(isEditMode && itemIndex < itemCount - 1);
485 downLink.setVisible(isEditMode);
486 final HippoIcon downIcon = HippoIcon.fromSprite("down-icon", Icon.ARROW_DOWN);
487 downLink.add(downIcon);
488 controls.add(downLink);
489
490 final MarkupContainer removeLink = new AjaxLink<Void>("remove") {
491 @Override
492 public void onClick(AjaxRequestTarget target) {
493 boolean removed = docCollection.remove(doc);
494 if (removed) {
495 exdocService.setFieldExternalDocuments(extDocServiceContext, docCollection);
496 target.add(ExternalDocumentFieldSelectorPlugin.this);
497 }
498 }
499 };
500 removeLink.setEnabled(isEditMode);
501 removeLink.setVisible(isEditMode);
502 final HippoIcon removeIcon = HippoIcon.fromSprite("remove-icon", Icon.TIMES);
503 removeLink.add(removeIcon);
504 controls.add(removeLink);
505
506 item.add(controls);
507 }
508 }