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.Collection;
019import java.util.List;
020import java.util.Map;
021import java.util.Map.Entry;
022import java.util.Objects;
023import java.util.Optional;
024import java.util.stream.Collectors;
025
026import org.apache.commons.lang3.StringUtils;
027
028import org.ametys.odf.program.Program;
029import org.ametys.plugins.repository.AmetysObjectFactory;
030import org.ametys.plugins.repository.AmetysObjectIterable;
031import org.ametys.plugins.repository.AmetysRepositoryException;
032import org.ametys.plugins.repository.CollectionIterable;
033import org.ametys.plugins.repository.UnknownAmetysObjectException;
034import org.ametys.plugins.repository.jcr.JCRAmetysObject;
035import org.ametys.plugins.repository.virtual.VirtualAmetysObjectFactory;
036import org.ametys.web.repository.page.Page;
037
038/**
039 * {@link AmetysObjectFactory} handling {@link FirstLevelPage}.
040 * This factory is referenced by an ODF root page.
041 */
042public class FirstLevelPageFactory extends AbstractOdfPageFactory implements VirtualAmetysObjectFactory<Page>
043{
044    @Override
045    public String getScheme()
046    {
047        return "odfLevel1";
048    }
049    
050    @Override
051    public Page getAmetysObjectById(String id) throws AmetysRepositoryException
052    {
053        // E.g: odfLevel1://XA?rootId=xxx
054        String childName = _extractChildName(id);
055        Page rootPage = _extractRoot(id);
056        
057        return _findFirstLevelValueEntry(rootPage, childName)
058            .map(entry -> _toFirstLevelPage(rootPage, entry))
059            .orElseThrow(() -> 
060                new UnknownAmetysObjectException("There's no virtual child page named " + childName + " for parent " + rootPage));
061    }
062    
063    private Page _extractRoot(String id)
064    {
065        String rootId = StringUtils.substringAfter(id, "?rootId=");
066        return _resolver.resolveById(rootId);
067    }
068    
069    private String _extractChildName(String id)
070    {
071        String rawChildName = StringUtils.substringBetween(id, "://", "?");
072        return _odfPageHandler.decodeLevelValue(rawChildName);
073    }
074    
075    private Optional<Entry<String, String>> _findFirstLevelValueEntry(Page rootPage, String levelCode)
076    {
077        Map<String, String> firstLevelValues = _odfPageHandler.getLevel1Values(rootPage);
078        return _findFirstLevelValueEntry(firstLevelValues, levelCode);
079    }
080    
081    private Optional<Entry<String, String>> _findFirstLevelValueEntry(Map<String, String> firstLevelValues, String levelCode)
082    {
083        return firstLevelValues.entrySet().stream()
084             // entry = (code, title)
085            .filter(entry -> entry.getKey().equals(levelCode))
086            .findFirst();
087    }
088    
089    private FirstLevelPage _toFirstLevelPage(Page rootPage, Map.Entry<String, String> firstLevelValueEntry)
090    {
091        String childName = firstLevelValueEntry.getKey();
092        String title = firstLevelValueEntry.getValue();
093        return new FirstLevelPage(this, rootPage, childName, title);
094    }
095    
096    private ProgramPage _toProgramPage(Program program, Page root)
097    {
098        return new ProgramPage(getProgramPageFactory(), root, program, null, null, root);
099    }
100    
101    @Override
102    public boolean hasAmetysObjectForId(String id) throws AmetysRepositoryException
103    {
104        // E.g: odfLevel1://XA?rootId=xxx
105        String childName = _extractChildName(id);
106        Page rootPage = _extractRoot(id);
107        
108        return _hasFirstLevelValue(rootPage, childName);
109    }
110    
111    @Override
112    public Page getChild(JCRAmetysObject parent, String childName)
113    {
114        if (!(parent instanceof Page))
115        {
116            throw new IllegalArgumentException("The holder of the ODF virtual pages should be a page.");
117        }
118        
119        Page rootPage = (Page) parent;
120        String level1Metadata = _odfPageHandler.getLevel1Metadata(rootPage);
121        if (StringUtils.isBlank(level1Metadata))
122        {
123            return _pageCache.getChildProgramPage(rootPage, rootPage, null, null, childName);
124        }
125        else
126        {
127            Map<String, String> firstLevelValues = _odfPageHandler.getLevel1Values(rootPage);
128            
129            // E.g: licence-lmd-XA
130            int i = childName.lastIndexOf("-");
131            String code = i != -1 ? _odfPageHandler.decodeLevelValue(childName.substring(i + 1)) : _odfPageHandler.decodeLevelValue(childName); // extract first level code
132            
133            String title = firstLevelValues.get(code);
134            if (title != null)
135            {
136                return new FirstLevelPage(this, rootPage, code, title);
137            }
138        }
139
140        throw new UnknownAmetysObjectException("There's no virtual child page named " + childName + " for parent " + parent);
141    }
142    
143    @Override
144    public AmetysObjectIterable<Page> getChildren(JCRAmetysObject parent)
145    {
146        if (!(parent instanceof Page))
147        {
148            throw new IllegalArgumentException("The holder of the ODF virtual pages should be a page.");
149        }
150        
151        Page rootPage = (Page) parent;
152        String level1Metadata = _odfPageHandler.getLevel1Metadata(rootPage);
153        if (StringUtils.isBlank(level1Metadata))
154        {
155            List<Page> pages = _pageCache.getPrograms(rootPage, null, null, true)
156                    .get().stream()
157                    .map(p -> _toProgramPage(p, rootPage))
158                    .collect(Collectors.toList());
159            
160            return new CollectionIterable<>(pages);
161        }
162        else
163        {
164            Map<String, String> firstLevelValues = _odfPageHandler.getLevel1Values(rootPage);
165            Map<String, Map<String, Collection<Program>>> firstLevelCache = _pageCache.getProgramCache(rootPage, true);
166            
167            List<Page> children = firstLevelCache.keySet().stream()
168                    .map(firstLevelCode -> _findFirstLevelValueEntry(firstLevelValues, firstLevelCode).orElse(null))
169                    .filter(Objects::nonNull)
170                    .map(entry -> _toFirstLevelPage(rootPage, entry))
171                    .collect(Collectors.toList());
172            
173            return new CollectionIterable<>(children);
174        }
175    }
176
177    @Override
178    public boolean hasChild(JCRAmetysObject parent, String childName)
179    {
180        if (!(parent instanceof Page))
181        {
182            throw new IllegalArgumentException("The holder of the ODF virtual pages should be a page.");
183        }
184        
185        Page rootPage = (Page) parent;
186        String level1Metadata = _odfPageHandler.getLevel1Metadata(rootPage);
187        if (StringUtils.isBlank(level1Metadata))
188        {
189            Optional<Program> program = _pageCache.getProgramFromPageName(rootPage, null, null, childName);
190            return program.isPresent();
191        }
192        else
193        {
194            return _hasFirstLevelValue(rootPage, childName);
195        }
196    }
197    
198    private boolean _hasFirstLevelValue(Page rootPage, String level1Code) throws AmetysRepositoryException
199    {
200        // FIXME should use page cache, because can return true when page does
201        // not exist. Maybe done like this for performance reasons?
202        Map<String, String> firstLevelValues = _odfPageHandler.getLevel1Values(rootPage);
203        return firstLevelValues.containsKey(level1Code);
204    }
205}