View Javadoc
1   package org.argeo.connect.ui.parts;
2   
3   import java.util.ArrayList;
4   import java.util.List;
5   
6   import javax.jcr.Node;
7   import javax.jcr.Property;
8   import javax.jcr.RepositoryException;
9   import javax.jcr.Session;
10  import javax.jcr.Value;
11  import javax.jcr.nodetype.NodeType;
12  import javax.jcr.version.VersionManager;
13  
14  import org.apache.commons.logging.Log;
15  import org.apache.commons.logging.LogFactory;
16  import org.argeo.cms.ui.eclipse.forms.AbstractFormPart;
17  import org.argeo.cms.ui.eclipse.forms.FormToolkit;
18  import org.argeo.cms.util.CmsUtils;
19  import org.argeo.connect.ConnectException;
20  import org.argeo.connect.resources.ResourcesNames;
21  import org.argeo.connect.resources.ResourcesService;
22  import org.argeo.connect.ui.ConnectEditor;
23  import org.argeo.connect.ui.ConnectUiStyles;
24  import org.argeo.connect.ui.ConnectUiUtils;
25  import org.argeo.connect.ui.SystemWorkbenchService;
26  import org.argeo.connect.ui.widgets.TagLikeDropDown;
27  import org.argeo.connect.util.ConnectJcrUtils;
28  import org.argeo.eclipse.ui.EclipseUiUtils;
29  import org.eclipse.jface.dialogs.MessageDialog;
30  import org.eclipse.swt.SWT;
31  import org.eclipse.swt.events.SelectionAdapter;
32  import org.eclipse.swt.events.SelectionEvent;
33  import org.eclipse.swt.events.TraverseEvent;
34  import org.eclipse.swt.events.TraverseListener;
35  import org.eclipse.swt.layout.GridData;
36  import org.eclipse.swt.layout.RowData;
37  import org.eclipse.swt.layout.RowLayout;
38  import org.eclipse.swt.widgets.Button;
39  import org.eclipse.swt.widgets.Composite;
40  import org.eclipse.swt.widgets.Link;
41  import org.eclipse.swt.widgets.Shell;
42  import org.eclipse.swt.widgets.Text;
43  //import org.eclipse.ui.forms.AbstractFormPart;
44  //import org.eclipse.ui.forms.widgets.FormToolkit;
45  
46  /**
47   * Wraps an Abstract form part that enable management of a tag like list in a
48   * form editor.
49   */
50  public class TagLikeListSmallPart extends Composite {
51  	private static final long serialVersionUID = -312141685147619814L;
52  	private final static Log log = LogFactory.getLog(TagLikeListSmallPart.class);
53  
54  	// UI Context
55  	private final ConnectEditor editor;
56  	private final FormToolkit toolkit;
57  	private final String newTagMsg;
58  
59  	// Context
60  	private final ResourcesService resourcesService;
61  	private final SystemWorkbenchService systemWorkbenchService;
62  	private final Node taggable;
63  	private final Node tagParent;
64  	private final String tagId;
65  	private String tagCodePropName = null;
66  	private String cssStyle = ConnectUiStyles.ENTITY_HEADER;
67  	private final String taggablePropName;
68  
69  	// Deduced from the context, shortcut for this class
70  	private final Session session;
71  
72  	/**
73  	 * 
74  	 * @param parent
75  	 * @param style
76  	 * @param toolkit
77  	 * @param form
78  	 * @param resourcesService
79  	 * @param systemWorkbenchService
80  	 * @param taggable
81  	 * @param tagId
82  	 * @param newTagMsg
83  	 */
84  	public TagLikeListSmallPart(ConnectEditor editor, Composite parent, int style, ResourcesService resourcesService,
85  			SystemWorkbenchService systemWorkbenchService, String tagId, Node taggable, String taggablePropName,
86  			String newTagMsg) {
87  		super(parent, style);
88  		this.editor = editor;
89  		this.toolkit = editor.getFormToolkit();
90  		this.resourcesService = resourcesService;
91  		this.systemWorkbenchService = systemWorkbenchService;
92  		this.tagId = tagId;
93  		this.taggable = taggable;
94  		this.taggablePropName = taggablePropName;
95  		this.newTagMsg = newTagMsg;
96  
97  		// Cache some context object to ease implementation
98  		session = ConnectJcrUtils.getSession(taggable);
99  		tagParent = resourcesService.getTagLikeResourceParent(session, tagId);
100 
101 		try {
102 			if (tagParent.hasProperty(ResourcesNames.RESOURCES_TAG_CODE_PROP_NAME))
103 				tagCodePropName = tagParent.getProperty(ResourcesNames.RESOURCES_TAG_CODE_PROP_NAME).getString();
104 		} catch (RepositoryException e) {
105 			throw new ConnectException("unable to get tag prop name for " + tagParent, e);
106 		}
107 		RowLayout rl = new RowLayout(SWT.HORIZONTAL);
108 		rl.wrap = true;
109 		rl.marginLeft = rl.marginTop = rl.marginBottom = 0;
110 		rl.marginRight = 8;
111 		this.setLayout(rl);
112 
113 		AbstractFormPart tagFormPart = new TagFormPart(this);
114 		// must be refreshed on first pass.
115 		tagFormPart.refresh();
116 		tagFormPart.initialize(editor.getManagedForm());
117 		editor.getManagedForm().addPart(tagFormPart);
118 	}
119 
120 	private class TagFormPart extends AbstractFormPart {
121 		private Composite parentCmp;
122 
123 		// Cache to trace newly created versionable tag like objects.
124 		private List<String> createdTagPath = new ArrayList<String>();
125 
126 		// caches current value to check if the composite must be redrawn
127 		// private Value[] currValues;
128 
129 		public TagFormPart(Composite parent) {
130 			this.parentCmp = parent;
131 		}
132 
133 		@Override
134 		public void commit(boolean onSave) {
135 
136 			boolean isEmpty = createdTagPath.isEmpty();
137 			if (onSave && !isEmpty) {
138 				try {
139 					Session session = taggable.getSession();
140 					if (session.hasPendingChanges()) {
141 						log.warn("Session have been saved before commit " + "of newly created tags when saving node "
142 								+ taggable);
143 						session.save();
144 					}
145 					VersionManager manager = session.getWorkspace().getVersionManager();
146 					for (String path : createdTagPath) {
147 						Node newTag = session.getNode(path);
148 						if (newTag.isCheckedOut()) {
149 							manager.checkin(path);
150 						}
151 					}
152 					createdTagPath.clear();
153 				} catch (RepositoryException re) {
154 					throw new ConnectException("Error while committing tagrefreshing tag like list for " + taggable,
155 							re);
156 				}
157 			}
158 			super.commit(onSave);
159 		}
160 
161 		public void refresh() {
162 			super.refresh();
163 			if (parentCmp.isDisposed())
164 				return;
165 
166 			// We redraw the full control at each refresh, might be a more
167 			// efficient way to do
168 			CmsUtils.clear(parentCmp);
169 			boolean isCO = editor.isEditing();
170 
171 			try {
172 				if (taggable.hasProperty(taggablePropName)) {
173 					Value[] values = taggable.getProperty(taggablePropName).getValues();
174 					for (final Value value : values) {
175 						final String tagKey = value.getString();
176 
177 						String tagValue;
178 						if (tagCodePropName != null)
179 							tagValue = resourcesService.getEncodedTagValue(session, tagId, tagKey);
180 						else
181 							tagValue = tagKey;
182 
183 						Composite tagCmp = toolkit.createComposite(parentCmp, SWT.NO_FOCUS);
184 						tagCmp.setLayout(ConnectUiUtils.noSpaceGridLayout(2));
185 						Link link = new Link(tagCmp, SWT.NONE);
186 						CmsUtils.markup(link);
187 
188 						if (taggablePropName.equals(ResourcesNames.CONNECT_TAGS)) {
189 							link.setText(" #<a>" + tagValue + "</a>");
190 							// } else if
191 							// (taggablePropName.equals(PeopleNames.PEOPLE_MAILING_LISTS))
192 							// {
193 							// link.setText(" @<a>" + tagValue + "</a>");
194 						} else
195 							link.setText(" <a>" + tagValue + "</a>");
196 
197 						CmsUtils.style(link, cssStyle);
198 
199 						link.addSelectionListener(new SelectionAdapter() {
200 							private static final long serialVersionUID = 1L;
201 
202 							@Override
203 							public void widgetSelected(final SelectionEvent event) {
204 								Node tag = resourcesService.getRegisteredTag(tagParent, tagKey);
205 
206 								try {
207 									if (createdTagPath.contains(tag.getPath())) {
208 										String msg = "This category is still in a draft state.\n"
209 												+ "Please save first.";
210 										MessageDialog.openInformation(parentCmp.getShell(), "Forbidden action", msg);
211 									} else {
212 										// CommandUtils.callCommand(systemWorkbenchService.getOpenEntityEditorCmdId(),
213 										// ConnectEditor.PARAM_JCR_ID, ConnectJcrUtils.getIdentifier(tag));
214 										systemWorkbenchService.openEntityEditor(tag);
215 									}
216 								} catch (RepositoryException e) {
217 									throw new ConnectException("unable to get path for resource tag node " + tag
218 											+ " while editing " + taggable, e);
219 								}
220 							}
221 						});
222 
223 						if (isCO) {
224 							addDeleteButton(TagFormPart.this, tagCmp, value);
225 						}
226 					}
227 				}
228 				if (isCO) {
229 					final Text tagTxt = toolkit.createText(parentCmp, "", SWT.BORDER);
230 					tagTxt.setMessage(newTagMsg);
231 					RowData rd = new RowData(80, SWT.DEFAULT);
232 					tagTxt.setLayoutData(rd);
233 
234 					final TagLikeDropDown tagDD = new TagLikeDropDown(session, resourcesService, tagId, tagTxt);
235 
236 					tagTxt.addTraverseListener(new TraverseListener() {
237 						private static final long serialVersionUID = 1L;
238 
239 						public void keyTraversed(TraverseEvent e) {
240 							if (e.keyCode == SWT.CR) {
241 								String newTag = tagDD.getText();
242 								addTag(tagTxt.getShell(), TagFormPart.this, newTag);
243 								e.doit = false;
244 								// if (!tagTxt.isDisposed())
245 								// tagDD.reset("");
246 								// tagTxt.setText("");
247 							}
248 						}
249 					});
250 					// we must call this so that the row data can copute the OK
251 					// button size.
252 					tagTxt.getParent().layout();
253 
254 					Button okBtn = toolkit.createButton(parentCmp, "OK", SWT.BORDER | SWT.PUSH | SWT.BOTTOM);
255 					// Button okBtn = new Button(parentCmp, SWT.BORDER |
256 					// SWT.PUSH
257 					// | SWT.BOTTOM);
258 					// okBtn.setText("OK");
259 					rd = new RowData(SWT.DEFAULT, tagTxt.getSize().y - 2);
260 					okBtn.setLayoutData(rd);
261 
262 					okBtn.addSelectionListener(new SelectionAdapter() {
263 						private static final long serialVersionUID = 2780819012423622369L;
264 
265 						@Override
266 						public void widgetSelected(SelectionEvent e) {
267 							String newTag = tagDD.getText();
268 							if (EclipseUiUtils.isEmpty(newTag))
269 								return;
270 							else
271 								addTag(parentCmp.getShell(), TagFormPart.this, newTag);
272 						}
273 					});
274 				}
275 				parentCmp.layout();
276 				parentCmp.getParent().getParent().layout();
277 			} catch (RepositoryException re) {
278 				throw new ConnectException("Error while refreshing tag like list for " + taggable, re);
279 			}
280 		}
281 
282 		private void addTag(Shell shell, final AbstractFormPart part, String newTag) {
283 			String msg = null;
284 			try {
285 				Session session = taggable.getSession();
286 				// Retrieve code from value
287 				if (tagCodePropName != null)
288 					newTag = resourcesService.getEncodedTagCodeFromValue(session, tagId, newTag);
289 
290 				// Check if a tag with such a key is already registered
291 				Node registered = resourcesService.getRegisteredTag(tagParent, newTag);
292 
293 				if (registered == null) {
294 					if (resourcesService.canCreateTag(session)) {
295 
296 						// Ask end user if we create a new tag
297 						msg = "\"" + newTag + "\" is not yet registered.\n Are you sure you want to create it?";
298 						if (MessageDialog.openConfirm(shell, "Confirm creation", msg)) {
299 							registered = resourcesService.registerTag(session, tagId, newTag);
300 							if (registered.isNodeType(NodeType.MIX_VERSIONABLE))
301 								createdTagPath.add(registered.getPath());
302 						} else
303 							return;
304 					} else {
305 						msg = "\"" + newTag + "\" is not yet registered\n"
306 								+ "and you don't have sufficient rights to create it.\n"
307 								+ "Please contact a Business Admin and ask him "
308 								+ "to register it for you if it is valid.";
309 						MessageDialog.openError(shell, "Unvalid choice", msg);
310 						return;
311 					}
312 				}
313 
314 				Value[] values;
315 				String[] valuesStr;
316 				if (taggable.hasProperty(taggablePropName)) {
317 					values = taggable.getProperty(taggablePropName).getValues();
318 
319 					// Check duplicates
320 					for (Value tag : values) {
321 						String curTagUpperCase = tag.getString().toUpperCase().trim();
322 						if (newTag.toUpperCase().trim().equals(curTagUpperCase)) {
323 							msg = "\"" + ConnectJcrUtils.get(taggable, Property.JCR_TITLE)
324 									+ "\" is already linked with \"" + tag.getString() + "\". Nothing has been done.";
325 							MessageDialog.openError(shell, "Duplicate link", msg);
326 							return;
327 						}
328 					}
329 
330 					valuesStr = new String[values.length + 1];
331 					int i;
332 					for (i = 0; i < values.length; i++) {
333 						valuesStr[i] = values[i].getString();
334 					}
335 					valuesStr[i] = newTag;
336 				} else {
337 					valuesStr = new String[1];
338 					valuesStr[0] = newTag;
339 				}
340 				taggable.setProperty(taggablePropName, valuesStr);
341 				part.markDirty();
342 				part.refresh();
343 			} catch (RepositoryException re) {
344 				throw new ConnectException("Unable to set " + taggablePropName + " on " + taggable, re);
345 			}
346 		}
347 	}
348 
349 	private void addDeleteButton(final AbstractFormPart part, Composite parent, final Value value) {
350 		final Button deleteBtn = new Button(parent, SWT.FLAT);
351 		CmsUtils.style(deleteBtn, ConnectUiStyles.SMALL_DELETE_BTN);
352 		deleteBtn.setLayoutData(new GridData(8, 8));
353 		deleteBtn.addSelectionListener(new SelectionAdapter() {
354 			private static final long serialVersionUID = 1L;
355 
356 			@Override
357 			public void widgetSelected(final SelectionEvent event) {
358 				try {
359 					String tagToRemove = value.getString();
360 					List<String> tags = new ArrayList<String>();
361 					Value[] values = taggable.getProperty(taggablePropName).getValues();
362 					for (int i = 0; i < values.length; i++) {
363 						String curr = values[i].getString();
364 						if (!tagToRemove.equals(curr))
365 							tags.add(curr);
366 					}
367 					taggable.setProperty(taggablePropName, tags.toArray(new String[tags.size()]));
368 					part.markDirty();
369 					part.refresh();
370 				} catch (RepositoryException e) {
371 					throw new ConnectException("unable to initialise deletion", e);
372 				}
373 			}
374 		});
375 	}
376 }