001/*
002 *  Copyright 2012 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.odfweb.repository;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.Comparator;
022import java.util.HashMap;
023import java.util.Map;
024import java.util.Set;
025import java.util.TreeMap;
026
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.context.Context;
029import org.apache.avalon.framework.context.ContextException;
030import org.apache.avalon.framework.context.Contextualizable;
031import org.apache.avalon.framework.logger.AbstractLogEnabled;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.avalon.framework.service.Serviceable;
035import org.apache.cocoon.components.ContextHelper;
036import org.apache.cocoon.environment.Request;
037import org.apache.commons.lang.ArrayUtils;
038
039import org.ametys.odf.program.Program;
040import org.ametys.plugins.repository.AmetysObjectIterable;
041import org.ametys.plugins.repository.RepositoryConstants;
042import org.ametys.plugins.repository.provider.WorkspaceSelector;
043import org.ametys.web.WebConstants;
044import org.ametys.web.repository.page.Page;
045
046/**
047 * Maintains a per-request cache, dispatching ODF virtual pages accross degrees and domains.
048 */
049public class ODFPageCache extends AbstractLogEnabled implements Serviceable, Contextualizable, Component
050{
051    /** Avalon role. */
052    public static final String ROLE = ODFPageCache.class.getName();
053    
054    private static final String __ATT_CACHE_PREFIX = "odf.pageCache.";
055    private static final String __ATT_CACHE_PROGRAM_PREFIX = "odf.pageCache.program.";
056    private Context _context;
057    private OdfPageHandler _odfPageHandler;
058    private WorkspaceSelector _workspaceSelector;
059    
060    @Override
061    public void contextualize(Context context) throws ContextException
062    {
063        _context = context;
064    }
065    
066    @Override
067    public void service(ServiceManager manager) throws ServiceException
068    {
069        _odfPageHandler = (OdfPageHandler) manager.lookup(OdfPageHandler.ROLE);
070        _workspaceSelector = (WorkspaceSelector) manager.lookup(WorkspaceSelector.ROLE);
071    }
072    
073    Map<String, Map<String, Collection<Program>>> getProgramCache(Page rootPage, boolean computeIfNotPresent)
074    {
075        Request request = ContextHelper.getRequest(_context);
076        String workspace = _workspaceSelector.getWorkspace();
077        String pageId = rootPage.getId();
078        String requestAttributeName = __ATT_CACHE_PREFIX + workspace + "." + pageId;
079        
080        @SuppressWarnings("unchecked")
081        // Map<level1, Map<level2, Collection<Program>>>
082        Map<String, Map<String, Collection<Program>>> level1Cache = (Map<String, Map<String, Collection<Program>>>) request.getAttribute(requestAttributeName);
083        
084        if (level1Cache == null)
085        {
086            if (!computeIfNotPresent)
087            {
088                return null;
089            }
090            
091            Set<String> level1Codes = _odfPageHandler.getLevel1Values(rootPage).keySet();
092            CollectionComparator level1Comparator = new CollectionComparator(level1Codes);
093            
094            Set<String> level2Codes = _odfPageHandler.getLevel2Values(rootPage).keySet();
095            CollectionComparator level2Comparator = new CollectionComparator(level2Codes);
096            
097            level1Cache = new TreeMap<>(level1Comparator);
098            request.setAttribute(requestAttributeName, level1Cache);
099            
100            AmetysObjectIterable<Program> programs = _odfPageHandler.getProgramsWithRestrictions(rootPage, null, null, null);
101            
102            String level1MetaPath = _odfPageHandler.getLevel1Metadata(rootPage);
103            String level2MetaPath = _odfPageHandler.getLevel2Metadata(rootPage);
104            
105            for (Program program : programs)
106            {
107                String programL1Value = _odfPageHandler.getProgramLevelValue(program, level1MetaPath);
108                String programL2Value = _odfPageHandler.getProgramLevelValue(program, level2MetaPath);
109                
110                if (level1Codes.contains(programL1Value) && level2Codes.contains(programL2Value))
111                {
112                    Map<String, Collection<Program>> level2Cache = level1Cache.computeIfAbsent(programL1Value, x -> new TreeMap<>(level2Comparator));
113                    Collection<Program> programCache = level2Cache.computeIfAbsent(programL2Value, x -> new ArrayList<>());
114                    
115                    programCache.add(program);
116                }
117            }
118        }
119        
120        return level1Cache;
121    }
122    
123    private static class CollectionComparator implements Comparator<String>
124    {
125        private final String[] _baseCollection;
126        
127        public CollectionComparator(Collection<String> baseCollection)
128        {
129            _baseCollection = baseCollection.toArray(new String[baseCollection.size()]);
130        }
131        
132        public int compare(String s1, String s2)
133        {
134            Integer i1 = ArrayUtils.indexOf(_baseCollection, s1);
135            Integer i2 = ArrayUtils.indexOf(_baseCollection, s2);
136            
137            return i1.compareTo(i2);
138        }
139    }
140    
141    /**
142     * Get cached program
143     * @param rootPage The ODF root page
144     * @param level1 The value of first level
145     * @param level2 The value of second level
146     * @param programName The name of program
147     * @return program The program
148     */
149    Program getProgram(Page rootPage, String level1, String level2, String programName)
150    {
151        Request request = ContextHelper.getRequest(_context);
152        String workspace = _workspaceSelector.getWorkspace();
153        String pageId = rootPage.getId();
154        String requestAttributeName = __ATT_CACHE_PROGRAM_PREFIX + workspace + "." + pageId;
155        
156        @SuppressWarnings("unchecked")
157        // Map<level1, Map<level2, Map<program name, Program>>>
158        Map<String, Map<String, Map<String, Program>>> level1Cache = (Map<String, Map<String, Map<String, Program>>>) request.getAttribute(requestAttributeName);
159        
160        if (level1Cache == null)
161        {
162            level1Cache = new HashMap<>();
163            request.setAttribute(requestAttributeName, level1Cache);
164        }
165        
166        // level 1
167        Map<String, Map<String, Program>> level2Cache = level1Cache.computeIfAbsent(level1, x -> new HashMap<>());
168        
169        // level 2
170        final Map<String, Program> programCache = level2Cache.computeIfAbsent(level2, x -> new HashMap<>());
171        
172        // program name
173        Program program = programCache.computeIfAbsent(programName, name -> this._getProgramWithRestrictions(rootPage, level1, level2, programName));
174        
175        return program;
176    }
177    
178    private Program _getProgramWithRestrictions(Page rootPage, String level1, String level2, String programName)
179    {
180        return _odfPageHandler.getProgramsWithRestrictions(rootPage, level1, level2, programName).stream()
181                .findFirst().orElse(null);
182        
183    }
184    
185    /**
186     * Clear page cache
187     * @param rootPage The ODF root page
188     */
189    public void clearCache (Page rootPage)
190    {
191        Request request = ContextHelper.getRequest(_context);
192        String pageId = rootPage.getId();
193        
194        for (String workspace : Arrays.asList(RepositoryConstants.DEFAULT_WORKSPACE, WebConstants.LIVE_WORKSPACE))
195        {
196            String requestAttributeName = __ATT_CACHE_PREFIX + workspace + "." + pageId;
197            request.removeAttribute(requestAttributeName);
198            
199            requestAttributeName = __ATT_CACHE_PROGRAM_PREFIX + workspace + "." + pageId;
200            request.removeAttribute(requestAttributeName);
201        }
202    }
203}