001/*
002 *  Copyright 2019 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.core.util;
017
018import java.io.File;
019import java.io.IOException;
020import java.net.JarURLConnection;
021import java.net.URI;
022import java.net.URL;
023import java.net.URLConnection;
024import java.nio.file.FileSystem;
025import java.nio.file.FileSystems;
026import java.nio.file.NoSuchFileException;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.Map;
030
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034import org.ametys.core.util.LambdaUtils.LambdaException;
035
036/**
037 * This component centralize the access to zip files
038 * Beware: All opened zip files are kept opened in memory 
039 */
040public final class JarFSManager
041{
042    private static JarFSManager _instance;
043    
044    private Map<String, FileSystem> _openedFS = new HashMap<>();
045    
046    private Logger _logger = LoggerFactory.getLogger(getClass());
047    
048    private JarFSManager()
049    {
050        // private
051    }
052
053    /**
054     * Get the unique instance
055     * @return the unique instance
056     */
057    public static JarFSManager getInstance()
058    {
059        if (_instance == null)
060        {
061            _instance = new JarFSManager();
062        }
063        
064        return _instance;
065    }
066    
067    /**
068     * Return the jar filesystem associated with the given resource
069     * @param resourcePath The resource path to seek. Such as 'org/apache/ivy/logo.png'.
070     * @return The JAR filesystem or null if the resource cannot be found
071     * @throws IOException If an error occurred while getting the JAR file of an existing resource
072     */
073    public FileSystem getFileSystemByResource(String resourcePath) throws IOException
074    {
075        String uri = getJARFileURI(resourcePath);
076        return getFileSystemByURI(uri);
077    }
078    
079    /**
080     * Return the jar filesystem associated with the given jar file
081     * @param jarFile the jar file to open
082     * @return The JAR filesystem or null if the file has a problem
083     * @throws IOException If an error occurred while opening the JAR file
084     */
085    public FileSystem getFileSystemByFile(File jarFile) throws IOException
086    {
087        String uri = "jar:" + jarFile.getCanonicalFile().toURI().toString();
088        return getFileSystemByURI(uri);
089    }
090    
091    /**
092     * Determine the JAR file holding the given resource
093     * @param resourcePath The resource path to seek. Such as 'org/apache/ivy/logo.png'.
094     * @return The JAR file location. Such as '/path/to/jar/ivy.jar' or null if the resource cannot be found
095     * @throws IOException If an error occurred while getting the JAR file of an existing resource
096     */
097    public String getJARFileURI(String resourcePath) throws IOException
098    {
099        URL resource = getClass().getClassLoader().getResource(resourcePath);
100        if (resource == null)
101        {
102            return null;
103        }
104        
105        URLConnection connection = resource.openConnection();
106        URL jarFileURL = ((JarURLConnection) connection).getJarFileURL();
107        return "jar:" + jarFileURL.toString();
108    }
109    
110    /**
111     * Return the jar filesystem associated with the given jar file denoted by its URI
112     * @param jarURI Such as "jar:file:/path/to/file.jar"
113     * @return The filesystem or null if the jarURI is null
114     * @throws IOException If the JAR file cannot be opened correctly
115     */
116    public synchronized FileSystem getFileSystemByURI(String jarURI) throws IOException
117    {
118        if (jarURI == null)
119        {
120            return null;
121        }
122        
123        try
124        {
125            _logger.debug("Opening the jar at uri: {}", jarURI);
126            return _openedFS.computeIfAbsent(jarURI, LambdaUtils.wrap(u -> {
127                return FileSystems.newFileSystem(new URI(u), Collections.emptyMap());
128            }));
129        }
130        catch (LambdaException e)
131        {
132            if (e.getCause() instanceof NoSuchFileException)
133            {
134                _logger.error("Could not find the jar at uri: {}", jarURI);
135            }
136            throw (IOException) e.getCause();
137        }
138    }
139}