001/*
002 *  Copyright 2017 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.plugins.core.ui.minimize;
017
018import java.io.UnsupportedEncodingException;
019import java.util.ArrayList;
020import java.util.Base64;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Objects;
025import java.util.stream.Collectors;
026
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.commons.lang3.StringUtils;
032
033import org.ametys.plugins.core.ui.resources.ResourceDependenciesListExtensionPoint;
034import org.ametys.plugins.core.ui.util.RequestAttributesHelper;
035import org.ametys.runtime.plugin.component.AbstractLogEnabled;
036
037/**
038 * The cache for hashed list of files to minimize
039 */
040public class HashCache extends AbstractLogEnabled implements Component, Serviceable
041{
042    /** The avalon ROLE */
043    public static final String ROLE = HashCache.class.getName();
044    
045    /** RequestAttributesHelper */
046    protected RequestAttributesHelper _requestAttributesHelper;
047
048    /** ResourceDependenciesListExtensionPoint */
049    protected ResourceDependenciesListExtensionPoint _resourceDependenciesListEP;
050
051    /** 
052     * Hash cache
053     * Key if file url 
054     */
055    private Map<String, List<UriData>> _hashCache = new HashMap<>();
056
057    public void service(ServiceManager manager) throws ServiceException
058    {
059        _requestAttributesHelper = (RequestAttributesHelper) manager.lookup(RequestAttributesHelper.ROLE);
060        
061        _resourceDependenciesListEP = (ResourceDependenciesListExtensionPoint) manager.lookup(ResourceDependenciesListExtensionPoint.ROLE);
062    }
063    
064    /**
065     * Get the file list corresponding to the given hashcode
066     * @param hash The hashcode created by createHash in this session
067     * @param firstLevel File imported by other files will not be included
068     * @return The list of files or null if the hashcode does not exists
069     */
070    public List<UriData> getFilesForHash(String hash, boolean firstLevel)
071    {
072        if (firstLevel)
073        {
074            return _hashCache.get(hash).stream()
075                    .filter(UriData::isFirstLevel)
076                    .collect(Collectors.toList());
077        }
078        return _hashCache.get(hash);
079    }
080    
081    /**
082     * Creates and memorize a hash code corresponding to a list of files 
083     * @param files The files. Key is the file uri and the value is a map with two keys 'media' and 'tag'.
084     * @param salt Additional parameter used as salt for the hash key.
085     * @return The hash code
086     * @throws IllegalArgumentException If an error occurred
087     */
088    public String createHash(Map<String, Map<String, String>> files, String salt) throws IllegalArgumentException
089    {
090        List<UriData> hashCache = new ArrayList<>();
091        
092        Map<String, Object> attributes = _requestAttributesHelper.saveRequestAttributes();
093        
094        try
095        {
096            files.entrySet().stream()
097                    .map(file -> _resourceDependenciesListEP.getDependencies(file.getKey(), file.getValue()))
098                    .filter(Objects::nonNull)
099                    .forEach(fileDependencies -> hashCache.addAll(fileDependencies));
100            
101            String hash = Base64.getEncoder().withoutPadding().encodeToString(String.valueOf(31 * hashCache.hashCode() + salt.hashCode()).getBytes("UTF-8"));
102            _hashCache.put(hash, hashCache);
103            return hash;
104        }
105        catch (UnsupportedEncodingException e)
106        {
107            throw new IllegalArgumentException(e);
108        }
109        finally
110        {
111            _requestAttributesHelper.restoreRequestAttributes(attributes);
112        }
113    }
114
115    /**
116     * The description of an URI
117     */
118    public static class UriData
119    {
120        private String _uri;
121        private Long _lastModified;
122        private String _media;
123        private boolean _firstLevel;
124        
125        /**
126         * Default constructor for a file data
127         * @param uri The uri locating the file
128         * @param firstLevel False if the file data is an import from another file data
129         */
130        public UriData(String uri, boolean firstLevel)
131        {
132            _uri = uri;
133            _firstLevel = firstLevel;
134        }
135        
136        /**
137         * Set the last modified value
138         * @param lastModified the lastModified to set
139         */
140        public void setLastModified(Long lastModified)
141        {
142            this._lastModified = lastModified;
143        }
144
145        /**
146         * set the medias value
147         * @param media the medias to set
148         */
149        public void setMedia(String media)
150        {
151            this._media = media;
152        }
153
154        /**
155         * Get the file uri
156         * @return the uri
157         */
158        public String getUri()
159        {
160            return _uri;
161        }
162        
163        /**
164         * Get the file last modified date
165         * @return the lastModified
166         */
167        public Long getLastModified()
168        {
169            return _lastModified;
170        }
171        
172        /**
173         * Get the file medias
174         * @return the medias
175         */
176        public String getMedia()
177        {
178            return _media;
179        }
180        
181        /**
182         * Check if the file is a first level file
183         * @return True if the file is first level
184         */
185        public boolean isFirstLevel()
186        {
187            return _firstLevel;
188        }
189        
190        @Override
191        public boolean equals(Object obj)
192        {
193            if (obj instanceof UriData)
194            {
195                UriData fObj = (UriData) obj;
196                return StringUtils.equals(_uri, fObj._uri)
197                        && (_lastModified == null ? fObj._lastModified == null : _lastModified.equals(fObj._lastModified)) 
198                        && StringUtils.equals(_media, fObj._media);
199            }
200            return false;
201        }
202        
203        @Override
204        public int hashCode()
205        {
206            return Objects.hash(_uri, _lastModified, _media);
207        }
208        
209        @Override
210        public String toString()
211        {
212            if (_media != null)
213            {
214                return _uri + "#" + _media + " (" + _lastModified + ")";
215            }
216            else
217            {
218                return _uri + " (" + _lastModified + ")";
219            }
220        }
221    }
222}