001/*
002 *  Copyright 2017 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.restrictions;
017
018import java.io.File;
019import java.io.FileInputStream;
020import java.io.InputStream;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027
028import org.apache.avalon.framework.activity.Disposable;
029import org.apache.avalon.framework.component.Component;
030import org.apache.avalon.framework.configuration.Configuration;
031import org.apache.avalon.framework.configuration.ConfigurationException;
032import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
033import org.apache.avalon.framework.context.Context;
034import org.apache.avalon.framework.context.ContextException;
035import org.apache.avalon.framework.context.Contextualizable;
036import org.apache.avalon.framework.service.ServiceException;
037import org.apache.avalon.framework.service.ServiceManager;
038import org.apache.avalon.framework.service.Serviceable;
039import org.apache.cocoon.Constants;
040import org.apache.cocoon.components.ContextHelper;
041import org.apache.cocoon.environment.Request;
042import org.apache.commons.lang.StringUtils;
043
044import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
045import org.ametys.core.ui.Callable;
046import org.ametys.odf.orgunit.OrgUnit;
047import org.ametys.odf.orgunit.RootOrgUnitProvider;
048import org.ametys.plugins.odfweb.restrictions.rules.OdfAndRestrictionRule;
049import org.ametys.plugins.odfweb.restrictions.rules.OdfMetadataRestrictionRule;
050import org.ametys.plugins.odfweb.restrictions.rules.OdfNotRestrictionRule;
051import org.ametys.plugins.odfweb.restrictions.rules.OdfOrRestrictionRule;
052import org.ametys.plugins.odfweb.restrictions.rules.OdfOrgunitRestrictionRule;
053import org.ametys.plugins.odfweb.restrictions.rules.OdfRestrictionRule;
054import org.ametys.plugins.repository.AmetysObjectResolver;
055import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
056import org.ametys.runtime.i18n.I18nizableText;
057import org.ametys.runtime.plugin.component.AbstractLogEnabled;
058import org.ametys.web.repository.page.Page;
059import org.ametys.web.repository.site.Site;
060import org.ametys.web.repository.site.SiteManager;
061
062/**
063 * Component able to handle program restrictions related to the "odf-restrictions" site parameter.
064 */
065public class OdfProgramRestrictionManager extends AbstractLogEnabled implements Component, Serviceable, Contextualizable, Disposable
066{
067    /** The avalon role. */
068    public static final String ROLE = OdfProgramRestrictionManager.class.getName();
069    
070    /** Site Manager */
071    protected SiteManager _siteManager;
072    
073    /** The content type extension point. */
074    protected ContentTypeExtensionPoint _cTypeEP;
075    
076    /** Ametys object resolver */
077    protected AmetysObjectResolver _resolver;
078    
079    /** Root orgunit provider */
080    protected RootOrgUnitProvider _rootOrgUnitProvider;
081    
082    /** Cocoon context */
083    protected org.apache.cocoon.environment.Context _cocoonContext;
084    
085    /** The available odf restrictions */
086    protected Map<String, OdfProgramRestriction> _restrictions;
087    
088    private Context _context;
089    
090    @Override
091    public void service(ServiceManager serviceManager) throws ServiceException
092    {
093        _siteManager = (SiteManager) serviceManager.lookup(SiteManager.ROLE);
094        _cTypeEP = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE);
095        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
096        _rootOrgUnitProvider = (RootOrgUnitProvider) serviceManager.lookup(RootOrgUnitProvider.ROLE);
097    }
098    
099    @Override
100    public void contextualize(Context context) throws ContextException
101    {
102        _context = context;
103        _cocoonContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
104    }
105    
106    @Override
107    public void dispose()
108    {
109        _restrictions = null;
110    }
111    
112    /**
113     * Retrieves the available restrictions
114     * @return The map of restriction, where keys are restriction ids. 
115     */
116    public Map<String, OdfProgramRestriction> getRestrictions()
117    {
118        // Lazy initialize restrictions because orgunit restrictions cannot be
119        // resolved during the initialization of the component
120        if (_restrictions == null)
121        {
122            _readRestrictionConfigurationFile();
123        }
124        
125        return _restrictions;
126    }
127    
128    /**
129     * Get the ODF restriction for the given ODF root page
130     * @param odfRootPage The ODF root page
131     * @return The restriction or <code>null</code>
132     */
133    public OdfProgramRestriction getRestriction(Page odfRootPage)
134    {
135        Site site = odfRootPage.getSite();
136        String restrictionId = site.getValue("odf-restrictions");
137        if (StringUtils.isNotEmpty(restrictionId))
138        {
139            return getRestrictions().get(restrictionId);
140        }
141        
142        return null;
143    }
144    
145    /**
146     * Indicate is there is any restriction for this root page
147     * @param rootPage odf root page
148     * @return true if it is the case
149     */
150    public boolean hasRestrictions(Page rootPage)
151    {
152        Site site = rootPage.getSite();
153        String restrictionId = site.getValue("odf-restrictions");
154        return StringUtils.isNotEmpty(restrictionId) ? getRestrictions().containsKey(restrictionId) : false;
155    }
156    
157    /**
158     * Indicate is there is any restriction related to orgunit for this root page
159     * @param rootPage odf root page
160     * @return true if it is the case
161     */
162    public boolean hasOrgunitRestrictions(Page rootPage)
163    {
164        Site site = rootPage.getSite();
165        String restrictionId = site.getValue("odf-restrictions");
166        if (StringUtils.isNotEmpty(restrictionId))
167        {
168            OdfProgramRestriction odfProgramRestriction = getRestrictions().get(restrictionId);
169            return odfProgramRestriction.hasOrgunitRestrictions();
170        }
171        
172        return false;
173    }
174    
175    /**
176     * Get the id of the restriction root orgunit
177     * @param siteName the site name
178     * @return the id of the restriction root orgunit
179     */
180    @Callable
181    public String getRestrictionRootOrgUnitId(String siteName)
182    {
183        String odfRootId = null;
184        
185        Site site = _siteManager.getSite(siteName);
186        String restrictionId = site.getValue("odf-restrictions");
187        if (StringUtils.isNotEmpty(restrictionId))
188        {
189            OdfProgramRestriction odfProgramRestriction = getRestrictions().get(restrictionId);
190            if (odfProgramRestriction != null && odfProgramRestriction.hasOrgunitRestrictions())
191            {
192                odfRootId = odfProgramRestriction.getId();
193            }
194        }
195        
196        if (StringUtils.isBlank(odfRootId))
197        {
198            odfRootId = _rootOrgUnitProvider.getRootId();
199        }
200        
201        return odfRootId;
202    }
203    
204    /**
205     * Configured the restrictions from the XML configuration file
206     */
207    protected void _readRestrictionConfigurationFile()
208    {
209        _restrictions = new HashMap<>();
210        
211        File restrictionFile = new File (_cocoonContext.getRealPath("/WEB-INF/param/odf-restrictions.xml"));
212        
213        if (restrictionFile.exists())
214        {
215            try (InputStream is = new FileInputStream(restrictionFile);)
216            {
217                Configuration cfg = new DefaultConfigurationBuilder().build(is);
218                
219                Configuration[] restrictionConfs = cfg.getChildren("restriction");
220                for (Configuration restrictionConf : restrictionConfs)
221                {
222                    String id = restrictionConf.getAttribute("id");
223                    I18nizableText label = _configureI18nizableText(restrictionConf, "label", "", "application");
224                    List<OdfRestrictionRule> rules = _configureRestrictionRules(restrictionConf.getChild("rules"));
225                    
226                    _restrictions.put(id, new OdfProgramRestriction(id, label, rules));
227                }
228                
229                Configuration orgunitConf = cfg.getChild("orgunits", false);
230                if (orgunitConf != null)
231                {
232                    _addDefaultOrgunitRestrictions();
233                }
234            }
235            catch (Exception e)
236            {
237                getLogger().error("Cannot read the configuration file located at /WEB-INF/param/odf-restrictions.xml. Reverting to default.", e);
238                _restrictions.clear();
239                _addDefaultOrgunitRestrictions();
240            }
241        }
242        else        
243        {
244            _addDefaultOrgunitRestrictions();
245        }
246    }
247    
248    private I18nizableText _configureI18nizableText(Configuration config, String name, String defaultValue, String defaultCatalog)
249    {
250        Configuration textConfig = config.getChild(name);
251        boolean i18nSupported = textConfig.getAttributeAsBoolean("i18n", false);
252        String text = textConfig.getValue(defaultValue);
253        
254        if (i18nSupported)
255        {
256            String catalogue = textConfig.getAttribute("catalogue", defaultCatalog);
257            return new I18nizableText(catalogue, text);
258        }
259        else
260        {
261            return new I18nizableText(text);
262        }
263    }
264    
265    /**
266     * Create a list of rules from configuration nodes
267     * @param rulesConf Array of configuration node that represents the set of rules 
268     * @return list of rules
269     * @throws ConfigurationException if a configuration error is encountered
270     */
271    protected List<OdfRestrictionRule> _configureRestrictionRules(Configuration rulesConf) throws ConfigurationException
272    {
273        List<OdfRestrictionRule> rules = new ArrayList<>();
274        
275        for (Configuration ruleConf : rulesConf.getChildren())
276        {
277            rules.add(_configureRestrictionRule(ruleConf));
278        }
279        
280        return rules;
281    }
282    
283    /**
284     * Configure a restriction rule from a configuration node
285     * @param ruleConf The configuration node representing the rule
286     * @return The odf restriction rule
287     * @throws ConfigurationException if a configuration error is encountered
288     */
289    protected OdfRestrictionRule _configureRestrictionRule(Configuration ruleConf) throws ConfigurationException
290    {
291        String name = ruleConf.getName();
292        
293        if ("metadata".equals(name))
294        {
295            String metadataPath = ruleConf.getAttribute("name");
296            String metadataValue = ruleConf.getAttribute("value");
297            return new OdfMetadataRestrictionRule(metadataPath, metadataValue);
298        }
299        else if ("orgunit".equals(name))
300        {
301            String orgunitId = ruleConf.getAttribute("id", null);
302            
303            if (StringUtils.isEmpty(orgunitId))
304            {
305                throw new ConfigurationException("Expecting 'id' attribute for orgunit restriction rule.");
306            }
307            
308            return new OdfOrgunitRestrictionRule(_rootOrgUnitProvider, orgunitId);
309        }
310        else if ("and".equals(name))
311        {
312            List<OdfRestrictionRule> childRules = _configureRestrictionRules(ruleConf);
313            return new OdfAndRestrictionRule(childRules);
314        }
315        else if ("or".equals(name))
316        {
317            List<OdfRestrictionRule> childRules = _configureRestrictionRules(ruleConf);
318            return new OdfOrRestrictionRule(childRules);
319        }
320        else if ("not".equals(name))
321        {
322            List<OdfRestrictionRule> childRules = _configureRestrictionRules(ruleConf);
323            return new OdfNotRestrictionRule(childRules);
324        }
325        
326        throw new ConfigurationException("Unknow node name in restriction configuration : " + name);
327    }
328    
329    private void _addDefaultOrgunitRestrictions()
330    {
331        Request request = ContextHelper.getRequest(_context);
332        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
333        
334        try
335        {
336            // Force default workspace
337            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, null);
338            
339            String rootOrgunitId = _rootOrgUnitProvider.getRootId();
340            Set<String> orgunitIds = _rootOrgUnitProvider.getChildOrgUnitIds(rootOrgunitId, true);
341            orgunitIds.add(rootOrgunitId);
342            
343            for (String id : orgunitIds)
344            {
345                if (!StringUtils.equals(id, rootOrgunitId))
346                {
347                    OrgUnit orgunit = _resolver.resolveById(id);
348                    
349                    OdfRestrictionRule rule = new OdfOrgunitRestrictionRule(_rootOrgUnitProvider, id);
350                    OdfProgramRestriction restriction = new OdfProgramRestriction(id, new I18nizableText(orgunit.getTitle()), Collections.singletonList(rule));
351                    
352                    _restrictions.put(id, restriction);
353                }
354            }
355        }
356        finally
357        {
358            // Restore workspace
359            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
360        }
361    }
362}