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