Project

General

Profile

Download (13.7 KB) Statistics
| Branch: | Tag: | Revision:
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.
58
 *
59
 * @author pplitzner
60
 * @date 07.07.2017
61
 *
62
 */
63
public class ModelResourceHandler implements IModelResourceHandler {
64

    
65
    private static final String WORKBENCH_XMI = "workbench.xmi";
66
    private ResourceSetImpl resourceSetImpl;
67
    private Resource resource;
68

    
69
    @Inject
70
    private Logger logger;
71

    
72
    @Inject
73
    private IEclipseContext context;
74

    
75
    @Inject
76
    @Named(E4Workbench.INITIAL_WORKBENCH_MODEL_URI)
77
    private URI applicationDefinitionInstance;
78

    
79
    /**
80
     * Dictates whether the model should be stored using EMF or with the merging algorithm.
81
     * https://bugs.eclipse.org/bugs/show_bug.cgi?id=295524
82
     *
83
     */
84
    final private boolean saveAndRestore;
85
    final private boolean clearPersistedState;
86

    
87
    /**
88
     * Constructor.
89
     *
90
     * @param saveAndRestore
91
     * @param clearPersistedState
92
     */
93
    @Inject
94
    public ModelResourceHandler(@Named(IWorkbench.PERSIST_STATE) boolean saveAndRestore,
95
            @Named(IWorkbench.CLEAR_PERSISTED_STATE) boolean clearPersistedState) {
96
        //FIXME E4 we are currently not saving the workbench.xmi because of problems with missing context menus
97
        // see #6954
98
        this.saveAndRestore = false;
99
        this.clearPersistedState = clearPersistedState;
100
    }
101

    
102
    @PostConstruct
103
    void init() {
104
        resourceSetImpl = new ResourceSetImpl();
105
        resourceSetImpl.getResourceFactoryRegistry().getExtensionToFactoryMap()
106
                .put(Resource.Factory.Registry.DEFAULT_EXTENSION, new E4XMIResourceFactory());
107

    
108
        resourceSetImpl.getPackageRegistry().put(ApplicationPackageImpl.eNS_URI,
109
                ApplicationPackageImpl.eINSTANCE);
110
        resourceSetImpl.getPackageRegistry().put(CommandsPackageImpl.eNS_URI,
111
                CommandsPackageImpl.eINSTANCE);
112
        resourceSetImpl.getPackageRegistry().put(UiPackageImpl.eNS_URI, UiPackageImpl.eINSTANCE);
113
        resourceSetImpl.getPackageRegistry()
114
                .put(MenuPackageImpl.eNS_URI, MenuPackageImpl.eINSTANCE);
115
        resourceSetImpl.getPackageRegistry().put(BasicPackageImpl.eNS_URI,
116
                BasicPackageImpl.eINSTANCE);
117
        resourceSetImpl.getPackageRegistry().put(AdvancedPackageImpl.eNS_URI,
118
                AdvancedPackageImpl.eINSTANCE);
119
        resourceSetImpl
120
                .getPackageRegistry()
121
                .put(org.eclipse.e4.ui.model.application.descriptor.basic.impl.BasicPackageImpl.eNS_URI,
122
                        org.eclipse.e4.ui.model.application.descriptor.basic.impl.BasicPackageImpl.eINSTANCE);
123

    
124
    }
125

    
126
    /**
127
     * @return {@code true} if the current application model has top-level windows.
128
     */
129
    public boolean hasTopLevelWindows() {
130
        return hasTopLevelWindows(resource);
131
    }
132

    
133
    /**
134
     * @return {@code true} if the specified application model has top-level windows.
135
     */
136
    private boolean hasTopLevelWindows(Resource applicationResource) {
137
        if (applicationResource == null || applicationResource.getContents() == null) {
138
            // If the application resource doesn't exist or has no contents, then it has no
139
            // top-level windows (and we are in an error state).
140
            return false;
141
        }
142
        MApplication application = (MApplication) applicationResource.getContents().get(0);
143
        return !application.getChildren().isEmpty();
144
    }
145

    
146
    @Override
147
    public Resource loadMostRecentModel() {
148
        File workbenchData = null;
149
        URI restoreLocation = null;
150

    
151
        if (saveAndRestore) {
152
            workbenchData = getWorkbenchSaveLocation();
153
            restoreLocation = URI.createFileURI(workbenchData.getAbsolutePath());
154
        }
155

    
156
        if (clearPersistedState && workbenchData != null && workbenchData.exists()) {
157
            workbenchData.delete();
158
        }
159

    
160
        // last stored time-stamp
161
        long restoreLastModified = restoreLocation == null ? 0L : new File(
162
                restoreLocation.toFileString()).lastModified();
163

    
164
        // See bug 380663, bug 381219
165
        // long lastApplicationModification = getLastApplicationModification();
166
        // boolean restore = restoreLastModified > lastApplicationModification;
167
        boolean restore = restoreLastModified > 0;
168
        boolean initialModel;
169

    
170
        resource = null;
171
        if (restore && saveAndRestore) {
172
            resource = loadResource(restoreLocation);
173
            // If the saved model does not have any top-level windows, Eclipse will exit
174
            // immediately, so throw out the persisted state and reinitialize with the defaults.
175
            if (!hasTopLevelWindows(resource)) {
176
                if (logger != null) {
177
                    logger.error(new Exception(), // log a stack trace to help debug the corruption
178
                            "The persisted workbench has no top-level windows, so reinitializing with defaults."); //$NON-NLS-1$
179
                }
180
                resource = null;
181
            }
182
        }
183
        if (resource == null) {
184
            Resource applicationResource = loadResource(applicationDefinitionInstance);
185
            MApplication theApp = (MApplication) applicationResource.getContents().get(0);
186
            resource = createResourceWithApp(theApp);
187
            context.set(E4Workbench.NO_SAVED_MODEL_FOUND, Boolean.TRUE);
188
            initialModel = true;
189
        } else {
190
            initialModel = false;
191
        }
192

    
193
        // Add model items described in the model extension point
194
        // This has to be done before commands are put into the context
195
        MApplication appElement = (MApplication) resource.getContents().get(0);
196

    
197
        this.context.set(MApplication.class, appElement);
198
        ModelAssembler contribProcessor = ContextInjectionFactory.make(ModelAssembler.class,
199
                context);
200
        contribProcessor.processModel(initialModel);
201

    
202
        if (!hasTopLevelWindows(resource) && logger != null) {
203
            logger.error(new Exception(), // log a stack trace to help debug the
204
                                            // corruption
205
                    "Initializing from the application definition instance yields no top-level windows! " //$NON-NLS-1$
206
                            + "Continuing execution, but the missing windows may cause other initialization failures."); //$NON-NLS-1$
207
        }
208

    
209
        if (!clearPersistedState) {
210
            CommandLineOptionModelProcessor processor = ContextInjectionFactory.make(
211
                    CommandLineOptionModelProcessor.class, context);
212
            processor.process();
213
        }
214

    
215
        return resource;
216
    }
217

    
218
    @Override
219
    public void save() throws IOException {
220
        if (saveAndRestore) {
221
            resource.save(null);
222
        }
223
    }
224

    
225
    /**
226
     * Creates a resource with an app Model, used for saving copies of the main app model.
227
     *
228
     * @param theApp
229
     *            the application model to add to the resource
230
     * @return a resource with a proper save path with the model as contents
231
     */
232
    @Override
233
    public Resource createResourceWithApp(MApplication theApp) {
234
        Resource res = createResource();
235
        res.getContents().add((EObject) theApp);
236
        return res;
237
    }
238

    
239
    private Resource createResource() {
240
        if (saveAndRestore) {
241
            URI saveLocation = URI.createFileURI(getWorkbenchSaveLocation().getAbsolutePath());
242
            return resourceSetImpl.createResource(saveLocation);
243
        }
244
        return resourceSetImpl.createResource(URI.createURI(WORKBENCH_XMI)); //$NON-NLS-1$
245
    }
246

    
247
    private File getWorkbenchSaveLocation() {
248
        File workbenchData = new File(getBaseLocation(), WORKBENCH_XMI); //$NON-NLS-1$
249
        return workbenchData;
250
    }
251

    
252
    private File getBaseLocation() {
253
        File baseLocation;
254
        try {
255
            baseLocation = new File(URIUtil.toURI(Platform.getInstallLocation().getURL()));
256
        } catch (Exception e) {
257
            throw new RuntimeException(e);
258
        }
259
        baseLocation = new File(baseLocation, "configuration"); //$NON-NLS-1$
260
        return baseLocation;
261
    }
262

    
263
    // Ensures that even models with error are loaded!
264
    private Resource loadResource(URI uri) {
265
        Resource resource;
266
        try {
267
            resource = getResource(uri);
268
        } catch (Exception e) {
269
            // TODO We could use diagnostics for better analyzing the error
270
            logger.error(e, "Unable to load resource " + uri.toString()); //$NON-NLS-1$
271
            return null;
272
        }
273

    
274
        // TODO once we switch from deltas, we only need this once on the default model?
275
        String contributorURI = URIHelper.EMFtoPlatform(uri);
276
        if (contributorURI != null) {
277
            TreeIterator<EObject> it = EcoreUtil.getAllContents(resource.getContents());
278
            while (it.hasNext()) {
279
                EObject o = it.next();
280
                if (o instanceof MApplicationElement) {
281
                    ((MApplicationElement) o).setContributorURI(contributorURI);
282
                }
283
            }
284
        }
285
        return resource;
286
    }
287

    
288
    private Resource getResource(URI uri) throws Exception {
289
        Resource resource;
290
        if (saveAndRestore) {
291
            resource = resourceSetImpl.getResource(uri, true);
292
        } else {
293
            // Workaround for java.lang.IllegalStateException: No instance data can be specified
294
            // thrown by org.eclipse.core.internal.runtime.DataArea.assertLocationInitialized
295
            // The DataArea.assertLocationInitialized is called by ResourceSetImpl.getResource(URI,
296
            // boolean)
297
            resource = resourceSetImpl.createResource(uri);
298
            resource.load(new URL(uri.toString()).openStream(), resourceSetImpl.getLoadOptions());
299
        }
300

    
301
        return resource;
302
    }
303

    
304
    protected long getLastApplicationModification() {
305
        long appLastModified = 0L;
306
        ResourceSetImpl resourceSetImpl = new ResourceSetImpl();
307

    
308
        Map<String, ?> attributes = resourceSetImpl.getURIConverter().getAttributes(
309
                applicationDefinitionInstance,
310
                Collections.singletonMap(URIConverter.OPTION_REQUESTED_ATTRIBUTES,
311
                        Collections.singleton(URIConverter.ATTRIBUTE_TIME_STAMP)));
312

    
313
        Object timestamp = attributes.get(URIConverter.ATTRIBUTE_TIME_STAMP);
314
        if (timestamp instanceof Long) {
315
            appLastModified = ((Long) timestamp).longValue();
316
        } else if (applicationDefinitionInstance.isPlatformPlugin()) {
317
            try {
318
                java.net.URL url = new java.net.URL(applicationDefinitionInstance.toString());
319
                // can't just use 'url.openConnection()' as it usually returns a
320
                // PlatformURLPluginConnection which doesn't expose the
321
                // last-modification time. So we try to resolve the file through
322
                // the bundle to obtain a BundleURLConnection instead.
323
                Object[] obj = PlatformURLPluginConnection.parse(url.getFile().trim(), url);
324
                Bundle b = (Bundle) obj[0];
325
                // first try to resolve as an bundle file entry, then as a resource using
326
                // the bundle's classpath
327
                java.net.URL resolved = b.getEntry((String) obj[1]);
328
                if (resolved == null) {
329
                    resolved = b.getResource((String) obj[1]);
330
                }
331
                if (resolved != null) {
332
                    URLConnection openConnection = resolved.openConnection();
333
                    appLastModified = openConnection.getLastModified();
334
                }
335
            } catch (Exception e) {
336
                // ignore
337
            }
338
        }
339

    
340
        return appLastModified;
341
    }
342

    
343

    
344

    
345
}
(6-6/8)