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