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