001/*
002 *  Copyright 2014 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.explorer.calendars.jcr;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.Calendar;
021import java.util.Date;
022import java.util.GregorianCalendar;
023import java.util.List;
024
025import javax.jcr.Node;
026import javax.jcr.RepositoryException;
027
028import org.ametys.core.user.UserIdentity;
029import org.ametys.plugins.explorer.calendars.CalendarEvent;
030import org.ametys.plugins.explorer.calendars.EventRecurrenceTypeEnum;
031import org.ametys.plugins.explorer.calendars.ModifiableCalendarEvent;
032import org.ametys.plugins.explorer.calendars.helper.RecurrentEventHelper;
033import org.ametys.plugins.repository.AmetysObject;
034import org.ametys.plugins.repository.AmetysRepositoryException;
035import org.ametys.plugins.repository.RepositoryConstants;
036import org.ametys.plugins.repository.jcr.DefaultAmetysObject;
037import org.ametys.plugins.repository.metadata.CompositeMetadata;
038import org.ametys.plugins.repository.metadata.UnknownMetadataException;
039
040/**
041 * Default implementation of an {@link CalendarEvent}, backed by a JCR node.<br>
042 */
043public class JCRCalendarEvent extends DefaultAmetysObject<JCRCalendarEventFactory> implements ModifiableCalendarEvent
044{
045    /** Metadata's name for author's event* */
046    public static final String METADATA_CREATOR = "creator";
047    
048    /** Metadata's name for lastModified's event* */
049    public static final String METADATA_CREATION = "creationDate";
050
051    /** Metadata's name for last contributor's event* */
052    public static final String METADATA_CONTRIBUTOR = "contributor";
053    
054    /** Metadata's name for lastModified's event* */
055    public static final String METADATA_MODIFIED = "lastModified";
056    
057    /** Metadata's name for title's event* */
058    public static final String METADATA_TITLE = "title";
059    
060    /** Metadata's name for description's event* */
061    public static final String METADATA_DESC = "description";
062    
063    /** Metadata's name for keywords' event* */
064    public static final String METADATA_KEYWORDS = "keywords";
065    
066    /** Metadata's name for location's event* */
067    public static final String METADATA_LOCATION = "location";
068    
069    /** Metadata's name for startDate's event* */
070    public static final String METADATA_START_DATE = "startDate";
071    
072    /** Metadata's name for endDate's event* */
073    public static final String METADATA_END_DATE = "endDate";
074    
075    /** Metadata's name for last validated date's event* */
076    public static final String METADATA_VALIDATED = "lastValidated";
077    
078    /** Metadata's name for last validator's event* */
079    public static final String METADATA_VALIDATOR = "validator";
080    
081    /** Metadata's name for fullDay's event* */
082    public static final String METADATA_FULL_DAY = "fullDay";
083    
084    /** Metadata's name for fullDay's event* */
085    public static final String METADATA_RECURRENCE_TYPE = "recurrenceType";
086    
087    /** Metadata's name for fullDay's event* */
088    public static final String METADATA_UNTIL_DATE = "untilDate";
089    
090    /** Metadata's name for fullDay's event* */
091    public static final String METADATA_EXCLUDED_DATE = "excludedDate";
092    
093    /** Property's name for workflow id */
094    public static final String PROPERTY_WORKFLOW_ID = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":workflowId";
095    
096    /**
097     * Creates an {@link JCRCalendarEvent}.
098     * @param node the node backing this {@link AmetysObject}
099     * @param parentPath the parentPath in the Ametys hierarchy
100     * @param factory the DefaultAmetysObjectFactory which created the AmetysObject
101     */
102    public JCRCalendarEvent(Node node, String parentPath, JCRCalendarEventFactory factory)
103    {
104        super(node, parentPath, factory);
105    }
106
107    @Override
108    public String getTitle()
109    {
110        return getMetadataHolder().getString(METADATA_TITLE);
111    }
112    
113    @Override
114    public String getDescription()
115    {
116        return getMetadataHolder().getString(METADATA_DESC);
117    }
118    
119    @Override
120    public String getLocation()
121    {
122        return getMetadataHolder().getString(METADATA_LOCATION, null);
123    }
124    
125    @Override
126    public String[] getKeywords() throws AmetysRepositoryException
127    {
128        return getMetadataHolder().getStringArray(METADATA_KEYWORDS, new String[0]);
129    }
130    
131    @Override
132    public Date getStartDate()
133    {
134        return getMetadataHolder().getDate(METADATA_START_DATE);
135    }
136
137    @Override
138    public Date getEndDate()
139    {
140        return getMetadataHolder().getDate(METADATA_END_DATE);
141    }
142
143    @Override
144    public Boolean getFullDay()
145    {
146        return getMetadataHolder().getBoolean(METADATA_FULL_DAY);
147    }
148    
149    @Override
150    public UserIdentity getCreator()
151    {
152        CompositeMetadata creatorMetadata = getMetadataHolder().getCompositeMetadata(METADATA_CREATOR);
153        return new UserIdentity(creatorMetadata.getString("login"), creatorMetadata.getString("population"));
154    }
155
156    @Override
157    public Date getCreationDate()
158    {
159        return getMetadataHolder().getDate(METADATA_CREATION);
160    }
161
162    @Override
163    public UserIdentity getLastContributor()
164    {
165        CompositeMetadata contributorMetadata = getMetadataHolder().getCompositeMetadata(METADATA_CONTRIBUTOR);
166        return new UserIdentity(contributorMetadata.getString("login"), contributorMetadata.getString("population"));
167    }
168
169    @Override
170    public Date getLastModified()
171    {
172        return getMetadataHolder().getDate(METADATA_MODIFIED);
173    }
174    
175    @Override
176    public UserIdentity getLastValidator() throws UnknownMetadataException, AmetysRepositoryException
177    {
178        if (getMetadataHolder().hasMetadata(METADATA_VALIDATOR))
179        {
180            CompositeMetadata validatorMetadata = getMetadataHolder().getCompositeMetadata(METADATA_VALIDATOR);
181            return new UserIdentity(validatorMetadata.getString("login"), validatorMetadata.getString("population"));
182        }
183        
184        return null;
185    }
186    
187    @Override
188    public Date getLastValidated() throws UnknownMetadataException, AmetysRepositoryException
189    {
190        if (getMetadataHolder().hasMetadata(METADATA_VALIDATED))
191        {
192            return getMetadataHolder().getDate(METADATA_VALIDATED);
193        }
194        
195        return null;
196    }
197    
198    @Override
199    public EventRecurrenceTypeEnum getRecurrenceType()
200    {
201        EventRecurrenceTypeEnum recurrenceEnum = EventRecurrenceTypeEnum.NEVER;
202        if (getMetadataHolder().hasMetadata(METADATA_RECURRENCE_TYPE))
203        {
204            String recurrenceType = getMetadataHolder().getString(METADATA_RECURRENCE_TYPE);
205            try
206            {
207                recurrenceEnum = EventRecurrenceTypeEnum.valueOf(recurrenceType);
208            }
209            catch (Exception e)
210            {
211                // ignore
212            }
213        }
214        
215        return recurrenceEnum;
216    }
217    
218    @Override
219    public Boolean isRecurrent()
220    {
221        return !getRecurrenceType().equals(EventRecurrenceTypeEnum.NEVER);
222    }
223    
224    @Override
225    public Date getRepeatUntil()
226    {
227        if (getMetadataHolder().hasMetadata(METADATA_UNTIL_DATE))
228        {
229            return getMetadataHolder().getDate(METADATA_UNTIL_DATE);
230        }
231        
232        return null;
233    }
234    
235    @Override
236    public List<Date> getExcludedOccurences()
237    {
238        List<Date> excludedOccurences = new ArrayList<>();
239        if (getMetadataHolder().hasMetadata(METADATA_EXCLUDED_DATE))
240        {
241            excludedOccurences = Arrays.asList(getMetadataHolder().getDateArray(METADATA_EXCLUDED_DATE));
242        }
243        return excludedOccurences;
244    }
245    
246    @Override
247    public void setTitle(String title)
248    {
249        getMetadataHolder().setMetadata(METADATA_TITLE, title);
250    }
251    
252    @Override
253    public void setDescription(String desc)
254    {
255        getMetadataHolder().setMetadata(METADATA_DESC, desc);
256    }
257    
258    @Override
259    public void setLocation(String location)
260    {
261        getMetadataHolder().setMetadata(METADATA_LOCATION, location);
262    }
263    
264    @Override
265    public void setKeywords(String[] keywords)
266    {
267        getMetadataHolder().setMetadata(METADATA_KEYWORDS, keywords);
268    }
269    
270    @Override
271    public void setStartDate(Date startDate)
272    {
273        getMetadataHolder().setMetadata(METADATA_START_DATE, startDate);
274    }
275
276    @Override
277    public void setEndDate(Date endDate)
278    {
279        getMetadataHolder().setMetadata(METADATA_END_DATE, endDate);     
280    }
281
282    @Override
283    public void setFullDay(Boolean fullDay)
284    {
285        getMetadataHolder().setMetadata(METADATA_FULL_DAY, fullDay);
286    }
287    
288    @Override
289    public void setCreator(UserIdentity user) throws UnknownMetadataException, AmetysRepositoryException
290    {
291        try
292        {
293            Node creatorNode = null;
294            if (getNode().hasNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CREATOR))
295            {
296                creatorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CREATOR);
297            }
298            else
299            {
300                creatorNode = getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CREATOR, RepositoryConstants.USER_NODETYPE);
301            }
302            creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", user.getLogin());
303            creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", user.getPopulationId());
304        }
305        catch (RepositoryException e)
306        {
307            throw new AmetysRepositoryException("Error setting the creator property.", e);
308        }
309    }
310
311    @Override
312    public void setCreationDate(Date date) throws UnknownMetadataException, AmetysRepositoryException
313    {
314        getMetadataHolder().setMetadata(METADATA_CREATION, date);
315    }
316
317    @Override
318    public void setLastContributor(UserIdentity user) throws UnknownMetadataException, AmetysRepositoryException
319    {
320        try
321        {
322            Node creatorNode = null;
323            if (getNode().hasNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CONTRIBUTOR))
324            {
325                creatorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CONTRIBUTOR);
326            }
327            else
328            {
329                creatorNode = getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CONTRIBUTOR, RepositoryConstants.USER_NODETYPE);
330            }
331            creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", user.getLogin());
332            creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", user.getPopulationId());
333        }
334        catch (RepositoryException e)
335        {
336            throw new AmetysRepositoryException("Error setting the contributor property.", e);
337        }
338    }
339
340    @Override
341    public void setLastModified(Date date) throws UnknownMetadataException, AmetysRepositoryException
342    {
343        getMetadataHolder().setMetadata(METADATA_MODIFIED, date);
344    }
345    
346    @Override
347    public void setLastValidator(UserIdentity user) throws UnknownMetadataException, AmetysRepositoryException
348    {
349        try
350        {
351            Node creatorNode = null;
352            if (getNode().hasNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_VALIDATOR))
353            {
354                creatorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_VALIDATOR);
355            }
356            else
357            {
358                creatorNode = getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_VALIDATOR, RepositoryConstants.USER_NODETYPE);
359            }
360            creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", user.getLogin());
361            creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", user.getPopulationId());
362        }
363        catch (RepositoryException e)
364        {
365            throw new AmetysRepositoryException("Error setting the validator property.", e);
366        }
367    }
368    
369    @Override
370    public void setLastValidated(Date date) throws UnknownMetadataException, AmetysRepositoryException
371    {
372        getMetadataHolder().setMetadata(METADATA_VALIDATED, date);
373    }
374    
375    @Override
376    public void setRecurrenceType(String recurrenceType)
377    {
378        getMetadataHolder().setMetadata(METADATA_RECURRENCE_TYPE, recurrenceType);
379    }
380    
381    @Override
382    public void setRepeatUntil(Date untilDate)
383    {
384        if (untilDate == null)
385        {
386            if (getMetadataHolder().hasMetadata(METADATA_UNTIL_DATE))
387            {
388                getMetadataHolder().removeMetadata(METADATA_UNTIL_DATE);
389            }
390        }
391        else
392        {
393            getMetadataHolder().setMetadata(METADATA_UNTIL_DATE, untilDate);
394        }
395    }
396    
397    @Override
398    public void setExcludedOccurrences(List<Date> excludedOccurrences)
399    {
400        getMetadataHolder().setMetadata(METADATA_EXCLUDED_DATE, excludedOccurrences.toArray(new Date[excludedOccurrences.size()]));
401    }
402    
403    @Override
404    public List<Date> getOccurrences(Date startDate, Date endDate)
405    {
406        Date untilDate = getRepeatUntil();
407        
408        // until date must be included in list of occurrences.
409        // For that purpose, until date is used here as a threshold value that
410        // must be set to the start of the next day
411        if (untilDate != null)
412        {
413            Calendar cal = Calendar.getInstance();
414            cal.setTime(untilDate);
415            cal.add(Calendar.DATE, 1);
416            cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
417            cal.set(java.util.Calendar.MINUTE, 0);
418            cal.set(java.util.Calendar.SECOND, 0);
419            cal.set(java.util.Calendar.MILLISECOND, 0);
420            untilDate = cal.getTime();
421        }
422        
423        List<Date> occurences = new ArrayList<>();
424        Date firstDate = getFirstOccurrence(startDate);
425        if (firstDate == null || (untilDate != null && !firstDate.before(untilDate)))
426        {
427            return occurences;
428        }
429        
430        List<Date> excludedOccurences = getExcludedOccurences();
431        
432        GregorianCalendar firstDateCalendar = new GregorianCalendar();
433        firstDateCalendar.setTime(firstDate);
434        firstDateCalendar.set(java.util.Calendar.HOUR_OF_DAY, 0);
435        firstDateCalendar.set(java.util.Calendar.MINUTE, 0);
436        firstDateCalendar.set(java.util.Calendar.SECOND, 0);
437        firstDateCalendar.set(java.util.Calendar.MILLISECOND, 0);
438        
439        if (!excludedOccurences.contains(firstDateCalendar.getTime()))
440        {
441            occurences.add(firstDate);
442        }
443        
444        Date nextDate = getNextOccurrence(firstDate);
445        while (nextDate != null && nextDate.before(endDate) && (untilDate == null || untilDate.after(nextDate)))
446        {
447            GregorianCalendar nextDateCalendar = new GregorianCalendar();
448            nextDateCalendar.setTime(nextDate);
449            nextDateCalendar.set(java.util.Calendar.HOUR_OF_DAY, 0);
450            nextDateCalendar.set(java.util.Calendar.MINUTE, 0);
451            nextDateCalendar.set(java.util.Calendar.SECOND, 0);
452            nextDateCalendar.set(java.util.Calendar.MILLISECOND, 0);
453            
454            if (!excludedOccurences.contains(nextDateCalendar.getTime()))
455            {
456                occurences.add(nextDate);
457            }
458            nextDate = getNextOccurrence(nextDate);
459        }
460        
461        return occurences;
462    }
463    
464    @Override
465    public Date getFirstOccurrence(Date date)
466    {
467        Date eventStartDate = getStartDate();
468        Date eventEndDate = getEndDate();
469        
470        if (eventEndDate.after(date))
471        {
472            return eventStartDate;
473        }
474        else
475        {
476            long eventDuringTime = eventEndDate.getTime() - eventStartDate.getTime();
477            
478            Date endDate = eventEndDate;
479            Date startDate = eventStartDate;
480            while (startDate != null && endDate.before(date))
481            {
482                startDate = RecurrentEventHelper.getNextDate(this, startDate);
483                if (startDate != null)
484                {
485                    endDate = new Date(startDate.getTime() + eventDuringTime);
486                }
487            }
488            
489            return startDate;
490        }
491    }
492    
493    @Override
494    public Date getNextOccurrence(Date date)
495    {
496        return RecurrentEventHelper.getNextDate(this, date);
497    }
498    
499    @Override
500    public long getWorkflowId() throws AmetysRepositoryException
501    {
502        try
503        {
504            return getNode().getProperty(PROPERTY_WORKFLOW_ID).getLong();
505        }
506        catch (RepositoryException e)
507        {
508            throw new AmetysRepositoryException("Unable to get workflowId property", e);
509        }
510    }
511    
512    @Override
513    public void setWorkflowId(long workflowId) throws AmetysRepositoryException
514    {
515        Node node = getNode();
516        
517        try
518        {
519            node.setProperty(PROPERTY_WORKFLOW_ID, workflowId);
520        }
521        catch (RepositoryException e)
522        {
523            throw new AmetysRepositoryException("Unable to set workflowId property", e);
524        }
525    }
526    
527    @Override
528    public long getCurrentStepId()
529    {
530        throw new UnsupportedOperationException();
531    }
532    
533    @Override
534    public void setCurrentStepId(long stepId)
535    {
536        throw new UnsupportedOperationException();
537    }
538}