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. 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
}
(6-6/8)