001/*
002 *  Copyright 2016 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.repository.activities;
017
018import java.time.ZonedDateTime;
019import java.time.temporal.ChronoUnit;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.avalon.framework.configuration.Configurable;
025import org.apache.avalon.framework.configuration.Configuration;
026import org.apache.avalon.framework.configuration.ConfigurationException;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030
031import org.ametys.core.user.CurrentUserProvider;
032import org.ametys.core.user.UserIdentity;
033import org.ametys.plugins.core.user.UserHelper;
034import org.ametys.runtime.i18n.I18nizableText;
035import org.ametys.runtime.model.type.DataContext;
036import org.ametys.runtime.model.type.ElementType;
037import org.ametys.runtime.plugin.component.AbstractLogEnabled;
038import org.ametys.runtime.plugin.component.PluginAware;
039
040/**
041 * Default implementation for {@link ActivityType} storing activity in JCR
042 */
043public class DefaultActivityType extends AbstractLogEnabled implements Configurable, ActivityType, Serviceable, PluginAware
044{
045    /** The default time range to merge compatible activities : 5 minutes */
046    private static final int __MAX_MERGE_ACTIVITY_RANGE = 5 * 60;
047    
048    /** The current user provider */
049    protected CurrentUserProvider _currentUserProvider;
050    /** Helper to get users */
051    protected UserHelper _userHelper;
052    
053    private Map<String, I18nizableText> _supportedEventTypes;
054
055    private String _pluginName;
056    
057    public void setPluginInfo(String pluginName, String featureName, String id)
058    {
059        _pluginName = pluginName;
060    }
061    
062    @Override
063    public void configure(Configuration configuration) throws ConfigurationException
064    {
065        _supportedEventTypes = new HashMap<>();
066        
067        Configuration[] supportedTypesConf = configuration.getChild("supported-types").getChildren("supported-type");
068        if (supportedTypesConf.length == 0)
069        {
070            throw new ConfigurationException("Missing 'supported-types' configuration", configuration);
071        }
072        
073        for (Configuration conf : supportedTypesConf)
074        {
075            String id = conf.getAttribute("id");
076            I18nizableText label = I18nizableText.parseI18nizableText(conf.getChild("label"), "plugin." + _pluginName, id);
077            _supportedEventTypes.put(id, label);
078        }
079    }
080    
081    @Override   
082    public void service(ServiceManager serviceManager) throws ServiceException
083    {
084        _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
085        _userHelper = (UserHelper) serviceManager.lookup(UserHelper.ROLE);
086    }
087    
088    @Override
089    public Map<String, I18nizableText> getSupportedEventTypes()
090    {
091        return _supportedEventTypes;
092    }
093    
094    @Override
095    public boolean isMergeable (Activity activity1, Activity activity2)
096    {
097        if (activity1 == null || activity2 == null)
098        {
099            return false;
100        }
101        
102        String type1 = activity1.getEventType();
103        UserIdentity author1 = activity1.getAuthor();
104        ZonedDateTime date1 = activity1.getDate();
105        
106        if (type1 == null || author1 == null || date1 == null)
107        {
108            getLogger().error("Mandatory type, date or author is missing for activity '{}'. Consider it as no mergeable", activity1.getId());
109            return false;
110        }
111        
112        String type2 = activity2.getEventType();
113        UserIdentity author2 = activity2.getAuthor();
114        ZonedDateTime date2 = activity2.getDate();
115        
116        if (type2 == null || author2 == null || date2 == null)
117        {
118            getLogger().error("Mandatory type, date or author is missing for activity '{}'. Consider it as no mergeable", activity2.getId());
119            return false;
120        }
121        
122        return type1.equals(type2) && author1.equals(author2) && Math.abs(ChronoUnit.SECONDS.between(date1, date2)) < __MAX_MERGE_ACTIVITY_RANGE;
123    }
124    
125    @Override
126    public Map<String, Object> mergeActivities(List<Activity> activities)
127    {
128        Map<String, Object> mergedActivity = activities.get(0).toJSONForClient();
129        
130        mergedActivity.put("amount", activities.size());
131        
132        // Date range 
133        if (activities.size() > 1)
134        {
135            ZonedDateTime startDate = null;
136            ZonedDateTime endDate = null;
137            
138            for (Activity activity : activities)
139            {
140                ZonedDateTime activityDate = activity.getDate();
141                if (startDate == null || activityDate.isBefore(startDate))
142                {
143                    startDate = activityDate;
144                }
145                
146                if (endDate == null || activityDate.isAfter(endDate))
147                {
148                    endDate = activityDate;
149                }
150            }
151            DataContext dataContext = DataContext.newInstance();
152            ElementType dateElementType = (ElementType) activities.get(0).getType(ActivityFactory.DATE);
153            mergedActivity.put(ActivityFactory.DATE, dateElementType.valueToJSONForClient(startDate, dataContext));
154            
155            if (endDate != null
156                && startDate != null
157                && ChronoUnit.SECONDS.between(startDate, endDate) > 1)
158            {
159                // Ignore end date if events are less than 1 minute apart 
160                mergedActivity.put("endDate", dateElementType.valueToJSONForClient(endDate, dataContext));
161            }
162        }
163        
164        return mergedActivity;
165    }
166}