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.util.Collections;
027import java.util.HashMap;
028import java.util.Map;
029
030/**
031 * This component centralize the access to zip files
032 * Beware: All opened zip files are kept opened in memory 
033 */
034public final class JarFSManager
035{
036    private static JarFSManager _instance;
037    
038    private Map<String, FileSystem> _openedFS = new HashMap<>();
039    
040    private JarFSManager()
041    {
042        // private
043    }
044
045    /**
046     * Get the unique instance
047     * @return the unique instance
048     */
049    public static JarFSManager getInstance()
050    {
051        if (_instance == null)
052        {
053            _instance = new JarFSManager();
054        }
055        
056        return _instance;
057    }
058    
059    /**
060     * Return the jar filesystem associated with the given resource
061     * @param resourcePath The resource path to seek. Such as 'org/apache/ivy/logo.png'.
062     * @return The JAR filesystem or null if the resource cannot be found
063     * @throws IOException If an error occurred while getting the JAR file of an existing resource
064     */
065    public FileSystem getFileSystemByResource(String resourcePath) throws IOException
066    {
067        String uri = getJARFileURI(resourcePath);
068        return getFileSystemByURI(uri);
069    }
070    
071    /**
072     * Return the jar filesystem associated with the given jar file
073     * @param jarFile the jar file to open
074     * @return The JAR filesystem or null if the file has a problem
075     * @throws IOException If an error occurred while opening the JAR file
076     */
077    public FileSystem getFileSystemByFile(File jarFile) throws IOException
078    {
079        String uri = "jar:" + jarFile.getCanonicalFile().toURI().toString();
080        return getFileSystemByURI(uri);
081    }
082    
083    /**
084     * Determine the JAR file holding the given resource
085     * @param resourcePath The resource path to seek. Such as 'org/apache/ivy/logo.png'.
086     * @return The JAR file location. Such as '/path/to/jar/ivy.jar' or null if the resource cannot be found
087     * @throws IOException If an error occurred while getting the JAR file of an existing resource
088     */
089    public String getJARFileURI(String resourcePath) throws IOException
090    {
091        URL resource = getClass().getClassLoader().getResource(resourcePath);
092        if (resource == null)
093        {
094            return null;
095        }
096        
097        URLConnection connection = resource.openConnection();
098        URL jarFileURL = ((JarURLConnection) connection).getJarFileURL();
099        return "jar:" + jarFileURL.toString();
100    }
101    
102    /**
103     * Return the jar filesystem associated with the given jar file denoted by its URI
104     * @param jarURI Such as "jar:file:/path/to/file.jar"
105     * @return The filesystem or null if the jarURI is null
106     * @throws IOException If the JAR file cannot be opened correctly
107     */
108    public synchronized FileSystem getFileSystemByURI(String jarURI) throws IOException
109    {
110        if (jarURI == null)
111        {
112            return null;
113        }
114        
115        return _openedFS.computeIfAbsent(jarURI, LambdaUtils.wrap(u -> FileSystems.newFileSystem(new URI(u), Collections.emptyMap())));
116    }
117}