View Javadoc
1   package org.argeo.connect.ui.util;
2   
3   import java.text.DateFormat;
4   import java.text.SimpleDateFormat;
5   import java.util.List;
6   import java.util.Map;
7   
8   import javax.jcr.Item;
9   import javax.jcr.Node;
10  import javax.jcr.NodeIterator;
11  import javax.jcr.Property;
12  import javax.jcr.PropertyIterator;
13  import javax.jcr.PropertyType;
14  import javax.jcr.RepositoryException;
15  import javax.jcr.Value;
16  
17  import org.argeo.cms.auth.CurrentUser;
18  import org.argeo.cms.ui.eclipse.forms.AbstractFormPart;
19  import org.argeo.cms.ui.eclipse.forms.FormToolkit;
20  import org.argeo.cms.ui.eclipse.forms.IManagedForm;
21  import org.argeo.cms.util.CmsUtils;
22  import org.argeo.connect.ConnectConstants;
23  import org.argeo.connect.ConnectException;
24  import org.argeo.connect.ConnectNames;
25  import org.argeo.connect.UserAdminService;
26  import org.argeo.connect.util.ConnectJcrUtils;
27  import org.argeo.connect.versioning.ItemDiff;
28  import org.argeo.connect.versioning.VersionDiff;
29  import org.argeo.connect.versioning.VersionUtils;
30  import org.argeo.eclipse.ui.EclipseUiUtils;
31  import org.argeo.jcr.PropertyDiff;
32  import org.argeo.node.NodeConstants;
33  import org.eclipse.swt.SWT;
34  import org.eclipse.swt.layout.FillLayout;
35  import org.eclipse.swt.layout.GridData;
36  import org.eclipse.swt.widgets.Composite;
37  import org.eclipse.swt.widgets.Label;
38  import org.eclipse.swt.widgets.Text;
39  
40  /**
41   * A composite to include in a form and that displays the evolutions of a given
42   * versionable Node over the time.
43   */
44  public class HistoryLog extends LazyCTabControl {
45  	private static final long serialVersionUID = -4736848221960630767L;
46  	// private final static Log log = LogFactory.getLog(HistoryLog.class);
47  
48  	public final static String CTAB_ID = "org.argeo.connect.ui.ctab.history";
49  
50  	private final IManagedForm editor;
51  	private final FormToolkit toolkit;
52  	private final UserAdminService userAdminService;
53  	private final Node entity;
54  	private DateFormat dateTimeFormat = new SimpleDateFormat(ConnectConstants.DEFAULT_DATE_TIME_FORMAT);
55  
56  	// this page UI Objects
57  	private MyFormPart myFormPart;
58  
59  	public HistoryLog(Composite parent, int style, FormToolkit toolkit, IManagedForm editor,
60  			UserAdminService userAdminService, Node entity) {
61  		super(parent, style);
62  		this.editor = editor;
63  		this.toolkit = toolkit;
64  		this.userAdminService = userAdminService;
65  		this.entity = entity;
66  	}
67  
68  	@Override
69  	public void refreshPartControl() {
70  		myFormPart.refresh();
71  		layout(true, true);
72  	}
73  
74  	@Override
75  	public void createPartControl(Composite parent) {
76  		parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
77  
78  		// Add info to be able to find the node via the data explorer
79  		if (CurrentUser.isInRole(NodeConstants.ROLE_DATA_ADMIN) || CurrentUser.isInRole(NodeConstants.ROLE_ADMIN)) {
80  			Label label = new Label(parent, SWT.WRAP);
81  			CmsUtils.markup(label);
82  			GridData gd = EclipseUiUtils.fillWidth();
83  			gd.verticalIndent = 3;
84  			gd.horizontalIndent = 5;
85  			label.setLayoutData(gd);
86  			StringBuilder builder = new StringBuilder();
87  			String puid = ConnectJcrUtils.get(entity, ConnectNames.CONNECT_UID);
88  			if (EclipseUiUtils.notEmpty(puid)) {
89  				builder.append("Connect UID: ").append(puid);
90  				builder.append(" <br/>");
91  			}
92  			builder.append("JcrID: ").append(ConnectJcrUtils.getIdentifier(entity));
93  			builder.append(" <br/>");
94  
95  			builder.append("Path: ").append(ConnectJcrUtils.getPath(entity));
96  			label.setText(builder.toString());
97  		}
98  		Composite historyCmp = new Composite(parent, SWT.NONE);
99  		historyCmp.setLayoutData(EclipseUiUtils.fillAll());
100 		historyCmp.setLayout(new FillLayout());
101 		final Text styledText = toolkit.createText(historyCmp, "", SWT.BORDER | SWT.MULTI | SWT.WRAP | SWT.V_SCROLL);
102 		myFormPart = new MyFormPart(styledText);
103 		myFormPart.initialize(editor);
104 		editor.addPart(myFormPart);
105 	}
106 
107 	private class MyFormPart extends AbstractFormPart {
108 		private final Text text;
109 
110 		public MyFormPart(Text text) {
111 			this.text = text;
112 		}
113 
114 		@Override
115 		public void refresh() {
116 			super.refresh();
117 			refreshHistory(text);
118 		}
119 	}
120 
121 	protected void refreshHistory(Text styledText) {
122 		try {
123 			List<VersionDiff> lst = VersionUtils.listHistoryDiff(entity, VersionUtils.DEFAULT_FILTERED_OUT_PROP_NAMES);
124 			StringBuilder main = new StringBuilder();
125 
126 			for (int i = lst.size() - 1; i >= 0; i--) {
127 				StringBuilder firstL = new StringBuilder();
128 				if (i == 0)
129 					firstL.append("Creation (");
130 				else
131 					firstL.append("Update " + i + " (");
132 
133 				if (lst.get(i).getUserId() != null)
134 					firstL.append("User : " + userAdminService.getUserDisplayName(lst.get(i).getUserId()) + ", ");
135 				if (lst.get(i).getUpdateTime() != null) {
136 					firstL.append("Date : ");
137 					firstL.append(dateTimeFormat.format(lst.get(i).getUpdateTime().getTime()));
138 				}
139 				firstL.append(")");
140 				String fl = firstL.toString();
141 				main.append(fl).append("\n");
142 				for (int j = 0; j < fl.length(); j++)
143 					main.append("=");
144 				main.append("\n");
145 
146 				StringBuilder buf = new StringBuilder();
147 				Map<String, ItemDiff> diffs = lst.get(i).getDiffs();
148 				for (String prop : diffs.keySet()) {
149 					ItemDiff diff = diffs.get(prop);
150 					Item refItem = diff.getReferenceItem();
151 					Item newItem = diff.getObservedItem();
152 					Item tmpItem = refItem == null ? newItem : refItem;
153 					if (tmpItem instanceof Property)
154 						if (((Property) tmpItem).isMultiple())
155 							appendMultiplePropertyModif(buf, (Property) newItem, (Property) refItem);
156 						else {
157 							String refValueStr = "";
158 							String newValueStr = "";
159 							if (refItem != null)
160 								refValueStr = getValueAsString(((Property) refItem).getValue());
161 							if (newItem != null)
162 								newValueStr = getValueAsString(((Property) newItem).getValue());
163 							appendPropModif(buf, diff.getType(), propLabel(diff.getRelPath()), refValueStr,
164 									newValueStr);
165 						}
166 					else { // node
167 						String refStr = refItem == null ? null : ((Node) refItem).getName();
168 						String obsStr = newItem == null ? null : ((Node) newItem).getName();
169 						appendNodeModif(buf, (Node) newItem, diff.getType(), diff.getRelPath(), refStr, obsStr);
170 					}
171 				}
172 				buf.append("\n");
173 				main.append(buf);
174 			}
175 			styledText.setText(main.toString());
176 		} catch (RepositoryException e) {
177 			throw new ConnectException("Cannot generate history for current entity.", e);
178 		}
179 	}
180 
181 	private String getValueAsString(Value refValue) throws RepositoryException {
182 		String refValueStr;
183 		if (refValue.getType() == PropertyType.DATE) {
184 			refValueStr = dateTimeFormat.format(refValue.getDate().getTime());
185 		}
186 		// TODO implement other type formatting if needed.
187 		else
188 			refValueStr = refValue.getString();
189 
190 		return refValueStr;
191 	}
192 
193 	private void appendPropModif(StringBuilder buf, Integer type, String label, String oldValue, String newValue) {
194 
195 		if (type == ItemDiff.MODIFIED) {
196 			buf.append("\t");
197 			buf.append(label).append(": ");
198 			buf.append(oldValue);
199 			buf.append(" > ");
200 			buf.append(newValue);
201 			buf.append("\n");
202 		} else if (type == PropertyDiff.ADDED && !"".equals(newValue)) {
203 			// we don't list property that have been added with an
204 			// empty string as value
205 			buf.append("\t");
206 			buf.append(label).append(": ");
207 			buf.append(" + ");
208 			buf.append(newValue);
209 			buf.append("\n");
210 		} else if (type == PropertyDiff.REMOVED) {
211 			buf.append("\t");
212 			buf.append(label).append(": ");
213 			buf.append(" - ");
214 			buf.append(oldValue);
215 			buf.append("\n");
216 		}
217 	}
218 
219 	private void appendNodeModif(StringBuilder buf, Node node, Integer type, String label, String oldValue,
220 			String newValue) throws RepositoryException {
221 		if (type == PropertyDiff.MODIFIED) {
222 			buf.append("Node ");
223 			buf.append(label).append(" modified: ");
224 			buf.append("\n");
225 		} else if (type == PropertyDiff.ADDED) {
226 			buf.append("Node ");
227 			buf.append(label).append(" added: ");
228 			buf.append("\n");
229 			appendAddedNodeProperties(buf, node, 1);
230 		} else if (type == PropertyDiff.REMOVED) {
231 			buf.append("Node ");
232 			buf.append(label).append(" removed: ");
233 			buf.append("\n");
234 		}
235 	}
236 
237 	// Small hack to list the properties of a added sub node
238 	private void appendAddedNodeProperties(StringBuilder builder, Node node, int level) throws RepositoryException {
239 		PropertyIterator pit = node.getProperties();
240 		while (pit.hasNext()) {
241 			Property prop = pit.nextProperty();
242 			if (!VersionUtils.DEFAULT_FILTERED_OUT_PROP_NAMES.contains(prop.getName())) {
243 				String label = propLabel(prop.getName());
244 				for (int i = 0; i < level; i++)
245 					builder.append("\t");
246 				builder.append(label).append(": ");
247 				builder.append(" + ");
248 				if (prop.isMultiple())
249 					builder.append(ConnectJcrUtils.getMultiAsString(prop.getParent(), prop.getName(), "; "));
250 				else
251 					builder.append(getValueAsString(prop.getValue()));
252 				builder.append("\n");
253 			}
254 		}
255 		NodeIterator nit = node.getNodes();
256 		while (nit.hasNext()) {
257 			Node currNode = nit.nextNode();
258 			for (int i = 0; i < level; i++)
259 				builder.append("\t");
260 			builder.append("Sub Node ");
261 			builder.append(currNode.getName()).append(" added: ");
262 			builder.append("\n");
263 			appendAddedNodeProperties(builder, currNode, level + 1);
264 		}
265 	}
266 
267 	private void appendMultiplePropertyModif(StringBuilder builder, Property obsProp, Property refProp)
268 			throws RepositoryException {
269 
270 		Value[] refValues = null;
271 		if (refProp != null)
272 			refValues = refProp.getValues();
273 
274 		Value[] newValues = null;
275 		if (obsProp != null)
276 			newValues = obsProp.getValues();
277 		if (refProp != null)
278 			refValues: for (Value refValue : refValues) {
279 				if (obsProp != null)
280 					for (Value newValue : newValues) {
281 						if (refValue.equals(newValue))
282 							continue refValues;
283 					}
284 				appendPropModif(builder, PropertyDiff.REMOVED, propLabel(refProp.getName()), getValueAsString(refValue),
285 						null);
286 			}
287 		if (obsProp != null)
288 			newValues: for (Value newValue : newValues) {
289 				if (refProp != null)
290 					for (Value refValue : refValues) {
291 						if (refValue.equals(newValue))
292 							continue newValues;
293 					}
294 				appendPropModif(builder, PropertyDiff.ADDED, propLabel(obsProp.getName()), null,
295 						getValueAsString(newValue));
296 			}
297 	}
298 
299 	/** Small hack to enhance prop label rendering **/
300 	protected String propLabel(String str) {
301 		if (str.lastIndexOf(":") < 2)
302 			return str;
303 		else
304 			str = str.substring(str.lastIndexOf(":") + 1);
305 
306 		StringBuilder builder = new StringBuilder();
307 
308 		for (int i = 0; i < str.length(); i++) {
309 			char curr = str.charAt(i);
310 			if (i == 0)
311 				builder.append(Character.toUpperCase(curr));
312 			else if (Character.isUpperCase(curr))
313 				builder.append(" ").append(curr);
314 			else
315 				builder.append(curr);
316 		}
317 
318 		return builder.toString();
319 	}
320 }