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