1
|
// $Id$
|
2
|
/**
|
3
|
* Copyright (C) 2017 EDIT
|
4
|
* European Distributed Institute of Taxonomy
|
5
|
* http://www.e-taxonomy.eu
|
6
|
*
|
7
|
* The contents of this file are subject to the Mozilla Public License Version 1.1
|
8
|
* See LICENSE.TXT at the top of this package for the full license terms.
|
9
|
*/
|
10
|
package eu.etaxonomy.taxeditor;
|
11
|
|
12
|
import java.io.File;
|
13
|
import java.io.IOException;
|
14
|
import java.net.URL;
|
15
|
import java.net.URLConnection;
|
16
|
import java.util.Collections;
|
17
|
import java.util.Map;
|
18
|
|
19
|
import javax.annotation.PostConstruct;
|
20
|
import javax.inject.Inject;
|
21
|
import javax.inject.Named;
|
22
|
|
23
|
import org.eclipse.core.internal.runtime.PlatformURLPluginConnection;
|
24
|
import org.eclipse.core.runtime.Platform;
|
25
|
import org.eclipse.core.runtime.URIUtil;
|
26
|
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
|
27
|
import org.eclipse.e4.core.contexts.IEclipseContext;
|
28
|
import org.eclipse.e4.core.services.log.Logger;
|
29
|
import org.eclipse.e4.ui.internal.workbench.CommandLineOptionModelProcessor;
|
30
|
import org.eclipse.e4.ui.internal.workbench.E4Workbench;
|
31
|
import org.eclipse.e4.ui.internal.workbench.E4XMIResourceFactory;
|
32
|
import org.eclipse.e4.ui.internal.workbench.ModelAssembler;
|
33
|
import org.eclipse.e4.ui.internal.workbench.URIHelper;
|
34
|
import org.eclipse.e4.ui.model.application.MApplication;
|
35
|
import org.eclipse.e4.ui.model.application.MApplicationElement;
|
36
|
import org.eclipse.e4.ui.model.application.commands.impl.CommandsPackageImpl;
|
37
|
import org.eclipse.e4.ui.model.application.impl.ApplicationPackageImpl;
|
38
|
import org.eclipse.e4.ui.model.application.ui.advanced.impl.AdvancedPackageImpl;
|
39
|
import org.eclipse.e4.ui.model.application.ui.basic.impl.BasicPackageImpl;
|
40
|
import org.eclipse.e4.ui.model.application.ui.impl.UiPackageImpl;
|
41
|
import org.eclipse.e4.ui.model.application.ui.menu.impl.MenuPackageImpl;
|
42
|
import org.eclipse.e4.ui.workbench.IModelResourceHandler;
|
43
|
import org.eclipse.e4.ui.workbench.IWorkbench;
|
44
|
import org.eclipse.emf.common.util.TreeIterator;
|
45
|
import org.eclipse.emf.common.util.URI;
|
46
|
import org.eclipse.emf.ecore.EObject;
|
47
|
import org.eclipse.emf.ecore.resource.Resource;
|
48
|
import org.eclipse.emf.ecore.resource.URIConverter;
|
49
|
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
|
50
|
import org.eclipse.emf.ecore.util.EcoreUtil;
|
51
|
import org.osgi.framework.Bundle;
|
52
|
|
53
|
/**
|
54
|
* This is a copy of
|
55
|
* {@link org.eclipse.e4.ui.internal.workbench.ResourceHandler}. The copy is
|
56
|
* registered in the plugin.xml in order to control the loading and saving of
|
57
|
* the application model. The only current change is the different save location
|
58
|
* (see {@link #getBaseLocation()})
|
59
|
*
|
60
|
* @author pplitzner
|
61
|
* @date 07.07.2017
|
62
|
*
|
63
|
*/
|
64
|
public class ModelResourceHandler implements IModelResourceHandler {
|
65
|
|
66
|
private static final String WORKBENCH_XMI = "workbench.xmi";
|
67
|
private ResourceSetImpl resourceSetImpl;
|
68
|
private Resource resource;
|
69
|
|
70
|
@Inject
|
71
|
private Logger logger;
|
72
|
|
73
|
@Inject
|
74
|
private IEclipseContext context;
|
75
|
|
76
|
@Inject
|
77
|
@Named(E4Workbench.INITIAL_WORKBENCH_MODEL_URI)
|
78
|
private URI applicationDefinitionInstance;
|
79
|
|
80
|
/**
|
81
|
* Dictates whether the model should be stored using EMF or with the merging algorithm.
|
82
|
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=295524
|
83
|
*
|
84
|
*/
|
85
|
final private boolean saveAndRestore;
|
86
|
final private boolean clearPersistedState;
|
87
|
|
88
|
/**
|
89
|
* Constructor.
|
90
|
*
|
91
|
* @param saveAndRestore
|
92
|
* @param clearPersistedState
|
93
|
*/
|
94
|
@Inject
|
95
|
public ModelResourceHandler(@Named(IWorkbench.PERSIST_STATE) boolean saveAndRestore,
|
96
|
@Named(IWorkbench.CLEAR_PERSISTED_STATE) boolean clearPersistedState) {
|
97
|
this.saveAndRestore = saveAndRestore;
|
98
|
this.clearPersistedState = clearPersistedState;
|
99
|
}
|
100
|
|
101
|
@PostConstruct
|
102
|
void init() {
|
103
|
resourceSetImpl = new ResourceSetImpl();
|
104
|
resourceSetImpl.getResourceFactoryRegistry().getExtensionToFactoryMap()
|
105
|
.put(Resource.Factory.Registry.DEFAULT_EXTENSION, new E4XMIResourceFactory());
|
106
|
|
107
|
resourceSetImpl.getPackageRegistry().put(ApplicationPackageImpl.eNS_URI,
|
108
|
ApplicationPackageImpl.eINSTANCE);
|
109
|
resourceSetImpl.getPackageRegistry().put(CommandsPackageImpl.eNS_URI,
|
110
|
CommandsPackageImpl.eINSTANCE);
|
111
|
resourceSetImpl.getPackageRegistry().put(UiPackageImpl.eNS_URI, UiPackageImpl.eINSTANCE);
|
112
|
resourceSetImpl.getPackageRegistry()
|
113
|
.put(MenuPackageImpl.eNS_URI, MenuPackageImpl.eINSTANCE);
|
114
|
resourceSetImpl.getPackageRegistry().put(BasicPackageImpl.eNS_URI,
|
115
|
BasicPackageImpl.eINSTANCE);
|
116
|
resourceSetImpl.getPackageRegistry().put(AdvancedPackageImpl.eNS_URI,
|
117
|
AdvancedPackageImpl.eINSTANCE);
|
118
|
resourceSetImpl
|
119
|
.getPackageRegistry()
|
120
|
.put(org.eclipse.e4.ui.model.application.descriptor.basic.impl.BasicPackageImpl.eNS_URI,
|
121
|
org.eclipse.e4.ui.model.application.descriptor.basic.impl.BasicPackageImpl.eINSTANCE);
|
122
|
|
123
|
}
|
124
|
|
125
|
/**
|
126
|
* @return {@code true} if the current application model has top-level windows.
|
127
|
*/
|
128
|
public boolean hasTopLevelWindows() {
|
129
|
return hasTopLevelWindows(resource);
|
130
|
}
|
131
|
|
132
|
/**
|
133
|
* @return {@code true} if the specified application model has top-level windows.
|
134
|
*/
|
135
|
private boolean hasTopLevelWindows(Resource applicationResource) {
|
136
|
if (applicationResource == null || applicationResource.getContents() == null) {
|
137
|
// If the application resource doesn't exist or has no contents, then it has no
|
138
|
// top-level windows (and we are in an error state).
|
139
|
return false;
|
140
|
}
|
141
|
MApplication application = (MApplication) applicationResource.getContents().get(0);
|
142
|
return !application.getChildren().isEmpty();
|
143
|
}
|
144
|
|
145
|
@Override
|
146
|
public Resource loadMostRecentModel() {
|
147
|
File workbenchData = null;
|
148
|
URI restoreLocation = null;
|
149
|
|
150
|
if (saveAndRestore) {
|
151
|
workbenchData = getWorkbenchSaveLocation();
|
152
|
restoreLocation = URI.createFileURI(workbenchData.getAbsolutePath());
|
153
|
}
|
154
|
|
155
|
if (clearPersistedState && workbenchData != null && workbenchData.exists()) {
|
156
|
workbenchData.delete();
|
157
|
}
|
158
|
|
159
|
// last stored time-stamp
|
160
|
long restoreLastModified = restoreLocation == null ? 0L : new File(
|
161
|
restoreLocation.toFileString()).lastModified();
|
162
|
|
163
|
// See bug 380663, bug 381219
|
164
|
// long lastApplicationModification = getLastApplicationModification();
|
165
|
// boolean restore = restoreLastModified > lastApplicationModification;
|
166
|
boolean restore = restoreLastModified > 0;
|
167
|
boolean initialModel;
|
168
|
|
169
|
resource = null;
|
170
|
if (restore && saveAndRestore) {
|
171
|
resource = loadResource(restoreLocation);
|
172
|
// If the saved model does not have any top-level windows, Eclipse will exit
|
173
|
// immediately, so throw out the persisted state and reinitialize with the defaults.
|
174
|
if (!hasTopLevelWindows(resource)) {
|
175
|
if (logger != null) {
|
176
|
logger.error(new Exception(), // log a stack trace to help debug the corruption
|
177
|
"The persisted workbench has no top-level windows, so reinitializing with defaults."); //$NON-NLS-1$
|
178
|
}
|
179
|
resource = null;
|
180
|
}
|
181
|
}
|
182
|
if (resource == null) {
|
183
|
Resource applicationResource = loadResource(applicationDefinitionInstance);
|
184
|
MApplication theApp = (MApplication) applicationResource.getContents().get(0);
|
185
|
resource = createResourceWithApp(theApp);
|
186
|
context.set(E4Workbench.NO_SAVED_MODEL_FOUND, Boolean.TRUE);
|
187
|
initialModel = true;
|
188
|
} else {
|
189
|
initialModel = false;
|
190
|
}
|
191
|
|
192
|
// Add model items described in the model extension point
|
193
|
// This has to be done before commands are put into the context
|
194
|
MApplication appElement = (MApplication) resource.getContents().get(0);
|
195
|
|
196
|
this.context.set(MApplication.class, appElement);
|
197
|
ModelAssembler contribProcessor = ContextInjectionFactory.make(ModelAssembler.class,
|
198
|
context);
|
199
|
contribProcessor.processModel(initialModel);
|
200
|
|
201
|
if (!hasTopLevelWindows(resource) && logger != null) {
|
202
|
logger.error(new Exception(), // log a stack trace to help debug the
|
203
|
// corruption
|
204
|
"Initializing from the application definition instance yields no top-level windows! " //$NON-NLS-1$
|
205
|
+ "Continuing execution, but the missing windows may cause other initialization failures."); //$NON-NLS-1$
|
206
|
}
|
207
|
|
208
|
if (!clearPersistedState) {
|
209
|
CommandLineOptionModelProcessor processor = ContextInjectionFactory.make(
|
210
|
CommandLineOptionModelProcessor.class, context);
|
211
|
processor.process();
|
212
|
}
|
213
|
|
214
|
return resource;
|
215
|
}
|
216
|
|
217
|
@Override
|
218
|
public void save() throws IOException {
|
219
|
if (saveAndRestore) {
|
220
|
resource.save(null);
|
221
|
}
|
222
|
}
|
223
|
|
224
|
/**
|
225
|
* Creates a resource with an app Model, used for saving copies of the main app model.
|
226
|
*
|
227
|
* @param theApp
|
228
|
* the application model to add to the resource
|
229
|
* @return a resource with a proper save path with the model as contents
|
230
|
*/
|
231
|
@Override
|
232
|
public Resource createResourceWithApp(MApplication theApp) {
|
233
|
Resource res = createResource();
|
234
|
res.getContents().add((EObject) theApp);
|
235
|
return res;
|
236
|
}
|
237
|
|
238
|
private Resource createResource() {
|
239
|
if (saveAndRestore) {
|
240
|
URI saveLocation = URI.createFileURI(getWorkbenchSaveLocation().getAbsolutePath());
|
241
|
return resourceSetImpl.createResource(saveLocation);
|
242
|
}
|
243
|
return resourceSetImpl.createResource(URI.createURI(WORKBENCH_XMI)); //$NON-NLS-1$
|
244
|
}
|
245
|
|
246
|
private File getWorkbenchSaveLocation() {
|
247
|
File workbenchData = new File(getBaseLocation(), WORKBENCH_XMI); //$NON-NLS-1$
|
248
|
return workbenchData;
|
249
|
}
|
250
|
|
251
|
private File getBaseLocation() {
|
252
|
File baseLocation;
|
253
|
try {
|
254
|
baseLocation = new File(URIUtil.toURI(Platform.getInstallLocation().getURL()));
|
255
|
} catch (Exception e) {
|
256
|
throw new RuntimeException(e);
|
257
|
}
|
258
|
baseLocation = new File(baseLocation, "configuration"); //$NON-NLS-1$
|
259
|
return baseLocation;
|
260
|
}
|
261
|
|
262
|
// Ensures that even models with error are loaded!
|
263
|
private Resource loadResource(URI uri) {
|
264
|
Resource resource;
|
265
|
try {
|
266
|
resource = getResource(uri);
|
267
|
} catch (Exception e) {
|
268
|
// TODO We could use diagnostics for better analyzing the error
|
269
|
logger.error(e, "Unable to load resource " + uri.toString()); //$NON-NLS-1$
|
270
|
return null;
|
271
|
}
|
272
|
|
273
|
// TODO once we switch from deltas, we only need this once on the default model?
|
274
|
String contributorURI = URIHelper.EMFtoPlatform(uri);
|
275
|
if (contributorURI != null) {
|
276
|
TreeIterator<EObject> it = EcoreUtil.getAllContents(resource.getContents());
|
277
|
while (it.hasNext()) {
|
278
|
EObject o = it.next();
|
279
|
if (o instanceof MApplicationElement) {
|
280
|
((MApplicationElement) o).setContributorURI(contributorURI);
|
281
|
}
|
282
|
}
|
283
|
}
|
284
|
return resource;
|
285
|
}
|
286
|
|
287
|
private Resource getResource(URI uri) throws Exception {
|
288
|
Resource resource;
|
289
|
if (saveAndRestore) {
|
290
|
resource = resourceSetImpl.getResource(uri, true);
|
291
|
} else {
|
292
|
// Workaround for java.lang.IllegalStateException: No instance data can be specified
|
293
|
// thrown by org.eclipse.core.internal.runtime.DataArea.assertLocationInitialized
|
294
|
// The DataArea.assertLocationInitialized is called by ResourceSetImpl.getResource(URI,
|
295
|
// boolean)
|
296
|
resource = resourceSetImpl.createResource(uri);
|
297
|
resource.load(new URL(uri.toString()).openStream(), resourceSetImpl.getLoadOptions());
|
298
|
}
|
299
|
|
300
|
return resource;
|
301
|
}
|
302
|
|
303
|
protected long getLastApplicationModification() {
|
304
|
long appLastModified = 0L;
|
305
|
ResourceSetImpl resourceSetImpl = new ResourceSetImpl();
|
306
|
|
307
|
Map<String, ?> attributes = resourceSetImpl.getURIConverter().getAttributes(
|
308
|
applicationDefinitionInstance,
|
309
|
Collections.singletonMap(URIConverter.OPTION_REQUESTED_ATTRIBUTES,
|
310
|
Collections.singleton(URIConverter.ATTRIBUTE_TIME_STAMP)));
|
311
|
|
312
|
Object timestamp = attributes.get(URIConverter.ATTRIBUTE_TIME_STAMP);
|
313
|
if (timestamp instanceof Long) {
|
314
|
appLastModified = ((Long) timestamp).longValue();
|
315
|
} else if (applicationDefinitionInstance.isPlatformPlugin()) {
|
316
|
try {
|
317
|
java.net.URL url = new java.net.URL(applicationDefinitionInstance.toString());
|
318
|
// can't just use 'url.openConnection()' as it usually returns a
|
319
|
// PlatformURLPluginConnection which doesn't expose the
|
320
|
// last-modification time. So we try to resolve the file through
|
321
|
// the bundle to obtain a BundleURLConnection instead.
|
322
|
Object[] obj = PlatformURLPluginConnection.parse(url.getFile().trim(), url);
|
323
|
Bundle b = (Bundle) obj[0];
|
324
|
// first try to resolve as an bundle file entry, then as a resource using
|
325
|
// the bundle's classpath
|
326
|
java.net.URL resolved = b.getEntry((String) obj[1]);
|
327
|
if (resolved == null) {
|
328
|
resolved = b.getResource((String) obj[1]);
|
329
|
}
|
330
|
if (resolved != null) {
|
331
|
URLConnection openConnection = resolved.openConnection();
|
332
|
appLastModified = openConnection.getLastModified();
|
333
|
}
|
334
|
} catch (Exception e) {
|
335
|
// ignore
|
336
|
}
|
337
|
}
|
338
|
|
339
|
return appLastModified;
|
340
|
}
|
341
|
|
342
|
|
343
|
|
344
|
}
|