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