]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.fileimport/src/org/simantics/fileimport/FileImportService.java
Updated file importer interface and fixed Spreadsheet import
[simantics/platform.git] / bundles / org.simantics.fileimport / src / org / simantics / fileimport / FileImportService.java
1 package org.simantics.fileimport;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6 import java.nio.file.Files;
7 import java.nio.file.Path;
8 import java.nio.file.Paths;
9 import java.util.ArrayList;
10 import java.util.Collections;
11 import java.util.HashMap;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Optional;
15 import java.util.Properties;
16 import java.util.Set;
17 import java.util.function.Consumer;
18 import java.util.stream.Collectors;
19
20 import org.osgi.framework.InvalidSyntaxException;
21 import org.osgi.framework.ServiceReference;
22 import org.simantics.databoard.util.Base64;
23 import org.simantics.db.Resource;
24 import org.simantics.fileimport.dropins.FileImportDropins;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 /**
29  * Utility class for Simantics File import functions
30  * 
31  * @author Jani Simomaa
32  *
33  */
34 public class FileImportService {
35
36     private static final Logger LOGGER = LoggerFactory.getLogger(FileImportService.class);
37     
38     private FileImportService() {}
39     
40     public static final String DB_FILE = ".simanticsdb";
41
42     private static List<IGenericFileImport> getFileImportServices() {
43         ServiceReference<?>[] serviceReferences = new ServiceReference<?>[0];
44         try {
45             serviceReferences = Activator.getContext().getAllServiceReferences(IGenericFileImport.class.getName(),
46                     null);
47         } catch (InvalidSyntaxException e) {
48             LOGGER.error("Could not get service references for IGenericFileImport!", e);
49         }
50         if (serviceReferences.length == 0)
51             return Collections.emptyList();
52
53         List<IGenericFileImport> services = new ArrayList<>(serviceReferences.length);
54         for (ServiceReference<?> reference : serviceReferences) {
55             IGenericFileImport service = (IGenericFileImport) Activator.getContext().getService(reference);
56             services.add(service);
57         }
58         return services;
59     }
60
61     /**
62      * Lists all supported file extensions which have a registered service for handling the import
63      * 
64      * @return Map containing the extension and the description of the extension in that order
65      */
66     public static Map<String, String> supportedExtensionsWithFilters() {
67         List<IGenericFileImport> services = getFileImportServices();
68         Map<String, String> extensionsWithFilters = new HashMap<>();
69         for (IGenericFileImport service : services)
70             extensionsWithFilters.putAll(service.allowedExtensionsWithFilters());
71
72         return extensionsWithFilters;
73     }
74     
75     private static class ConsumerHolder implements Consumer<Throwable> {
76
77         private Throwable throwable;
78         
79         @Override
80         public void accept(Throwable t) {
81             throwable = t;
82         }
83         
84         public Throwable getThrowable() {
85             return throwable;
86         }
87         
88     }
89     
90     public static String performFileImport(String base64, String name) throws Throwable {
91         byte[] bytes = Base64.decode(base64);
92         Path file = Activator.getModelsFolder().resolve(name);
93         Files.write(file, bytes);
94         
95         ConsumerHolder holder = new ConsumerHolder();
96         String result = performFileImport(file, Optional.empty(), Optional.of(holder));
97         if (holder.getThrowable() != null)
98             throw holder.getThrowable();
99         return result;
100     }
101     
102     /**
103      * Method that performs the import of the given file. This method is called when e.g. {@link FileImportDropins} watcher detects {@link java.nio.file.StandardWatchEventKinds.ENTRY_CREATE} operation
104      * 
105      * @param file Path file to be imported
106      * @param possibleSelection - the selected resource (if exists)
107      * @param callback Optional callback which can be used to catch Throwables thrown in the import process
108      */
109     public static String performFileImport(Path file, Optional<Resource> possibleSelection, Optional<Consumer<Throwable>> callback) {
110         if (file.getFileName().toString().equals(DB_FILE)) {
111             return null;
112         }
113         String result = "Import failed";
114         IGenericFileImport service = findServiceForFileExtension(file);
115         
116         if (service != null) {
117             try {
118                 Optional<String> resource;
119                 if (possibleSelection.isPresent() && service.defaultParentResource() == null) {
120                         resource = Optional.of(Long.toString(service.perform(possibleSelection.get(), file).get().getResourceId()));
121                 }
122                 else {
123                         resource = service.performWithDefaultParent(file);
124                 }
125                 saveResourceForPath(file, resource);
126                 result = resource.get();
127             } catch (Throwable t) {
128                 if (callback.isPresent()) {
129                     callback.get().accept(t);
130                 } else {
131                     LOGGER.error("Could not import file " + file, t);
132                 }
133             }
134         } else {
135             LOGGER.warn("Could not find service for importing file " + file);
136             if (callback.isPresent())
137                 callback.get().accept(new Exception("Could not find IGenericFileImport service for file " + file));
138         }
139         return result;
140     }
141     
142     /**
143      * Remove the entity that matches the file. This method is called when e.g. the {@link FileImportDropins} watcher detects {@link java.nio.file.StandardWatchEventKinds.ENTRY_DELETE} operation
144      * 
145      * @param file Path file that was deleted
146      * @param callback Optional callback to catch Throwables thrown during the deletion process
147      */
148     public static void removeResourceForFile(Path file, Optional<Consumer<Throwable>> callback) {
149         try {
150             Optional<String> resource = getResourceForPath(file);
151             if (!resource.isPresent())
152                 return;
153             IGenericFileImport service = findServiceForFileExtension(file);
154             if (service == null) {
155                 LOGGER.warn("Could not find service for importing file " + file);
156                 if (callback.isPresent())
157                     callback.get().accept(new Exception("Could not find IGenericFileImport service for file " + file));
158             }
159             service.remove(resource.get());
160             removeResourceForPath(file);
161         } catch (Throwable t) {
162             if (callback.isPresent()) {
163                 callback.get().accept(t);
164             } else {
165                 LOGGER.error("Could not remove resource for file " + file.toAbsolutePath(), t);
166             }
167         }
168     }
169     
170     public static void removeFileForResource(long id, Optional<Consumer<Throwable>> callback) {
171         Optional<Path> fileOp;
172         try {
173             fileOp = findPathForId(id);
174         } catch (IOException e) {
175             LOGGER.error("Could not remove file for resource id " + id, e);
176             return;
177         }
178         if (!fileOp.isPresent())
179             return;
180         Path file = fileOp.get();
181
182         try {
183             Optional<String> resource = getResourceForPath(file);
184             if (!resource.isPresent())
185                 return;
186             IGenericFileImport service = findServiceForFileExtension(file);
187             if (service == null) {
188                 LOGGER.warn("Could not find service for importing file " + file);
189                 if (callback.isPresent())
190                     callback.get().accept(new Exception("Could not find IGenericFileImport service for file " + file));
191             }
192             service.remove(resource.get());
193             removeResourceForPath(file);
194             try {
195                 Files.delete(file);
196             } catch (IOException e) {
197                 Files.delete(file);
198             }
199         } catch (Throwable t) {
200             if (callback.isPresent()) {
201                 callback.get().accept(t);
202             } else {
203                 LOGGER.error("Could not remove file for resource " + id, t);
204             }
205         }
206     }
207
208     private static Optional<Path> findPathForId(long id) throws IOException {
209         Path db = Activator.getDropinsFolder().resolve(DB_FILE);
210         if (!Files.exists(db))
211             Files.createFile(db);
212         Properties props = new Properties();
213         try (InputStream stream = Files.newInputStream(db)) {
214             props.load(stream);
215         }
216         for (Map.Entry<Object, Object> entry : props.entrySet()) {
217             Long value = Long.valueOf(entry.getValue().toString());
218             if (value.longValue() == id) {
219                 String key = (String) entry.getKey();
220                 return Optional.of(Paths.get(key));
221             }
222         }
223         return Optional.empty();
224     }
225
226     static final String FOLDER = "_folder_";
227     
228     /**
229      * Method for finding a File Import service for the given file based on the file extension
230      * 
231      * @param file Path file for which the import service is looked for
232      * @return Optional IGenerigFileImport service which is able to handle the import of this type of file
233      */
234     public static IGenericFileImport findServiceForFileExtension(Path file) {
235         String extension = "";
236
237         int i = file.getFileName().toString().lastIndexOf('.');
238         if (i > 0) {
239             extension = file.getFileName().toString().substring(i);
240         } else {
241             // Handle case that file is actually a directory
242             if (Files.isDirectory(file) || !Files.isRegularFile(file)) {
243                 extension = FOLDER;
244             }
245         }
246         return findServiceForExtension(extension);
247     }
248
249     public static List<String> filterSupportedExtensions(String filter) {
250         return getFileImportServices().stream().filter(s -> s.allowedExtensionsWithFilters().keySet().contains(filter)).map(s -> s.allowedExtensionsWithFilters().keySet()).flatMap(Set::stream).collect(Collectors.toList());
251     }
252
253     public static IGenericFileImport findServiceForExtension(String extension) {
254         List<IGenericFileImport> services = findServicesForExtension(extension);
255         IGenericFileImport service = null;
256         if (services.size() == 1) {
257             service = services.get(0);
258         } else {
259             for (IGenericFileImport servicee : services) {
260                 service = servicee;
261                 if (isPerfectMatch(servicee.allowedExtensionsWithFilters().keySet(), extension))
262                     break;
263             }
264         }
265         return service;
266     }
267     
268     public static List<IGenericFileImport> findServicesForExtension(String extension) {
269         List<IGenericFileImport> result = new ArrayList<>();
270         List<IGenericFileImport> services = getFileImportServices();
271         for (IGenericFileImport service : services) {
272             for (Map.Entry<String, String> entry : service.allowedExtensionsWithFilters().entrySet()) {
273                 String possibleExtensions = entry.getKey();
274                 if (possibleExtensions.startsWith("*"))
275                     possibleExtensions = possibleExtensions.substring(1);
276                 if (possibleExtensions.equals(extension) || possibleExtensions.isEmpty()) {
277                     if (extension.equals(FOLDER) && possibleExtensions.equals(FOLDER)) {
278                         result.add(service);
279                     } else if (!extension.isEmpty() && !extension.equals(FOLDER)){
280                         result.add(service);
281                     }
282                 }
283             }
284         }
285         return result;
286     }
287     
288     /**
289      * Method for listing all current paths and their corresponding identifiers in Simantics database
290      * 
291      * @return Map containing 
292      */
293     public static Map<String, String> getPathsAndResources() {
294         try {
295             Path db = Activator.getDropinsFolder().resolve(DB_FILE);
296             if (!Files.exists(db))
297                 Files.createFile(db);
298             Properties props = new Properties();
299             try (InputStream stream = Files.newInputStream(db)) {
300                 props.load(stream);
301             }
302             Map<String, String> map = new HashMap<>();
303             for (Map.Entry<Object, Object> entry : props.entrySet()) {
304                 String value = (String) entry.getValue();
305                 String key = (String) entry.getKey();
306                 map.put(key, value);
307             }
308             return map;
309         } catch (IOException e) {
310             LOGGER.error("Could not get current paths and resources!", e);
311             return Collections.emptyMap();
312         }
313     }
314
315     private static void saveResourceForPath(Path file, Optional<String> resource) {
316         resource.ifPresent(res -> {
317             try {
318                 Path db = Activator.getDropinsFolder().resolve(DB_FILE);
319                 if (!Files.exists(db))
320                     Files.createFile(db);
321                 Properties props = new Properties();
322                 try (InputStream stream = Files.newInputStream(db)) {
323                     props.load(stream);
324                 }
325                 props.put(file.getFileName().toString(), resource.get());
326                 try (OutputStream stream = Files.newOutputStream(db)) {
327                     props.store(stream, null);
328                 }
329             } catch (IOException e) {
330                 LOGGER.error("Could not save resource for path " + file.toAbsolutePath() + " and resource " + resource.get(), e);
331             }
332         });
333     }
334
335     private static void removeResourceForPath(Path file) throws IOException {
336         Path db = Activator.getDropinsFolder().resolve(DB_FILE);
337         if (!Files.exists(db))
338             Files.createFile(db);
339         Properties props = new Properties();
340         try (InputStream stream = Files.newInputStream(db)) {
341             props.load(stream);
342         }
343         props.remove(file.getFileName().toString());
344         try (OutputStream stream = Files.newOutputStream(db)) {
345             props.store(stream, null);
346         }
347     }
348     
349     private static Optional<String> getResourceForPath(Path file) throws IOException {
350         Path db = Activator.getDropinsFolder().resolve(DB_FILE);
351         if (!Files.exists(db))
352             Files.createFile(db);
353         Properties props = new Properties();
354         try (InputStream stream = Files.newInputStream(db)) {
355             props.load(stream);
356         }
357         String value = props.getProperty(file.getFileName().toString());
358         if (value == null)
359             return Optional.empty();
360         return Optional.of(value);
361     }
362     
363     /**
364      * Calls the proper imported without a selection (null possibleSelection)
365      * @param path
366      * @param extension
367      * @return
368      * @throws Exception
369      */
370     public static String importGenericFileWithExtension(String path, String extension) throws Exception {
371         IGenericFileImport service = findServiceForExtension(extension);
372         Optional<String> result = service.performWithDefaultParent(Paths.get(path));
373         return result.get();
374     }
375     
376     /**
377      * Calls the proper imported without a selection (null possibleSelection)
378      * @param parent
379      * @param path
380      * @param extension
381      * @return
382      * @throws Exception
383      */
384     public static Resource importGenericFileWithExtensionAndParent(Resource parent, String path, String extension) throws Exception {
385         IGenericFileImport service = findServiceForExtension(extension);
386         Optional<Resource> result = service.perform(parent, Paths.get(path));
387         return result.get();
388     }
389
390     private static boolean isPerfectMatch(Set<String> candidates, String extension) {
391         for (String ext : candidates) {
392             if (ext.startsWith("*"))
393                 ext = ext.substring(1);
394             if (ext.equals(extension))
395                 return true;
396         }
397         return false;
398     }
399 }