001/*
002 *  Copyright 2020 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.web.source;
017
018import java.io.IOException;
019import java.nio.file.Files;
020import java.nio.file.Path;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.List;
024import java.util.Set;
025import java.util.TreeSet;
026import java.util.stream.Collectors;
027import java.util.stream.Stream;
028
029import org.apache.commons.lang3.StringUtils;
030import org.apache.excalibur.source.Source;
031import org.apache.excalibur.source.SourceException;
032import org.apache.excalibur.source.SourceValidity;
033
034import org.ametys.core.util.path.PathSource;
035
036/**
037 * A skin source is a path source that supports for inheritance
038 */
039public class SkinSource extends PathSource
040{
041    /** The files and inherited files {@link #_path} is the first existing one */
042    protected List<Path> _files;
043
044    /**
045     * Builds a SkinSource, given an URI scheme, URI and a Path.
046     * @param scheme The scheme
047     * @param uri the URI
048     * @param files The files (inheritance) Cannot be null nor empty
049     */
050    public SkinSource(String scheme, String uri, List<Path> files)
051    {
052        super(scheme, uri, _getFirstExistingFile(files));
053        _files = files;
054    }
055
056    private static Path _getFirstExistingFile(List<Path> files)
057    {
058        for (Path file : files)
059        {
060            if (Files.exists(file))
061            {
062                return file;
063            }
064        }
065        
066        // No one is existing... return the first.
067        return files.get(0);
068    }
069    
070    //----------------------------------------------------------------------------------
071    //  TraversableSource interface methods
072    //----------------------------------------------------------------------------------
073    
074    @Override
075    public Source getChild(String name) throws SourceException
076    {
077        if (!Files.isDirectory(_path))
078        {
079            throw new SourceException(getURI() + " is not a directory");
080        }
081
082        List<Path> childFiles = _files.stream()
083                .map(p -> p.resolve(name))
084                .collect(Collectors.toList());
085        
086        return new SkinSource(this.getScheme(), _uri + "/" + name, childFiles);
087    }
088    
089    @Override
090    public Collection getChildren() throws SourceException
091    {
092        if (!Files.isDirectory(_path))
093        {
094            throw new SourceException(getURI() + " is not a directory");
095        }
096
097        Set<String> children = new TreeSet<>();
098        for (Path file : _files)
099        {
100            if (Files.exists(file) && Files.isDirectory(file))
101            {
102                try (Stream<Path> files = Files.list(file))
103                {
104                    files
105                        .map(Path::getFileName)
106                        .map(Path::toString)
107                        .forEach(children::add);
108                }
109                catch (IOException e)
110                {
111                    throw new SourceException("Cannot list files under " + file, e);
112                }
113            }
114        }
115        
116        Collection<Source> childrenSource = new ArrayList<>();
117        for (String child : children)
118        {
119            childrenSource.add(getChild(child));
120        }
121        return childrenSource;
122    }
123    
124    @Override
125    public Source getParent() throws SourceException
126    {
127        String parentUri = StringUtils.substringBeforeLast(_uri, "/");
128        if (parentUri.contains("//"))
129        {
130            List<Path> parentFiles = _files.stream()
131                .map(Path::getParent)
132                .collect(Collectors.toList());
133            return new SkinSource(getScheme(), parentUri, parentFiles);
134        }
135        else
136        {
137            return null;
138        }
139    }
140    
141    @Override
142    public SourceValidity getValidity()
143    {
144        return new SkinTimeStampValidity(_files);
145    }
146    
147}