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