1 package org.simantics.fileimport;
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;
14 import java.util.Optional;
15 import java.util.Properties;
17 import java.util.function.Consumer;
18 import java.util.stream.Collectors;
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;
29 * Utility class for Simantics File import functions
31 * @author Jani Simomaa
34 public class FileImportService {
36 private static final Logger LOGGER = LoggerFactory.getLogger(FileImportService.class);
38 private FileImportService() {}
40 public static final String DB_FILE = ".simanticsdb";
42 private static List<IGenericFileImport> getFileImportServices() {
43 ServiceReference<?>[] serviceReferences = new ServiceReference<?>[0];
45 serviceReferences = Activator.getContext().getAllServiceReferences(IGenericFileImport.class.getName(),
47 } catch (InvalidSyntaxException e) {
48 LOGGER.error("Could not get service references for IGenericFileImport!", e);
50 if (serviceReferences.length == 0)
51 return Collections.emptyList();
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);
62 * Lists all supported file extensions which have a registered service for handling the import
64 * @return Map containing the extension and the description of the extension in that order
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());
72 return extensionsWithFilters;
75 private static class ConsumerHolder implements Consumer<Throwable> {
77 private Throwable throwable;
80 public void accept(Throwable t) {
84 public Throwable getThrowable() {
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);
95 ConsumerHolder holder = new ConsumerHolder();
96 String result = performFileImport(file, Optional.empty(), Optional.of(holder));
97 if (holder.getThrowable() != null)
98 throw holder.getThrowable();
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
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
109 public static String performFileImport(Path file, Optional<Resource> possibleSelection, Optional<Consumer<Throwable>> callback) {
110 if (file.getFileName().toString().equals(DB_FILE)) {
113 String result = "Import failed";
114 IGenericFileImport service = findServiceForFileExtension(file);
116 if (service != null) {
118 Optional<String> resource;
119 if (possibleSelection.isPresent() && service.defaultParentResource() == null) {
120 resource = Optional.of(Long.toString(service.perform(possibleSelection.get(), file).get().getResourceId()));
123 resource = service.performWithDefaultParent(file);
125 saveResourceForPath(file, resource);
126 result = resource.get();
127 } catch (Throwable t) {
128 if (callback.isPresent()) {
129 callback.get().accept(t);
131 LOGGER.error("Could not import file " + file, t);
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));
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
145 * @param file Path file that was deleted
146 * @param callback Optional callback to catch Throwables thrown during the deletion process
148 public static void removeResourceForFile(Path file, Optional<Consumer<Throwable>> callback) {
150 Optional<String> resource = getResourceForPath(file);
151 if (!resource.isPresent())
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));
159 service.remove(resource.get());
160 removeResourceForPath(file);
161 } catch (Throwable t) {
162 if (callback.isPresent()) {
163 callback.get().accept(t);
165 LOGGER.error("Could not remove resource for file " + file.toAbsolutePath(), t);
170 public static void removeFileForResource(long id, Optional<Consumer<Throwable>> callback) {
171 Optional<Path> fileOp;
173 fileOp = findPathForId(id);
174 } catch (IOException e) {
175 LOGGER.error("Could not remove file for resource id " + id, e);
178 if (!fileOp.isPresent())
180 Path file = fileOp.get();
183 Optional<String> resource = getResourceForPath(file);
184 if (!resource.isPresent())
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));
192 service.remove(resource.get());
193 removeResourceForPath(file);
196 } catch (IOException e) {
199 } catch (Throwable t) {
200 if (callback.isPresent()) {
201 callback.get().accept(t);
203 LOGGER.error("Could not remove file for resource " + id, t);
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)) {
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));
223 return Optional.empty();
226 static final String FOLDER = "_folder_";
229 * Method for finding a File Import service for the given file based on the file extension
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
234 public static IGenericFileImport findServiceForFileExtension(Path file) {
235 String extension = "";
237 int i = file.getFileName().toString().lastIndexOf('.');
239 extension = file.getFileName().toString().substring(i);
241 // Handle case that file is actually a directory
242 if (Files.isDirectory(file) || !Files.isRegularFile(file)) {
246 return findServiceForExtension(extension);
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());
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);
259 for (IGenericFileImport servicee : services) {
261 if (isPerfectMatch(servicee.allowedExtensionsWithFilters().keySet(), extension))
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)) {
279 } else if (!extension.isEmpty() && !extension.equals(FOLDER)){
289 * Method for listing all current paths and their corresponding identifiers in Simantics database
291 * @return Map containing
293 public static Map<String, String> getPathsAndResources() {
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)) {
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();
309 } catch (IOException e) {
310 LOGGER.error("Could not get current paths and resources!", e);
311 return Collections.emptyMap();
315 private static void saveResourceForPath(Path file, Optional<String> resource) {
316 resource.ifPresent(res -> {
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)) {
325 props.put(file.getFileName().toString(), resource.get());
326 try (OutputStream stream = Files.newOutputStream(db)) {
327 props.store(stream, null);
329 } catch (IOException e) {
330 LOGGER.error("Could not save resource for path " + file.toAbsolutePath() + " and resource " + resource.get(), e);
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)) {
343 props.remove(file.getFileName().toString());
344 try (OutputStream stream = Files.newOutputStream(db)) {
345 props.store(stream, null);
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)) {
357 String value = props.getProperty(file.getFileName().toString());
359 return Optional.empty();
360 return Optional.of(value);
364 * Calls the proper imported without a selection (null possibleSelection)
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));
377 * Calls the proper imported without a selection (null possibleSelection)
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));
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))