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>
142     */
143    public OdfProgramRestriction getRestriction(Page odfRootPage)
144    {
145        Site site = odfRootPage.getSite();
146        String restrictionId = site.getValue("odf-restrictions");
147        if (StringUtils.isNotEmpty(restrictionId))
148        {
149            return getRestrictions().get(restrictionId);
150        }
151        
152        return null;
153    }
154    
155    /**
156     * Indicate is there is any restriction for this root page
157     * @param rootPage odf root page
158     * @return true if it is the case
159     */
160    public boolean hasRestrictions(Page rootPage)
161    {
162        Site site = rootPage.getSite();
163        String restrictionId = site.getValue("odf-restrictions");
164        return StringUtils.isNotEmpty(restrictionId) ? getRestrictions().containsKey(restrictionId) : false;
165    }
166    
167    /**
168     * Indicate is there is any restriction related to orgunit for this root page
169     * @param rootPage odf root page
170     * @return true if it is the case
171     */
172    public boolean hasOrgunitRestrictions(Page rootPage)
173    {
174        Site site = rootPage.getSite();
175        String restrictionId = site.getValue("odf-restrictions");
176        if (StringUtils.isNotEmpty(restrictionId))
177        {
178            OdfProgramRestriction odfProgramRestriction = getRestrictions().get(restrictionId);
179            return odfProgramRestriction.hasOrgunitRestrictions();
180        }
181        
182        return false;
183    }
184    
185    /**
186     * Get the id of the restriction root orgunit
187     * @param siteName the site name
188     * @return the id of the restriction root orgunit
189     */
190    @Callable
191    public String getRestrictionRootOrgUnitId(String siteName)
192    {
193        String odfRootId = null;
194        
195        Site site = _siteManager.getSite(siteName);
196        String restrictionId = site.getValue("odf-restrictions");
197        if (StringUtils.isNotEmpty(restrictionId))
198        {
199            OdfProgramRestriction odfProgramRestriction = getRestrictions().get(restrictionId);
200            if (odfProgramRestriction != null && odfProgramRestriction.hasOrgunitRestrictions())
201            {
202                odfRootId = odfProgramRestriction.getId();
203            }
204        }
205        
206        if (StringUtils.isBlank(odfRootId))
207        {
208            odfRootId = _rootOrgUnitProvider.getRootId();
209        }
210        
211        return odfRootId;
212    }
213    
214    /**
215     * Configured the restrictions from the XML configuration file
216     */
217    protected void _readRestrictionConfigurationFile()
218    {
219        _restrictions = new HashMap<>();
220        
221        File restrictionFile = new File (_cocoonContext.getRealPath("/WEB-INF/param/odf-restrictions.xml"));
222        
223        if (restrictionFile.exists())
224        {
225            try (InputStream is = new FileInputStream(restrictionFile);)
226            {
227                Configuration cfg = new DefaultConfigurationBuilder().build(is);
228                
229                Configuration[] restrictionConfs = cfg.getChildren("restriction");
230                for (Configuration restrictionConf : restrictionConfs)
231                {
232                    String id = restrictionConf.getAttribute("id");
233                    I18nizableText label = _configureI18nizableText(restrictionConf, "label", "", "application");
234                    List<OdfRestrictionRule> rules = _configureRestrictionRules(restrictionConf.getChild("rules"));
235                    
236                    _restrictions.put(id, new OdfProgramRestriction(id, label, rules));
237                }
238                
239                Configuration orgunitConf = cfg.getChild("orgunits", false);
240                if (orgunitConf != null)
241                {
242                    _addDefaultOrgunitRestrictions();
243                }
244            }
245            catch (Exception e)
246            {
247                getLogger().error("Cannot read the configuration file located at /WEB-INF/param/odf-restrictions.xml. Reverting to default.", e);
248                _restrictions.clear();
249                _addDefaultOrgunitRestrictions();
250            }
251        }
252        else        
253        {
254            _addDefaultOrgunitRestrictions();
255        }
256    }
257    
258    private I18nizableText _configureI18nizableText(Configuration config, String name, String defaultValue, String defaultCatalog)
259    {
260        Configuration textConfig = config.getChild(name);
261        boolean i18nSupported = textConfig.getAttributeAsBoolean("i18n", false);
262        String text = textConfig.getValue(defaultValue);
263        
264        if (i18nSupported)
265        {
266            String catalogue = textConfig.getAttribute("catalogue", defaultCatalog);
267            return new I18nizableText(catalogue, text);
268        }
269        else
270        {
271            return new I18nizableText(text);
272        }
273    }
274    
275    /**
276     * Create a list of rules from configuration nodes
277     * @param rulesConf Array of configuration node that represents the set of rules 
278     * @return list of rules
279     * @throws ConfigurationException if a configuration error is encountered
280     */
281    protected List<OdfRestrictionRule> _configureRestrictionRules(Configuration rulesConf) throws ConfigurationException
282    {
283        List<OdfRestrictionRule> rules = new ArrayList<>();
284        
285        for (Configuration ruleConf : rulesConf.getChildren())
286        {
287            rules.add(_configureRestrictionRule(ruleConf));
288        }
289        
290        return rules;
291    }
292    
293    /**
294     * Configure a restriction rule from a configuration node
295     * @param ruleConf The configuration node representing the rule
296     * @return The odf restriction rule
297     * @throws ConfigurationException if a configuration error is encountered
298     */
299    protected OdfRestrictionRule _configureRestrictionRule(Configuration ruleConf) throws ConfigurationException
300    {
301        String name = ruleConf.getName();
302        
303        if ("item".equals(name))
304        {
305            String attributePath = ruleConf.getAttribute("ref");
306            String attributeValue = ruleConf.getAttribute("value");
307            
308            ContentType programContentType = _cTypeEP.getExtension(ProgramFactory.PROGRAM_CONTENT_TYPE);
309            if (programContentType.hasModelItem(attributePath))
310            {
311                ModelItem modelItem = programContentType.getModelItem(attributePath);
312                if (modelItem instanceof ContentElementDefinition def && _odfReferenceTableHelper.isTableReference(def.getContentTypeId()))
313                {
314                    // If the attribute value is the table reference code, convert it and test the JCR id
315                    OdfReferenceTableEntry itemFromCode = _odfReferenceTableHelper.getItemFromCode(def.getContentTypeId(), attributeValue);
316                    if (itemFromCode != null)
317                    {
318                        return new OdfAttributeRestrictionRule(attributePath, itemFromCode.getId());
319                    }
320                }
321                
322                return new OdfAttributeRestrictionRule(attributePath, attributeValue);
323            }
324            
325            throw new ConfigurationException("Attribute path '" + attributePath + "' is unknown for program content type.");
326        }
327        else if ("orgunit".equals(name))
328        {
329            String orgunitId = ruleConf.getAttribute("id", null);
330            
331            if (StringUtils.isEmpty(orgunitId))
332            {
333                throw new ConfigurationException("Expecting 'id' attribute for orgunit restriction rule.");
334            }
335            
336            return new OdfOrgunitRestrictionRule(_rootOrgUnitProvider, orgunitId);
337        }
338        else if ("and".equals(name))
339        {
340            List<OdfRestrictionRule> childRules = _configureRestrictionRules(ruleConf);
341            return new OdfAndRestrictionRule(childRules);
342        }
343        else if ("or".equals(name))
344        {
345            List<OdfRestrictionRule> childRules = _configureRestrictionRules(ruleConf);
346            return new OdfOrRestrictionRule(childRules);
347        }
348        else if ("not".equals(name))
349        {
350            List<OdfRestrictionRule> childRules = _configureRestrictionRules(ruleConf);
351            return new OdfNotRestrictionRule(childRules);
352        }
353        
354        throw new ConfigurationException("Unknow node name in restriction configuration : " + name);
355    }
356    
357    private void _addDefaultOrgunitRestrictions()
358    {
359        Request request = ContextHelper.getRequest(_context);
360        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
361        
362        try
363        {
364            // Force default workspace
365            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, null);
366            
367            String rootOrgunitId = _rootOrgUnitProvider.getRootId();
368            Set<String> orgunitIds = _rootOrgUnitProvider.getChildOrgUnitIds(rootOrgunitId, true);
369            orgunitIds.add(rootOrgunitId);
370            
371            for (String id : orgunitIds)
372            {
373                if (!StringUtils.equals(id, rootOrgunitId))
374                {
375                    OrgUnit orgunit = _resolver.resolveById(id);
376                    
377                    OdfRestrictionRule rule = new OdfOrgunitRestrictionRule(_rootOrgUnitProvider, id);
378                    OdfProgramRestriction restriction = new OdfProgramRestriction(id, new I18nizableText(orgunit.getTitle()), Collections.singletonList(rule));
379                    
380                    _restrictions.put(id, restriction);
381                }
382            }
383        }
384        finally
385        {
386            // Restore workspace
387            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
388        }
389    }
390}