View Javadoc
1   package org.argeo.cms.ui.viewers;
2   
3   import java.security.AccessControlContext;
4   import java.security.AccessController;
5   import java.security.PrivilegedAction;
6   import java.util.Observable;
7   import java.util.Observer;
8   
9   import javax.jcr.Node;
10  import javax.jcr.RepositoryException;
11  import javax.jcr.Session;
12  import javax.security.auth.Subject;
13  
14  import org.apache.commons.logging.Log;
15  import org.apache.commons.logging.LogFactory;
16  import org.argeo.cms.CmsException;
17  import org.argeo.cms.ui.CmsEditable;
18  import org.argeo.cms.ui.widgets.ScrolledPage;
19  import org.eclipse.jface.viewers.ContentViewer;
20  import org.eclipse.jface.viewers.ISelection;
21  import org.eclipse.jface.viewers.StructuredSelection;
22  import org.eclipse.swt.SWT;
23  import org.eclipse.swt.events.FocusEvent;
24  import org.eclipse.swt.events.FocusListener;
25  import org.eclipse.swt.events.MouseAdapter;
26  import org.eclipse.swt.events.MouseListener;
27  import org.eclipse.swt.widgets.Composite;
28  import org.eclipse.swt.widgets.Control;
29  import org.eclipse.swt.widgets.Widget;
30  import org.xml.sax.SAXParseException;
31  
32  /** Base class for viewers related to a page */
33  public abstract class AbstractPageViewer extends ContentViewer implements Observer {
34  	private static final long serialVersionUID = 5438688173410341485L;
35  
36  	private final static Log log = LogFactory.getLog(AbstractPageViewer.class);
37  
38  	private final boolean readOnly;
39  	/** The basis for the layouts, typically a ScrolledPage. */
40  	private final Composite page;
41  	private final CmsEditable cmsEditable;
42  
43  	private MouseListener mouseListener;
44  	private FocusListener focusListener;
45  
46  	private EditablePart edited;
47  	private ISelection selection = StructuredSelection.EMPTY;
48  
49  	private AccessControlContext accessControlContext;
50  
51  	protected AbstractPageViewer(Section parent, int style, CmsEditable cmsEditable) {
52  		// read only at UI level
53  		readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
54  
55  		this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE : cmsEditable;
56  		if (this.cmsEditable instanceof Observable)
57  			((Observable) this.cmsEditable).addObserver(this);
58  
59  		if (cmsEditable.canEdit()) {
60  			mouseListener = createMouseListener();
61  			focusListener = createFocusListener();
62  		}
63  		page = findPage(parent);
64  		accessControlContext = AccessController.getContext();
65  	}
66  
67  	/**
68  	 * Can be called to simplify the called to isModelInitialized() and initModel()
69  	 */
70  	protected void initModelIfNeeded(Node node) {
71  		try {
72  			if (!isModelInitialized(node))
73  				if (getCmsEditable().canEdit()) {
74  					initModel(node);
75  					node.getSession().save();
76  				}
77  		} catch (Exception e) {
78  			throw new CmsException("Cannot initialize model", e);
79  		}
80  	}
81  
82  	/** Called if user can edit and model is not initialized */
83  	protected Boolean isModelInitialized(Node node) throws RepositoryException {
84  		return true;
85  	}
86  
87  	/** Called if user can edit and model is not initialized */
88  	protected void initModel(Node node) throws RepositoryException {
89  	}
90  
91  	/** Create (retrieve) the MouseListener to use. */
92  	protected MouseListener createMouseListener() {
93  		return new MouseAdapter() {
94  			private static final long serialVersionUID = 1L;
95  		};
96  	}
97  
98  	/** Create (retrieve) the FocusListener to use. */
99  	protected FocusListener createFocusListener() {
100 		return new FocusListener() {
101 			private static final long serialVersionUID = 1L;
102 
103 			@Override
104 			public void focusLost(FocusEvent event) {
105 			}
106 
107 			@Override
108 			public void focusGained(FocusEvent event) {
109 			}
110 		};
111 	}
112 
113 	protected Composite findPage(Composite composite) {
114 		if (composite instanceof ScrolledPage) {
115 			return (ScrolledPage) composite;
116 		} else {
117 			if (composite.getParent() == null)
118 				return composite;
119 			return findPage(composite.getParent());
120 		}
121 	}
122 
123 	@Override
124 	public void update(Observable o, Object arg) {
125 		if (o == cmsEditable)
126 			editingStateChanged(cmsEditable);
127 	}
128 
129 	/** To be overridden in order to provide the actual refresh */
130 	protected void refresh(Control control) throws RepositoryException {
131 	}
132 
133 	/** To be overridden.Save the edited part. */
134 	protected void save(EditablePart part) throws RepositoryException {
135 	}
136 
137 	/** Prepare the edited part */
138 	protected void prepare(EditablePart part, Object caretPosition) {
139 	}
140 
141 	/** Notified when the editing state changed. Does nothing, to be overridden */
142 	protected void editingStateChanged(CmsEditable cmsEditable) {
143 	}
144 
145 	@Override
146 	public void refresh() {
147 		// TODO check actual context in order to notice a discrepancy
148 		Subject viewerSubject = getViewerSubject();
149 		Subject.doAs(viewerSubject, (PrivilegedAction<Void>) () -> {
150 			try {
151 				if (cmsEditable.canEdit() && !readOnly)
152 					mouseListener = createMouseListener();
153 				else
154 					mouseListener = null;
155 				refresh(getControl());
156 				layout(getControl());
157 			} catch (RepositoryException e) {
158 				throw new CmsException("Cannot refresh", e);
159 			}
160 			return null;
161 		});
162 	}
163 
164 	@Override
165 	public void setSelection(ISelection selection, boolean reveal) {
166 		this.selection = selection;
167 	}
168 
169 	protected void updateContent(EditablePart part) throws RepositoryException {
170 	}
171 
172 	// LOW LEVEL EDITION
173 	protected void edit(EditablePart part, Object caretPosition) {
174 		try {
175 			if (edited == part)
176 				return;
177 
178 			if (edited != null && edited != part) {
179 				EditablePart previouslyEdited = edited;
180 				try {
181 					stopEditing(true);
182 				} catch (Exception e) {
183 					notifyEditionException(e);
184 					edit(previouslyEdited, caretPosition);
185 					return;
186 				}
187 			}
188 
189 			part.startEditing();
190 			updateContent(part);
191 			prepare(part, caretPosition);
192 			edited = part;
193 			layout(part.getControl());
194 		} catch (RepositoryException e) {
195 			throw new CmsException("Cannot edit " + part, e);
196 		}
197 	}
198 
199 	private void stopEditing(Boolean save) throws RepositoryException {
200 		if (edited instanceof Widget && ((Widget) edited).isDisposed()) {
201 			edited = null;
202 			return;
203 		}
204 
205 		assert edited != null;
206 		if (edited == null) {
207 			if (log.isTraceEnabled())
208 				log.warn("Told to stop editing while not editing anything");
209 			return;
210 		}
211 
212 		if (save)
213 			save(edited);
214 
215 		edited.stopEditing();
216 		updateContent(edited);
217 		layout(((EditablePart) edited).getControl());
218 		edited = null;
219 	}
220 
221 	// METHODS AVAILABLE TO EXTENDING CLASSES
222 	protected void saveEdit() {
223 		try {
224 			if (edited != null)
225 				stopEditing(true);
226 		} catch (RepositoryException e) {
227 			throw new CmsException("Cannot stop editing", e);
228 		}
229 	}
230 
231 	protected void cancelEdit() {
232 		try {
233 			if (edited != null)
234 				stopEditing(false);
235 		} catch (RepositoryException e) {
236 			throw new CmsException("Cannot cancel editing", e);
237 		}
238 	}
239 
240 	/** Layout this controls from the related base page. */
241 	public void layout(Control... controls) {
242 		page.layout(controls);
243 	}
244 
245 	/**
246 	 * Find the first {@link EditablePart} in the parents hierarchy of this control
247 	 */
248 	protected EditablePart findDataParent(Control parent) {
249 		if (parent instanceof EditablePart) {
250 			return (EditablePart) parent;
251 		}
252 		if (parent.getParent() != null)
253 			return findDataParent(parent.getParent());
254 		else
255 			throw new CmsException("No data parent found");
256 	}
257 
258 	// UTILITIES
259 	/** Check whether the edited part is in a proper state */
260 	protected void checkEdited() {
261 		if (edited == null || (edited instanceof Widget) && ((Widget) edited).isDisposed())
262 			throw new CmsException("Edited should not be null or disposed at this stage");
263 	}
264 
265 	/** Persist all changes. */
266 	protected void persistChanges(Session session) throws RepositoryException {
267 		session.save();
268 		session.refresh(false);
269 		// TODO notify that changes have been persisted
270 	}
271 
272 	/** Convenience method using a Node in order to save the underlying session. */
273 	protected void persistChanges(Node anyNode) throws RepositoryException {
274 		persistChanges(anyNode.getSession());
275 	}
276 
277 	/** Notify edition exception */
278 	protected void notifyEditionException(Throwable e) {
279 		Throwable eToLog = e;
280 		if (e instanceof IllegalArgumentException)
281 			if (e.getCause() instanceof SAXParseException)
282 				eToLog = e.getCause();
283 		log.error(eToLog.getMessage(), eToLog);
284 //		if (log.isTraceEnabled())
285 //			log.trace("Full stack of " + eToLog.getMessage(), e);
286 		// TODO Light error notification popup
287 	}
288 
289 	protected Subject getViewerSubject() {
290 		Subject res = null;
291 		if (accessControlContext != null) {
292 			res = Subject.getSubject(accessControlContext);
293 		}
294 		if (res == null)
295 			throw new CmsException("No subject associated with this viewer");
296 		return res;
297 	}
298 
299 	// GETTERS / SETTERS
300 	public boolean isReadOnly() {
301 		return readOnly;
302 	}
303 
304 	protected EditablePart getEdited() {
305 		return edited;
306 	}
307 
308 	public MouseListener getMouseListener() {
309 		return mouseListener;
310 	}
311 
312 	public FocusListener getFocusListener() {
313 		return focusListener;
314 	}
315 
316 	public CmsEditable getCmsEditable() {
317 		return cmsEditable;
318 	}
319 
320 	@Override
321 	public ISelection getSelection() {
322 		return selection;
323 	}
324 }