Project

General

Profile

Download (13.6 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.FileLocator;
25
import org.eclipse.core.runtime.Platform;
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
 * @author pplitzner
55
 * @date 07.07.2017
56
 *
57
 */
58
public class ModelResourceHandler implements IModelResourceHandler {
59

    
60
    private static final String WORKBENCH_XMI = "workbench.xmi";
61
    private ResourceSetImpl resourceSetImpl;
62
    private Resource resource;
63

    
64
    @Inject
65
    private Logger logger;
66

    
67
    @Inject
68
    private IEclipseContext context;
69

    
70
    @Inject
71
    @Named(E4Workbench.INITIAL_WORKBENCH_MODEL_URI)
72
    private URI applicationDefinitionInstance;
73

    
74
    /**
75
     * Dictates whether the model should be stored using EMF or with the merging algorithm.
76
     * https://bugs.eclipse.org/bugs/show_bug.cgi?id=295524
77
     *
78
     */
79
    final private boolean saveAndRestore;
80
    final private boolean clearPersistedState;
81

    
82
    /**
83
     * Constructor.
84
     *
85
     * @param saveAndRestore
86
     * @param clearPersistedState
87
     */
88
    @Inject
89
    public ModelResourceHandler(@Named(IWorkbench.PERSIST_STATE) boolean saveAndRestore,
90
            @Named(IWorkbench.CLEAR_PERSISTED_STATE) boolean clearPersistedState) {
91
        this.saveAndRestore = saveAndRestore;
92
        this.clearPersistedState = clearPersistedState;
93
    }
94

    
95
    @PostConstruct
96
    void init() {
97
        resourceSetImpl = new ResourceSetImpl();
98
        resourceSetImpl.getResourceFactoryRegistry().getExtensionToFactoryMap()
99
                .put(Resource.Factory.Registry.DEFAULT_EXTENSION, new E4XMIResourceFactory());
100

    
101
        resourceSetImpl.getPackageRegistry().put(ApplicationPackageImpl.eNS_URI,
102
                ApplicationPackageImpl.eINSTANCE);
103
        resourceSetImpl.getPackageRegistry().put(CommandsPackageImpl.eNS_URI,
104
                CommandsPackageImpl.eINSTANCE);
105
        resourceSetImpl.getPackageRegistry().put(UiPackageImpl.eNS_URI, UiPackageImpl.eINSTANCE);
106
        resourceSetImpl.getPackageRegistry()
107
                .put(MenuPackageImpl.eNS_URI, MenuPackageImpl.eINSTANCE);
108
        resourceSetImpl.getPackageRegistry().put(BasicPackageImpl.eNS_URI,
109
                BasicPackageImpl.eINSTANCE);
110
        resourceSetImpl.getPackageRegistry().put(AdvancedPackageImpl.eNS_URI,
111
                AdvancedPackageImpl.eINSTANCE);
112
        resourceSetImpl
113
                .getPackageRegistry()
114
                .put(org.eclipse.e4.ui.model.application.descriptor.basic.impl.BasicPackageImpl.eNS_URI,
115
                        org.eclipse.e4.ui.model.application.descriptor.basic.impl.BasicPackageImpl.eINSTANCE);
116

    
117
    }
118

    
119
    /**
120
     * @return {@code true} if the current application model has top-level windows.
121
     */
122
    public boolean hasTopLevelWindows() {
123
        return hasTopLevelWindows(resource);
124
    }
125

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

    
139
    @Override
140
    public Resource loadMostRecentModel() {
141
        File workbenchData = null;
142
        URI restoreLocation = null;
143

    
144
        if (saveAndRestore) {
145
            workbenchData = getWorkbenchSaveLocation();
146
            restoreLocation = URI.createFileURI(workbenchData.getAbsolutePath());
147
        }
148

    
149
        if (clearPersistedState && workbenchData != null && workbenchData.exists()) {
150
            workbenchData.delete();
151
        }
152

    
153
        // last stored time-stamp
154
        long restoreLastModified = restoreLocation == null ? 0L : new File(
155
                restoreLocation.toFileString()).lastModified();
156

    
157
        // See bug 380663, bug 381219
158
        // long lastApplicationModification = getLastApplicationModification();
159
        // boolean restore = restoreLastModified > lastApplicationModification;
160
        boolean restore = restoreLastModified > 0;
161
        boolean initialModel;
162

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

    
186
        // Add model items described in the model extension point
187
        // This has to be done before commands are put into the context
188
        MApplication appElement = (MApplication) resource.getContents().get(0);
189

    
190
        this.context.set(MApplication.class, appElement);
191
        ModelAssembler contribProcessor = ContextInjectionFactory.make(ModelAssembler.class,
192
                context);
193
        contribProcessor.processModel(initialModel);
194

    
195
        if (!hasTopLevelWindows(resource) && logger != null) {
196
            logger.error(new Exception(), // log a stack trace to help debug the
197
                                            // corruption
198
                    "Initializing from the application definition instance yields no top-level windows! " //$NON-NLS-1$
199
                            + "Continuing execution, but the missing windows may cause other initialization failures."); //$NON-NLS-1$
200
        }
201

    
202
        if (!clearPersistedState) {
203
            CommandLineOptionModelProcessor processor = ContextInjectionFactory.make(
204
                    CommandLineOptionModelProcessor.class, context);
205
            processor.process();
206
        }
207

    
208
        return resource;
209
    }
210

    
211
    @Override
212
    public void save() throws IOException {
213
        if (saveAndRestore) {
214
            resource.save(null);
215
        }
216
    }
217

    
218
    /**
219
     * Creates a resource with an app Model, used for saving copies of the main app model.
220
     *
221
     * @param theApp
222
     *            the application model to add to the resource
223
     * @return a resource with a proper save path with the model as contents
224
     */
225
    @Override
226
    public Resource createResourceWithApp(MApplication theApp) {
227
        Resource res = createResource();
228
        res.getContents().add((EObject) theApp);
229
        return res;
230
    }
231

    
232
    private Resource createResource() {
233
        if (saveAndRestore) {
234
            URI saveLocation = URI.createFileURI(getWorkbenchSaveLocation().getAbsolutePath());
235
            return resourceSetImpl.createResource(saveLocation);
236
        }
237
        return resourceSetImpl.createResource(URI.createURI(WORKBENCH_XMI)); //$NON-NLS-1$
238
    }
239

    
240
    private File getWorkbenchSaveLocation() {
241
        File workbenchData = new File(getBaseLocation(), WORKBENCH_XMI); //$NON-NLS-1$
242
        return workbenchData;
243
    }
244

    
245
    private File getBaseLocation() {
246
        File baseLocation;
247
        try {
248
            baseLocation = FileLocator.getBundleFile(Platform.getBundle(TaxonomicEditorPlugin.PLUGIN_ID));
249
        } catch (IOException e) {
250
            throw new RuntimeException(e);
251
        }
252
//        baseLocation = new File(baseLocation, ".metadata"); //$NON-NLS-1$
253
//        baseLocation = new File(baseLocation, ".plugins"); //$NON-NLS-1$
254
//        baseLocation = new File(baseLocation, "org.eclipse.e4.workbench"); //$NON-NLS-1$
255
        return baseLocation;
256
    }
257

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

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

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

    
296
        return resource;
297
    }
298

    
299
    protected long getLastApplicationModification() {
300
        long appLastModified = 0L;
301
        ResourceSetImpl resourceSetImpl = new ResourceSetImpl();
302

    
303
        Map<String, ?> attributes = resourceSetImpl.getURIConverter().getAttributes(
304
                applicationDefinitionInstance,
305
                Collections.singletonMap(URIConverter.OPTION_REQUESTED_ATTRIBUTES,
306
                        Collections.singleton(URIConverter.ATTRIBUTE_TIME_STAMP)));
307

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

    
335
        return appLastModified;
336
    }
337

    
338

    
339

    
340
}
(6-6/8)