Merge commit 'b809a171b6dfb81ed9ef9e84870dcbcbc5912f0e'
[simantics/platform.git] / bundles / org.simantics.fileimport / src / org / simantics / fileimport / FileImportService.java
1 package org.simantics.fileimport;\r
2 \r
3 import java.io.IOException;\r
4 import java.io.InputStream;\r
5 import java.io.OutputStream;\r
6 import java.nio.file.Files;\r
7 import java.nio.file.Path;\r
8 import java.nio.file.Paths;\r
9 import java.util.ArrayList;\r
10 import java.util.Collections;\r
11 import java.util.HashMap;\r
12 import java.util.List;\r
13 import java.util.Map;\r
14 import java.util.Optional;\r
15 import java.util.Properties;\r
16 import java.util.function.Consumer;\r
17 \r
18 import org.osgi.framework.InvalidSyntaxException;\r
19 import org.osgi.framework.ServiceReference;\r
20 import org.simantics.fileimport.dropins.FileImportDropins;\r
21 \r
22 /**\r
23  * Utility class for Simantics File import functions\r
24  * \r
25  * @author Jani Simomaa\r
26  *\r
27  */\r
28 public class FileImportService {\r
29 \r
30     public static final String DB_FILE = ".simanticsdb";\r
31 \r
32     private static List<IGenericFileImport> getFileImportServices() {\r
33         ServiceReference<?>[] serviceReferences = new ServiceReference<?>[0];\r
34         try {\r
35             serviceReferences = Activator.getContext().getAllServiceReferences(IGenericFileImport.class.getName(),\r
36                     null);\r
37         } catch (InvalidSyntaxException e) {\r
38             e.printStackTrace();\r
39         }\r
40         if (serviceReferences.length == 0)\r
41             return Collections.emptyList();\r
42 \r
43         List<IGenericFileImport> services = new ArrayList<>(serviceReferences.length);\r
44         for (ServiceReference<?> reference : serviceReferences) {\r
45             IGenericFileImport service = (IGenericFileImport) Activator.getContext().getService(reference);\r
46             services.add(service);\r
47         }\r
48         return services;\r
49     }\r
50 \r
51     /**\r
52      * Lists all supported file extensions which have a registered service for handling the import\r
53      * \r
54      * @return Map containing the extension and the description of the extension in that order\r
55      */\r
56     public static Map<String, String> supportedExtensionsWithFilters() {\r
57         List<IGenericFileImport> services = getFileImportServices();\r
58         Map<String, String> extensionsWithFilters = new HashMap<>();\r
59         for (IGenericFileImport service : services)\r
60             extensionsWithFilters.putAll(service.allowedExtensionsWithFilters());\r
61 \r
62         return extensionsWithFilters;\r
63     }\r
64 \r
65     /**\r
66      * 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\r
67      * \r
68      * @param file Path file to be imported\r
69      * @param callback Optional callback which can be used to catch Throwables thrown in the import process\r
70      */\r
71     public static void performFileImport(Path file, Optional<Consumer<Throwable>> callback) {\r
72         if (file.getFileName().toString().equals(DB_FILE))\r
73             return;\r
74         Optional<IGenericFileImport> serviceOp = findServiceForFileExtension(file);\r
75         serviceOp.ifPresent(service -> {\r
76             try {\r
77                 Optional<String> resource = service.perform(file);\r
78                 saveResourceForPath(file, resource);\r
79             } catch (Throwable t) {\r
80                 if (callback.isPresent()) {\r
81                     callback.get().accept(t);\r
82                 } else {\r
83                     t.printStackTrace();\r
84                 }\r
85             }\r
86         });\r
87     }\r
88 \r
89     \r
90     /**\r
91      * 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\r
92      * \r
93      * @param file Path file that was deleted\r
94      * @param callback Optional callback to catch Throwables thrown during the deletion process\r
95      */\r
96     public static void removeResourceForFile(Path file, Optional<Consumer<Throwable>> callback) {\r
97         Optional<IGenericFileImport> serviceOp = findServiceForFileExtension(file);\r
98         serviceOp.ifPresent(service -> {\r
99             try {\r
100                 Optional<String> resource = getResourceForPath(file);\r
101                 if (!resource.isPresent())\r
102                     return;\r
103                 service.remove(resource.get());\r
104                 removeResourceForPath(file);\r
105             } catch (Throwable t) {\r
106                 if (callback.isPresent()) {\r
107                     callback.get().accept(t);\r
108                 } else {\r
109                     t.printStackTrace();\r
110                 }\r
111             }\r
112         });\r
113     }\r
114     \r
115     public static void removeFileForResource(long id, Optional<Consumer<Throwable>> callback) {\r
116         Optional<Path> fileOp;\r
117         try {\r
118             fileOp = findPathForId(id);\r
119         } catch (IOException e) {\r
120             e.printStackTrace();\r
121             return;\r
122         }\r
123         if (!fileOp.isPresent())\r
124             return;\r
125         Path file = fileOp.get();\r
126         Optional<IGenericFileImport> serviceOp = findServiceForFileExtension(file);\r
127         serviceOp.ifPresent(service -> {\r
128             try {\r
129                 Optional<String> resource = getResourceForPath(file);\r
130                 if (!resource.isPresent())\r
131                     return;\r
132                 service.remove(resource.get());\r
133                 removeResourceForPath(file);\r
134             } catch (Throwable t) {\r
135                 if (callback.isPresent()) {\r
136                     callback.get().accept(t);\r
137                 } else {\r
138                     t.printStackTrace();\r
139                 }\r
140             }\r
141         });\r
142     }\r
143 \r
144     private static Optional<Path> findPathForId(long id) throws IOException {\r
145         Path db = Activator.getDropinsFolder().resolve(DB_FILE);\r
146         if (!Files.exists(db))\r
147             Files.createFile(db);\r
148         Properties props = new Properties();\r
149         try (InputStream stream = Files.newInputStream(db)) {\r
150             props.load(stream);\r
151         }\r
152         for (Map.Entry<Object, Object> entry : props.entrySet()) {\r
153             Long value = Long.valueOf(entry.getValue().toString());\r
154             if (value.longValue() == id) {\r
155                 String key = (String) entry.getKey();\r
156                 return Optional.of(Paths.get(key));\r
157             }\r
158         }\r
159         return Optional.empty();\r
160     }\r
161 \r
162     static final String FOLDER = "_folder_";\r
163     \r
164     /**\r
165      * Method for finding a File Import service for the given file based on the file extension\r
166      * \r
167      * @param file Path file for which the import service is looked for\r
168      * @return Optiona IGenerigFileImport service which is able to handle the import of this type of file\r
169      */\r
170     public static Optional<IGenericFileImport> findServiceForFileExtension(Path file) {\r
171         String extension = "";\r
172 \r
173         int i = file.getFileName().toString().lastIndexOf('.');\r
174         if (i > 0) {\r
175             extension = file.getFileName().toString().substring(i);\r
176         } else {\r
177             // Handle case that file is actually a directory\r
178             if (Files.isDirectory(file) || !Files.isRegularFile(file)) {\r
179                 extension = FOLDER;\r
180             }\r
181         }\r
182 \r
183         List<IGenericFileImport> services = getFileImportServices();\r
184         for (IGenericFileImport service : services) {\r
185             for (Map.Entry<String, String> entry : service.allowedExtensionsWithFilters().entrySet()) {\r
186                 String possibleExtensions = entry.getKey();\r
187                 if (possibleExtensions.startsWith("*"))\r
188                     possibleExtensions = possibleExtensions.substring(1);\r
189                 if (possibleExtensions.equals(extension) || possibleExtensions.isEmpty()) {\r
190                     if (extension.equals(FOLDER) && possibleExtensions.equals(FOLDER)) {\r
191                         return Optional.of(service);\r
192                     } else if (!extension.isEmpty() && !extension.equals(FOLDER)){\r
193                         return Optional.of(service);\r
194                     }\r
195                 }\r
196             }\r
197         }\r
198         return Optional.empty();\r
199     }\r
200     \r
201     /**\r
202      * Method for listing all current paths and their corresponding identifiers in Simantics database\r
203      * \r
204      * @return Map containing \r
205      */\r
206     public static Map<String, String> getPathsAndResources() {\r
207         try {\r
208             Path db = Activator.getDropinsFolder().resolve(DB_FILE);\r
209             if (!Files.exists(db))\r
210                 Files.createFile(db);\r
211             Properties props = new Properties();\r
212             try (InputStream stream = Files.newInputStream(db)) {\r
213                 props.load(stream);\r
214             }\r
215             Map<String, String> map = new HashMap<>();\r
216             for (Map.Entry<Object, Object> entry : props.entrySet()) {\r
217                 String value = (String) entry.getValue();\r
218                 String key = (String) entry.getKey();\r
219                 map.put(key, value);\r
220             }\r
221             return map;\r
222         } catch (IOException e) {\r
223             e.printStackTrace();\r
224             return Collections.emptyMap();\r
225         }\r
226     }\r
227 \r
228     private static void saveResourceForPath(Path file, Optional<String> resource) {\r
229         resource.ifPresent(res -> {\r
230             try {\r
231                 Path db = Activator.getDropinsFolder().resolve(DB_FILE);\r
232                 if (!Files.exists(db))\r
233                     Files.createFile(db);\r
234                 Properties props = new Properties();\r
235                 try (InputStream stream = Files.newInputStream(db)) {\r
236                     props.load(stream);\r
237                 }\r
238                 props.put(file.getFileName().toString(), resource.get());\r
239                 try (OutputStream stream = Files.newOutputStream(db)) {\r
240                     props.store(stream, null);\r
241                 }\r
242             } catch (IOException e) {\r
243                 e.printStackTrace();\r
244             }\r
245         });\r
246     }\r
247 \r
248     private static void removeResourceForPath(Path file) throws IOException {\r
249         Path db = Activator.getDropinsFolder().resolve(DB_FILE);\r
250         if (!Files.exists(db))\r
251             Files.createFile(db);\r
252         Properties props = new Properties();\r
253         try (InputStream stream = Files.newInputStream(db)) {\r
254             props.load(stream);\r
255         }\r
256         props.remove(file.getFileName().toString());\r
257         try (OutputStream stream = Files.newOutputStream(db)) {\r
258             props.store(stream, null);\r
259         }\r
260     }\r
261     \r
262     private static Optional<String> getResourceForPath(Path file) throws IOException {\r
263         Path db = Activator.getDropinsFolder().resolve(DB_FILE);\r
264         if (!Files.exists(db))\r
265             Files.createFile(db);\r
266         Properties props = new Properties();\r
267         try (InputStream stream = Files.newInputStream(db)) {\r
268             props.load(stream);\r
269         }\r
270         String value = props.getProperty(file.getFileName().toString());\r
271         if (value == null)\r
272             return Optional.empty();\r
273         return Optional.of(value);\r
274     }\r
275 }\r