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.Collections;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.NoSuchElementException;
025import java.util.Map.Entry;
026import java.util.Objects;
027import java.util.Optional;
028import java.util.Set;
029import java.util.stream.Collectors;
030
031import org.ametys.cms.FilterNameHelper;
032import org.ametys.odf.program.Program;
033import org.ametys.plugins.explorer.resources.ResourceCollection;
034import org.ametys.plugins.repository.AmetysObject;
035import org.ametys.plugins.repository.AmetysObjectIterable;
036import org.ametys.plugins.repository.AmetysObjectIterator;
037import org.ametys.plugins.repository.AmetysRepositoryException;
038import org.ametys.plugins.repository.CollectionIterable;
039import org.ametys.plugins.repository.UnknownAmetysObjectException;
040import org.ametys.plugins.repository.metadata.CompositeMetadata;
041import org.ametys.web.repository.page.Page;
042import org.ametys.web.repository.page.UnknownZoneException;
043import org.ametys.web.repository.page.Zone;
044import org.ametys.web.repository.site.Site;
045import org.ametys.web.repository.sitemap.Sitemap;
046
047/**
048 * Page representing a first-level page.
049 */
050public class FirstLevelPage implements Page
051{
052    private FirstLevelPageFactory _factory;
053    
054    private Page _root;
055    private String _name;
056    private String _title;
057    private OdfPageHandler _odfPageHandler;
058    
059    /**
060     * Constructor.
061     * @param factory the corresponding factory
062     * @param root the odf root page.
063     * @param name the page's name.
064     * @param title the page's title.
065     * @param odfPageHandler the ODF page handler.
066     */
067    public FirstLevelPage(FirstLevelPageFactory factory, Page root, String name, String title, OdfPageHandler odfPageHandler)
068    {
069        _factory = factory;
070        _root = root;
071        _name = name;
072        _title = title;
073        _odfPageHandler = odfPageHandler;
074    }
075    
076    @Override
077    public int getDepth() throws AmetysRepositoryException
078    {
079        return _root.getDepth() + 1;
080    }
081
082    @Override
083    public Set<String> getReferers() throws AmetysRepositoryException
084    {
085        return null;
086    }
087
088    @Override
089    public ResourceCollection getRootAttachments() throws AmetysRepositoryException
090    {
091        return null;
092    }
093
094    @Override
095    public String getTemplate() throws AmetysRepositoryException
096    {
097        // TODO
098        return "program-list";
099    }
100
101    @Override
102    public String getTitle() throws AmetysRepositoryException
103    {
104        return _title;
105    }
106    
107    @Override
108    public String getLongTitle() throws AmetysRepositoryException
109    {
110        return _title;
111    }
112
113    @Override
114    public PageType getType() throws AmetysRepositoryException
115    {
116        return PageType.CONTAINER;
117    }
118
119    @Override
120    public String getURL() throws AmetysRepositoryException
121    {
122        throw new UnsupportedOperationException("getURL not supported on virtual odf pages");
123    }
124
125    @Override
126    public LinkType getURLType() throws AmetysRepositoryException
127    {
128        throw new UnsupportedOperationException("getURLType not supported on virtual odf pages");
129    }
130
131    @Override
132    public Zone getZone(String name) throws UnknownZoneException, AmetysRepositoryException
133    {
134        if (!"default".equals(name))
135        {
136            throw new IllegalArgumentException("Only the zone named 'default' is actually supported on virtual program pages.");
137        }
138        
139        return new FirstLevelZone(this);
140    }
141
142    @Override
143    public AmetysObjectIterable< ? extends Zone> getZones() throws AmetysRepositoryException
144    {
145        ArrayList<Zone> zones = new ArrayList<>();
146        zones.add(new FirstLevelZone(this));
147        return new CollectionIterable<>(zones);
148    }
149
150    @Override
151    public boolean hasZone(String name) throws AmetysRepositoryException
152    {
153        return "default".equals(name);
154    }
155    
156    @Override
157    public AmetysObjectIterable<? extends Page> getChildrenPages() throws AmetysRepositoryException
158    {
159        Map<String, String> secondLevelValues = _odfPageHandler.getLevel2Values(_root);
160        Map<String, Collection<Program>> secondLevelCache = Optional.ofNullable(_factory.getODFPageCache().getProgramCache(_root, true).get(_name)).orElseGet(HashMap::new);
161        
162        List<Page> children = secondLevelCache.keySet().stream()
163            .map(secondLevelCode -> _findSecondLevelValueEntry(secondLevelValues, secondLevelCode).orElse(null))
164            .filter(Objects::nonNull)
165            .map(entry -> _toSecondLevelPage(entry))
166            .collect(Collectors.toList());
167            
168        return new CollectionIterable<>(children); 
169    }
170    
171    private Optional<Entry<String, String>> _findSecondLevelValueEntry(String levelCode)
172    {
173        Map<String, String> secondLevelValues = _odfPageHandler.getLevel2Values(_root);
174        return _findSecondLevelValueEntry(secondLevelValues, levelCode);
175    }
176    
177    private Optional<Entry<String, String>> _findSecondLevelValueEntry(Map<String, String> secondLevelValues, String levelCode)
178    {
179        return secondLevelValues.entrySet().stream()
180             // entry = (code, title)
181            .filter(entry -> entry.getKey().equals(levelCode))
182            .findFirst();
183    }
184    
185    private SecondLevelPage _toSecondLevelPage(Map.Entry<String, String> secondLevelValueEntry)
186    {
187        String childName = secondLevelValueEntry.getKey();
188        String title = secondLevelValueEntry.getValue();
189        
190        return new SecondLevelPage(_factory.getResolver(), _factory.getODFPageCache(), _root, _name, childName, title, _odfPageHandler, this);
191    }
192    
193    @Override
194    public String getPathInSitemap() throws AmetysRepositoryException
195    {
196        return _root.getPathInSitemap() + "/" + getName();
197    }
198
199    @Override
200    public Site getSite() throws AmetysRepositoryException
201    {
202        return _root.getSite();
203    }
204
205    @Override
206    public String getSiteName() throws AmetysRepositoryException
207    {
208        return _root.getSiteName();
209    }
210
211    @Override
212    public Sitemap getSitemap() throws AmetysRepositoryException
213    {
214        return _root.getSitemap();
215    }
216
217    @Override
218    public String getSitemapName() throws AmetysRepositoryException
219    {
220        return _root.getSitemapName();
221    }
222
223    @SuppressWarnings("unchecked")
224    @Override
225    public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException
226    {
227        if (path.isEmpty())
228        {
229            throw new AmetysRepositoryException("path must be non empty");
230        }
231        
232        int i = path.indexOf('/');
233        
234        String name = i == -1 ? path : path.substring(0, path.indexOf('/'));
235        
236        int index = name.lastIndexOf("-");
237        String level2Code = index != -1 ? _odfPageHandler.decodeLevelValue(name.substring(index + 1)) : _odfPageHandler.decodeLevelValue(name);
238        
239        Page level2Page = _findSecondLevelValueEntry(level2Code)
240                .map(this::_toSecondLevelPage)
241                .orElseThrow(() -> 
242                    new UnknownAmetysObjectException("There's no virtual child page at path " + path + " for page " + _name));
243        
244        if (i == -1)
245        {
246            return (A) level2Page;
247        }
248        else
249        {
250            return (A) level2Page.getChild(path.substring(i + 1));
251        }
252    }
253    
254    @SuppressWarnings("unchecked")
255    @Override
256    public AmetysObjectIterable<? extends Page> getChildren() throws AmetysRepositoryException
257    {
258        return getChildrenPages();
259    }
260
261    @Override
262    public boolean hasChild(String childName) throws AmetysRepositoryException
263    {
264        // FIXME should use page cache, because can return true when page does
265        // not exist. Maybe done like this for performance reasons?
266        Map<String, String> secondLevelValues = _odfPageHandler.getLevel2Values(_root);
267        return secondLevelValues.containsKey(childName);
268    }
269    
270    @Override
271    public String getId() throws AmetysRepositoryException
272    {
273        // E.g: odfLevel1://XA?rootId=...
274        return "odfLevel1://" + _odfPageHandler.encodeLevelValue(_name) + "?rootId=" + _root.getId();
275    }
276
277    @Override
278    public String getName() throws AmetysRepositoryException
279    {
280        // E.g: licence-lmd-XA
281        return FilterNameHelper.filterName(_title) + "-" + _odfPageHandler.encodeLevelValue(_name);
282    }
283    
284    @SuppressWarnings("unchecked")
285    @Override
286    public Page getParent() throws AmetysRepositoryException
287    {
288        return _root;
289    }
290
291    @Override
292    public String getParentPath() throws AmetysRepositoryException
293    {
294        return _root.getPath();
295    }
296
297    @Override
298    public String getPath() throws AmetysRepositoryException
299    {
300        return getParentPath() + "/" + getName();
301    }
302
303    @Override
304    public CompositeMetadata getMetadataHolder()
305    {
306        return new StaticCompositeMetadata();
307    }
308
309    @Override
310    public Set<String> getTags() throws AmetysRepositoryException
311    {
312        return Collections.emptySet();
313    }
314    
315    @Override
316    public boolean isVisible() throws AmetysRepositoryException
317    {
318        return true;
319    }
320
321    @Override
322    public AmetysObjectIterable< ? extends Page> getChildrenPages(boolean includeInvisiblePages) throws AmetysRepositoryException
323    {
324        return getChildrenPages();
325    }
326    
327    @Override
328    public Page getChildPageAt(int index) throws UnknownAmetysObjectException, AmetysRepositoryException
329    {
330        if (index < 0)
331        {
332            throw new AmetysRepositoryException("Child page index cannot be negative");
333        }
334        
335        AmetysObjectIterator<? extends Page> childPages = getChildrenPages().iterator();
336        
337        try
338        {
339            childPages.skip(index);
340            return childPages.next();
341        }
342        catch (NoSuchElementException e)
343        {
344            throw new UnknownAmetysObjectException("There's no child page at index " + index + " for page " + this.getId());
345        }
346    }
347}