001/*
002 *  Copyright 2021 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.forms.actions;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Optional;
023import java.util.function.Predicate;
024import java.util.stream.Stream;
025
026import org.apache.avalon.framework.parameters.Parameters;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.cocoon.acting.ServiceableAction;
030import org.apache.cocoon.environment.ObjectModelHelper;
031import org.apache.cocoon.environment.Redirector;
032import org.apache.cocoon.environment.Request;
033import org.apache.cocoon.environment.SourceResolver;
034import org.apache.commons.lang3.StringUtils;
035import org.apache.commons.lang3.tuple.Pair;
036
037import org.ametys.core.cocoon.JSonReader;
038import org.ametys.core.user.CurrentUserProvider;
039import org.ametys.core.user.UserIdentity;
040import org.ametys.plugins.forms.dao.FormDAO;
041import org.ametys.plugins.forms.dao.FormDirectoryDAO;
042import org.ametys.plugins.forms.repository.Form;
043import org.ametys.plugins.forms.repository.Form.FormProfile;
044import org.ametys.plugins.forms.repository.FormDirectory;
045import org.ametys.plugins.repository.AmetysObjectIterable;
046import org.ametys.plugins.repository.AmetysObjectResolver;
047
048import com.google.common.base.Predicates;
049
050
051/**
052 * Get forms action
053 */
054public class GetFormsAction extends ServiceableAction
055{
056    /** The current user provider */
057    protected CurrentUserProvider _userProvider;
058    
059    /** The Ametys object resolver */
060    protected AmetysObjectResolver _resolver;
061    
062    /** DAO for manipulating form directory */
063    protected FormDirectoryDAO _formDirectoryDAO;
064    
065    /** DAO for manipulating form */
066    protected FormDAO _formDAO;
067    
068    @Override
069    public void service(ServiceManager serviceManager) throws ServiceException
070    {
071        super.service(serviceManager);
072        _userProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
073        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
074        _formDirectoryDAO = (FormDirectoryDAO) serviceManager.lookup(FormDirectoryDAO.ROLE);
075        _formDAO = (FormDAO) serviceManager.lookup(FormDAO.ROLE);
076    }
077    
078    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
079    {
080        @SuppressWarnings("unchecked")
081        Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
082        String profileId = StringUtils.defaultIfEmpty((String) jsParameters.get("profile"), "read_access");
083        
084        if (!"read_access".equals(profileId) && !"write_access".equals(profileId) && !"right_access".equals(profileId))
085        {
086            throw new IllegalArgumentException("Unexpected profile identifier : " + profileId);
087        }
088        
089        FormProfile profile = FormProfile.valueOf(profileId.toUpperCase());
090        FormDirectory parentDirectory = _getParentDirectory(jsParameters);
091        Boolean onlyDirectories = (Boolean) jsParameters.getOrDefault("onlyDirectories", Boolean.FALSE);
092        Boolean onlyConfiguredForm = (Boolean) jsParameters.getOrDefault("onlyConfiguredForm", Boolean.FALSE);
093        Boolean onlyForms = onlyDirectories ? false : (Boolean) jsParameters.getOrDefault("onlyForms", Boolean.FALSE);
094        Boolean testIfEmptyDirectory = (Boolean) jsParameters.getOrDefault("testIfEmptyDirectory", Boolean.FALSE);
095        Boolean allDescendants = (Boolean) jsParameters.getOrDefault("allDescendants", Boolean.FALSE);
096        
097        UserIdentity user = _userProvider.getUser();
098        
099        List<Map<String, Object>> nodes = new ArrayList<>();
100        Stream<Form> childFormsStream = onlyDirectories
101                ? Stream.empty()
102                : _getChildForms(parentDirectory, !allDescendants, profile, user)
103                    .filter(f -> !onlyConfiguredForm || _formDAO.isFormConfigured(f));
104        childFormsStream
105                .map(_formDAO::getFormProperties)
106                .forEach(nodes::add);
107        
108        Stream<Pair<FormDirectory, AdditionalInfoOnDirectory>> childDirectoriesStream = onlyForms
109                ? Stream.empty()
110                : _getChildFormDirectories(parentDirectory, testIfEmptyDirectory, profile, user);
111        childDirectoriesStream
112                .forEach(p ->
113                {
114                    Map<String, Object> props = _formDirectoryDAO.getFormDirectoryProperties(p.getLeft());
115                    AdditionalInfoOnDirectory additionalInfo = p.getRight();
116                    additionalInfo.fillAdditionalInfo(props);
117                    switch (profile)
118                    {
119                        case WRITE_ACCESS:
120                            if ((boolean) props.get("displayForWrite"))
121                            {
122                                nodes.add(props);
123                            }
124                            break;
125                        case RIGHT_ACCESS:
126                            if ((boolean) props.get("displayForRights"))
127                            {
128                                nodes.add(props);
129                            }
130                            break;
131                        case READ_ACCESS:
132                        default:
133                            if ((boolean) props.get("displayForRead"))
134                            {
135                                nodes.add(props);
136                            }
137                            break;
138                    }
139                });
140        
141        Map<String, Object> result = new HashMap<>();
142        result.put("forms", nodes);
143        
144        Request request = ObjectModelHelper.getRequest(objectModel);
145        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
146        
147        return EMPTY_MAP;
148    }
149    
150    private FormDirectory _getParentDirectory(Map jsParameters)
151    {
152        Predicate<String> isNotRoot = Predicates.not(Predicates.equalTo(FormDirectoryDAO.ROOT_FORM_DIRECTORY_ID));
153        FormDirectory parent = Optional.of("node")
154            .map(jsParameters::get)
155            .filter(String.class::isInstance)
156            .map(String.class::cast)
157            .filter(isNotRoot)
158            .map(_resolver::<FormDirectory>resolveById)
159            .orElse(_formDirectoryDAO.getFormDirectoriesRootNode((String) jsParameters.get("siteName"))/* root node must exist, so it must be created if it does not exist yet */);
160        return parent;
161    }
162    
163    private Stream<Pair<FormDirectory, AdditionalInfoOnDirectory>> _getChildFormDirectories(FormDirectory parentDirectory, boolean testIfEmptyDirectory, FormProfile profile, UserIdentity user)
164    {
165        AmetysObjectIterable<FormDirectory> childFormDirectories = _formDirectoryDAO.getChildFormDirectories(parentDirectory);
166        
167        Stream<FormDirectory> childFormDirectoriesStream = childFormDirectories.stream();
168        if (testIfEmptyDirectory)
169        {
170            // indicates if directory do not have (not necessarily direct) form children (i.e. is empty)
171            return childFormDirectoriesStream
172                    .map(ct -> Pair.of(ct, new AdditionalInfoOnDirectory(_hasNoDescendant(ct, profile, user))));
173        }
174        else
175        {
176            return childFormDirectoriesStream
177                    .map(ct -> Pair.of(ct, new AdditionalInfoOnDirectory()));
178        }
179    }
180    
181    private Boolean _hasNoDescendant(FormDirectory formDirectory, FormProfile profile, UserIdentity user)
182    {
183        boolean onlyDirectChildren = false; // in order to test no only direct children, but all descendants
184        Stream<Form> childForms = _getChildForms(formDirectory, onlyDirectChildren, profile, user);
185        return !childForms.findAny().isPresent();
186    }
187    
188    private Stream<Form> _getChildForms(FormDirectory parentDirectory, boolean onlyDirectChildren, FormProfile profile, UserIdentity user)
189    {
190        Stream<Form> childForms;
191        switch (profile)
192        {
193            case WRITE_ACCESS:
194                childForms = _formDirectoryDAO.getChildFormsInWriteAccess(parentDirectory, onlyDirectChildren, user);
195                break;
196            case RIGHT_ACCESS:
197                childForms = _formDirectoryDAO.getChildFormsInRightAccess(parentDirectory, onlyDirectChildren, user);
198                break;
199            case READ_ACCESS:
200            default:
201                childForms = _formDirectoryDAO.getChildFormsInReadAccess(parentDirectory, onlyDirectChildren, user);
202                break;
203        }
204        
205        return childForms;
206    }
207    
208    private static class AdditionalInfoOnDirectory
209    {
210        private Optional<Boolean> _hasNoDescendant = Optional.empty();
211        
212        AdditionalInfoOnDirectory()
213        {
214        }
215        
216        AdditionalInfoOnDirectory(boolean hasNoDescendant)
217        {
218            _hasNoDescendant = Optional.of(hasNoDescendant);
219        }
220        
221        void fillAdditionalInfo(Map<String, Object> props)
222        {
223            _hasNoDescendant.ifPresent(b -> props.put("hasNoDescendantForm", b));
224        }
225    }
226}