001/*
002 *  Copyright 2024 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.pagesubscription.dao;
017
018import java.util.List;
019import java.util.Map;
020
021import org.apache.avalon.framework.component.Component;
022import org.apache.avalon.framework.context.Context;
023import org.apache.avalon.framework.context.ContextException;
024import org.apache.avalon.framework.context.Contextualizable;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.avalon.framework.service.Serviceable;
028import org.apache.cocoon.components.ContextHelper;
029import org.apache.cocoon.environment.Request;
030import org.apache.commons.lang3.StringUtils;
031
032import org.ametys.cms.fo.ForceDefaultRepositoryWorkspaceCallableDecorator;
033import org.ametys.core.ui.Callable;
034import org.ametys.core.user.CurrentUserProvider;
035import org.ametys.core.user.UserIdentity;
036import org.ametys.plugins.pagesubscription.BroadcastChannelHelper.BroadcastChannel;
037import org.ametys.plugins.pagesubscription.FrequencyHelper.Frequency;
038import org.ametys.plugins.pagesubscription.Subscription;
039import org.ametys.plugins.pagesubscription.SubscriptionException;
040import org.ametys.plugins.pagesubscription.SubscriptionException.Type;
041import org.ametys.plugins.pagesubscription.type.SubscriptionType;
042import org.ametys.plugins.pagesubscription.type.SubscriptionType.FrequencyTiming;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044import org.ametys.plugins.repository.RepositoryConstants;
045import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
046import org.ametys.runtime.plugin.component.AbstractLogEnabled;
047
048/**
049 * Abstract DAO for subscriptions
050 */
051public abstract class AbstractSubscriptionsDAO extends AbstractLogEnabled implements Component, Serviceable, Contextualizable
052{
053    /** The ametys object resolver */
054    protected AmetysObjectResolver _resolver;
055    
056    /** The current user provider */
057    protected CurrentUserProvider _currentUserProvider;
058    
059    /** the Avalon context */
060    protected Context _context;
061    
062    public void service(ServiceManager smanager) throws ServiceException
063    {
064        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
065        _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
066    }
067    
068    public void contextualize(Context context) throws ContextException
069    {
070        _context = context;
071    }
072    
073    private void _checkSubscription(Subscription subscription)
074    {
075        UserIdentity user = _currentUserProvider.getUser();
076        boolean isSubscriber = subscription.getSubscriber()
077            .filter(u -> u.equals(user))
078            .isPresent();
079        if (!isSubscriber)
080        {
081            throw new SubscriptionException("Can access to the subscription with id '" + subscription.getId() + "' because the subscriber is not the current user.", Type.NO_ACCESS);
082        }
083    }
084    
085    /**
086     * Get the current user subscription from the given id
087     * @param subscriptionId the subscription id
088     * @return the subscription as JSON
089     */
090    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION, decorators = ForceDefaultRepositoryWorkspaceCallableDecorator.DECORATOR_ID)
091    public Map<String, Object> getSubscription(String subscriptionId)
092    {
093        return getSubscription(subscriptionId, true);
094    }
095    
096    /**
097     * Get the subscription from the given id
098     * @param subscriptionId the subscription id
099     * @param checkCurrentUser <code>true</code> to check if the subscription belong to the current user
100     * @return the subscription as JSON
101     */
102    public Map<String, Object> getSubscription(String subscriptionId, boolean checkCurrentUser)
103    {
104        try
105        {
106            Subscription subscription = _resolver.resolveById(subscriptionId);
107            if (checkCurrentUser)
108            {
109                _checkSubscription(subscription);
110            }
111            
112            return Map.of("success", true, "subscription", subscription.getSubscriptionType().subscriptionToJSON(subscription));
113        }
114        catch (Exception e)
115        {
116            getLogger().error("Fail to get subscription with id '{}'", subscriptionId, e);
117            return Map.of("success", false);
118        }
119    }
120    
121    /**
122     * Edit the current user subscription with the given id
123     * @param subscriptionId the subscription id
124     * @param frequency the frequency
125     * @param broadcastChannels the broadcast channels
126     * @return the subscription as JSON
127     */
128    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION, decorators = ForceDefaultRepositoryWorkspaceCallableDecorator.DECORATOR_ID)
129    public Map<String, Object> editSubscription(String subscriptionId, String frequency, List<String> broadcastChannels)
130    {
131        return editSubscription(subscriptionId, frequency, null, broadcastChannels, true);
132    }
133    
134    /**
135     * Edit the subscription with the given id
136     * @param subscriptionId the subscription id
137     * @param frequency the frequency
138     * @param forcedTiming the force frequency timing. Can be null if not forced
139     * @param broadcastChannels the broadcast channels
140     * @param checkCurrentUser <code>true</code> to check if the subscription belong to the current user
141     * @return a Map with success status and the subscription as JSON
142     */
143    @SuppressWarnings("unchecked")
144    public Map<String, Object> editSubscription(String subscriptionId, String frequency, FrequencyTiming forcedTiming, List<String> broadcastChannels, boolean checkCurrentUser)
145    {
146        Request request = ContextHelper.getRequest(_context);
147        // Retrieve the current workspace.
148        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
149        
150        try
151        {
152            // Force default the workspace.
153            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE);
154            
155            Subscription subscription = _resolver.resolveById(subscriptionId);
156            if (checkCurrentUser)
157            {
158                _checkSubscription(subscription);
159            }
160            
161            SubscriptionType subscriptionType = subscription.getSubscriptionType();
162            
163            Frequency subFrequency = StringUtils.isNotBlank(frequency)
164                    ? Frequency.valueOf(frequency)
165                    : subscription.getFrequency();
166            
167            List<BroadcastChannel> subChannels = broadcastChannels != null
168                    ? broadcastChannels.stream()
169                            .map(BroadcastChannel::valueOf)
170                            .toList()
171                    : subscription.getBroadcastChannels();
172            
173            if (forcedTiming != null)
174            {
175                subscriptionType.editForceSubscription(subscription, subFrequency, subChannels, forcedTiming, null);
176            }
177            else
178            {
179                subscriptionType.editSubscription(subscription, subFrequency, subChannels, null);
180            }
181            
182            return Map.of("success", true, "subscription", subscriptionType.subscriptionToJSON(subscription));
183        }
184        catch (Exception e)
185        {
186            getLogger().error("Unable to edit subscription with id '{}'", subscriptionId, e);
187            return Map.of("success", false);
188        }
189        finally
190        {
191            // Restore current workspace
192            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
193        }
194    }
195    
196    /**
197     * Remove the current user subscription with the given id
198     * @param subscriptionId the subscription id
199     * @return true if successfully subscribe, false otherwise
200     * @throws Exception if an error occurred
201     */
202    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION, decorators = ForceDefaultRepositoryWorkspaceCallableDecorator.DECORATOR_ID)
203    public boolean unsubscribe(String subscriptionId) throws Exception
204    {
205        return unsubscribe(subscriptionId, true);
206    }
207    
208    /**
209     * Remove the subscription with the given id
210     * @param subscriptionId the subscription id
211     * @return true if successfully subscribe, false otherwise
212     * @param checkCurrentUser <code>true</code> to check if the subscription belong to the current user
213     * @throws Exception if an error occurred
214     */
215    public boolean unsubscribe(String subscriptionId, boolean checkCurrentUser) throws Exception
216    {
217        Request request = ContextHelper.getRequest(_context);
218        // Retrieve the current workspace.
219        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
220        
221        try
222        {
223            // Force default the workspace.
224            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE);
225            
226            Subscription subscription = _resolver.resolveById(subscriptionId);
227            if (checkCurrentUser)
228            {
229                _checkSubscription(subscription);
230            }
231            
232            subscription.getSubscriptionType().unsubscribe(subscription);
233            return true;
234        }
235        catch (Exception e)
236        {
237            getLogger().error("Fail to unsubscribe to suscription with id {}", subscriptionId, e);
238            return false;
239        }
240        finally
241        {
242            // Restore current workspace
243            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
244        }
245    }
246}