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