001/*
002 *  Copyright 2010 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.Collection;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Map.Entry;
024import java.util.Objects;
025import java.util.Optional;
026import java.util.stream.Collectors;
027
028import org.apache.commons.lang3.StringUtils;
029
030import org.ametys.cms.FilterNameHelper;
031import org.ametys.odf.program.Program;
032import org.ametys.plugins.repository.AmetysObject;
033import org.ametys.plugins.repository.AmetysObjectIterable;
034import org.ametys.plugins.repository.AmetysRepositoryException;
035import org.ametys.plugins.repository.CollectionIterable;
036import org.ametys.plugins.repository.UnknownAmetysObjectException;
037import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
038import org.ametys.plugins.repository.data.holder.ModelLessDataHolder;
039import org.ametys.plugins.repository.data.holder.impl.DefaultModelLessDataHolder;
040import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
041import org.ametys.plugins.repository.data.repositorydata.impl.MemoryRepositoryData;
042import org.ametys.web.repository.page.Page;
043import org.ametys.web.repository.page.UnknownZoneException;
044import org.ametys.web.repository.page.Zone;
045import org.ametys.web.repository.site.Site;
046import org.ametys.web.repository.sitemap.Sitemap;
047
048/**
049 * Page representing a first-level page.
050 */
051public class FirstLevelPage extends AbstractLevelPage
052{
053    private FirstLevelPageFactory _factory;
054    
055    private Page _root;
056    private String _name;
057    private String _title;
058    
059    
060    /**
061     * Constructor.
062     * @param factory the corresponding factory
063     * @param root the odf root page.
064     * @param name the page's name.
065     * @param title the page's title.
066     */
067    public FirstLevelPage(FirstLevelPageFactory factory, Page root, String name, String title)
068    {
069        _factory = factory;
070        _root = root;
071        _name = name;
072        _title = title;
073    }
074    
075    @Override
076    public int getDepth() throws AmetysRepositoryException
077    {
078        return _root.getDepth() + 1;
079    }
080
081    @Override
082    public String getTitle() throws AmetysRepositoryException
083    {
084        return _title;
085    }
086    
087    @Override
088    public String getLongTitle() throws AmetysRepositoryException
089    {
090        return _title;
091    }
092
093    @Override
094    public Zone getZone(String name) throws UnknownZoneException, AmetysRepositoryException
095    {
096        if (!"default".equals(name))
097        {
098            throw new IllegalArgumentException("Only the zone named 'default' is actually supported on virtual program pages.");
099        }
100        
101        return new FirstLevelZone(this, _factory.getZoneDataTypeEP(), _factory.getServiceEP(), _factory.getZoneItemDataTypeEP());
102    }
103
104    @Override
105    public AmetysObjectIterable< ? extends Zone> getZones() throws AmetysRepositoryException
106    {
107        ArrayList<Zone> zones = new ArrayList<>();
108        zones.add(new FirstLevelZone(this, _factory.getZoneDataTypeEP(), _factory.getServiceEP(), _factory.getZoneItemDataTypeEP()));
109        return new CollectionIterable<>(zones);
110    }
111
112    @Override
113    public AmetysObjectIterable<? extends Page> getChildrenPages() throws AmetysRepositoryException
114    {
115        if (!_hasSecondLevelPages())
116        {
117            List<ProgramPage> pages = _getChildrenProgramPageFromCache()
118                    .orElseGet(() -> _factory.getODFPageHandler().getProgramsWithRestrictions(_root, _name, null, null, null))
119                    .stream()
120                    .map(this::_toProgramPage)
121                    .collect(Collectors.toList());
122                
123            return new CollectionIterable<>(pages);
124                
125        }
126        else
127        {
128            Map<String, Collection<Program>> secondLevelCache = Optional.ofNullable(_factory.getODFPageCache().getProgramCache(_root, true).get(_name)).orElseGet(HashMap::new);
129            Map<String, String> secondLevelValues = _factory.getODFPageHandler().getLevel2Values(_root);
130            
131            List<Page> children = secondLevelCache.keySet().stream()
132                    .map(secondLevelCode -> _findSecondLevelValueEntry(secondLevelValues, secondLevelCode).orElse(null))
133                    .filter(Objects::nonNull)
134                    .map(entry -> _toSecondLevelPage(entry))
135                    .collect(Collectors.toList());
136            
137            return new CollectionIterable<>(children); 
138        }
139    }
140    
141    private boolean _hasSecondLevelPages()
142    {
143        String level2Metadata = _factory.getODFPageHandler().getLevel2Metadata(_root);
144        return StringUtils.isNotBlank(level2Metadata);
145    }
146    
147    private Optional<AmetysObjectIterable<Program>> _getChildrenProgramPageFromCache()
148    {
149        return _factory.getODFPageCache().getPrograms(_root, _name, null, false);
150    }
151    
152    private Optional<Entry<String, String>> _findSecondLevelValueEntry(String levelCode)
153    {
154        Map<String, String> secondLevelValues = _factory.getODFPageHandler().getLevel2Values(_root);
155        return _findSecondLevelValueEntry(secondLevelValues, levelCode);
156    }
157    
158    private Optional<Entry<String, String>> _findSecondLevelValueEntry(Map<String, String> secondLevelValues, String levelCode)
159    {
160        return secondLevelValues.entrySet().stream()
161             // entry = (code, title)
162            .filter(entry -> entry.getKey().equals(levelCode))
163            .findFirst();
164    }
165    
166    private SecondLevelPage _toSecondLevelPage(Map.Entry<String, String> secondLevelValueEntry)
167    {
168        String childName = secondLevelValueEntry.getKey();
169        String title = secondLevelValueEntry.getValue();
170        
171        return new SecondLevelPage(_factory.getSecondLevelPageFactory(), _root, _name, childName, title, this);
172    }
173    
174    private ProgramPage _toProgramPage(Program program)
175    {
176        return new ProgramPage(_factory.getProgramPageFactory(), _root, program, null, null, this);
177    }
178    
179    @Override
180    public String getPathInSitemap() throws AmetysRepositoryException
181    {
182        return _root.getPathInSitemap() + "/" + getName();
183    }
184
185    @Override
186    public Site getSite() throws AmetysRepositoryException
187    {
188        return _root.getSite();
189    }
190
191    @Override
192    public String getSiteName() throws AmetysRepositoryException
193    {
194        return _root.getSiteName();
195    }
196
197    @Override
198    public Sitemap getSitemap() throws AmetysRepositoryException
199    {
200        return _root.getSitemap();
201    }
202
203    @Override
204    public String getSitemapName() throws AmetysRepositoryException
205    {
206        return _root.getSitemapName();
207    }
208
209    @SuppressWarnings("unchecked")
210    @Override
211    public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException
212    {
213        if (path.isEmpty())
214        {
215            throw new AmetysRepositoryException("path must be non empty");
216        }
217        
218        if (!_hasSecondLevelPages())
219        {
220            return (A) _factory.getODFPageCache().getChildProgramPage(_root, this, _name, null, path);
221        }
222        else
223        {
224            int i = path.indexOf('/');
225            
226            String name = i == -1 ? path : path.substring(0, path.indexOf('/'));
227            
228            int index = name.lastIndexOf("-");
229            String level2Code = index != -1 ? _factory.getODFPageHandler().decodeLevelValue(name.substring(index + 1)) : _factory.getODFPageHandler().decodeLevelValue(name);
230            
231            Page level2Page = _findSecondLevelValueEntry(level2Code)
232                    .map(this::_toSecondLevelPage)
233                    .orElseThrow(() -> 
234                    new UnknownAmetysObjectException("There's no virtual child page at path " + path + " for page " + _name));
235            
236            if (i == -1)
237            {
238                return (A) level2Page;
239            }
240            else
241            {
242                return (A) level2Page.getChild(path.substring(i + 1));
243            }
244        }
245        
246    }
247    
248    @SuppressWarnings("unchecked")
249    @Override
250    public AmetysObjectIterable<? extends Page> getChildren() throws AmetysRepositoryException
251    {
252        return getChildrenPages();
253    }
254
255    @Override
256    public boolean hasChild(String childName) throws AmetysRepositoryException
257    {
258        // FIXME should use page cache, because can return true when page does
259        // not exist. Maybe done like this for performance reasons?
260        if (!_hasSecondLevelPages())
261        {
262            return _factory.getODFPageCache().getProgramFromPageName(_root, _name, null, childName).isPresent();
263        }
264        else
265        {
266            Map<String, String> secondLevelValues = _factory.getODFPageHandler().getLevel2Values(_root);
267            return secondLevelValues.containsKey(childName);
268        }
269    }
270    
271    @Override
272    public String getId() throws AmetysRepositoryException
273    {
274        // E.g: odfLevel1://XA?rootId=...
275        return "odfLevel1://" + _factory.getODFPageHandler().encodeLevelValue(_name) + "?rootId=" + _root.getId();
276    }
277
278    @Override
279    public String getName() throws AmetysRepositoryException
280    {
281        // E.g: licence-lmd-XA
282        return FilterNameHelper.filterName(_title) + "-" + _factory.getODFPageHandler().encodeLevelValue(_name);
283    }
284    
285    @SuppressWarnings("unchecked")
286    @Override
287    public Page getParent() throws AmetysRepositoryException
288    {
289        return _root;
290    }
291
292    @Override
293    public String getParentPath() throws AmetysRepositoryException
294    {
295        return _root.getPath();
296    }
297
298    public ModelLessDataHolder getDataHolder()
299    {
300        RepositoryData repositoryData = new MemoryRepositoryData(getName());
301        return new DefaultModelLessDataHolder(_factory.getPageDataTypeEP(), repositoryData);
302    }
303    
304    public ModelAwareDataHolder getTemplateParametersHolder() throws AmetysRepositoryException
305    {
306        return null;
307    }
308}