001/*
002 *  Copyright 2013 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.cart;
017
018import java.time.ZonedDateTime;
019import java.util.ArrayList;
020import java.util.Calendar;
021import java.util.Collections;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Set;
025import java.util.stream.Collectors;
026
027import javax.jcr.Node;
028import javax.jcr.RepositoryException;
029
030import org.apache.commons.lang3.ArrayUtils;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034import org.ametys.cms.data.ametysobject.ModifiableModelAwareDataAwareAmetysObject;
035import org.ametys.cms.data.holder.ModifiableIndexableDataHolder;
036import org.ametys.cms.data.holder.impl.DefaultModifiableModelAwareDataHolder;
037import org.ametys.cms.indexing.solr.SolrAclCacheUninfluentialObject;
038import org.ametys.cms.repository.Content;
039import org.ametys.core.user.UserIdentity;
040import org.ametys.core.util.DateUtils;
041import org.ametys.plugins.explorer.resources.Resource;
042import org.ametys.plugins.queriesdirectory.Query;
043import org.ametys.plugins.repository.AmetysObject;
044import org.ametys.plugins.repository.AmetysRepositoryException;
045import org.ametys.plugins.repository.UnknownAmetysObjectException;
046import org.ametys.plugins.repository.data.holder.group.ModifiableModelAwareRepeater;
047import org.ametys.plugins.repository.data.holder.group.ModifiableModelAwareRepeaterEntry;
048import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData;
049import org.ametys.plugins.repository.jcr.DefaultAmetysObject;
050
051/**
052 * Class representing a cart, backed by a JCR node.<br>
053 */
054@SolrAclCacheUninfluentialObject
055public class Cart extends DefaultAmetysObject<CartFactory> implements ModifiableModelAwareDataAwareAmetysObject
056{
057    /** Property name for cart title */
058    public static final String TITLE = "label";
059    /** Property name for cart description */
060    public static final String DESCRIPTION = "description";
061    /** Property name for cart documentation */
062    public static final String DOCUMENTATION = "documentation";
063    /** Property name for cart author */
064    public static final String AUTHOR = "author";
065    /** Property name for cart last contributor */
066    public static final String CONTRIBUTOR = "contributor";
067    /** Property name for cart creation date */
068    public static final String CREATIONDATE = "creationDate";
069    /** Property name for cart last modification date */
070    public static final String LASTMODIFICATIONDATE = "lastModificationDate";
071
072    /** Property name for cart content elements */
073    public static final String CONTENT_CART_ELEMENTS = "contents";    
074    /** Property name for cart resource elements */
075    public static final String RESOURCE_CART_ELEMENTS = "resources";
076    /** Property name for cart queries elements */
077    public static final String QUERIES_CART_ELEMENTS = "queries";
078    /** Property name for cart queries from directory elements */
079    public static final String QUERIES_FROM_DIRECTORY_CART_ELEMENTS = "queries-from-directory";
080    
081    /** Property name for cart query id */
082    public static final String QUERY_ID_PROPERTY = "id";
083    /** Property name for cart query description */
084    public static final String QUERY_DESCRIPTION_PROPERTY = "description";
085    /** Property name for cart query author */
086    public static final String QUERY_AUTHOR_PROPERTY = "author";
087    /** Property name for cart query title */
088    public static final String QUERY_TITLE_PROPERTY = "title";
089    /** Property name for cart query creation date */
090    public static final String QUERY_DATE_PROPERTY = "date";
091    
092    private static Logger _logger = LoggerFactory.getLogger(Cart.class.getName());
093    
094    /**
095     * Rights profiles
096     */
097    public enum CartProfile
098    {
099        /** Read access */
100        READ_ACCESS,
101        /** Write access */
102        WRITE_ACCESS,
103        /** Right access */
104        RIGHT_ACCESS;
105        
106        @Override
107        public String toString()
108        {
109            return name().toLowerCase();
110        }
111    }
112    
113    /**
114     * Types of CartElement
115     */
116    public enum CartElementType
117    {
118        /** Type: content. */
119        CONTENT,
120        /** Type: resource. */
121        RESOURCE,
122        /** Type: cartQuery. */
123        CARTQUERY,
124        /** Type: query from directory. */
125        CARTQUERYFROMDIRECTORY
126    }
127    
128    /**
129     * Creates an {@link Cart}.
130     * @param node the node backing this {@link AmetysObject}
131     * @param parentPath the parentPath in the Ametys hierarchy
132     * @param factory the DefaultAmetysObjectFactory which created the AmetysObject
133     */
134    public Cart(Node node, String parentPath, CartFactory factory)
135    {
136        super(node, parentPath, factory);
137    }        
138    
139    /**
140     * Set the title of this cart.
141     * @param title the title
142     */
143    public void setTitle(String title)
144    {
145        this.setValue(TITLE, title);
146    }
147    
148    /**
149     * Set the description of this cart.
150     * @param description the description
151     */
152    public void setDescription(String description)
153    {
154        this.setValue(DESCRIPTION, description);
155    }
156    
157    /**
158     * Set the documentation of this cart.
159     * @param documentation the documentation
160     */
161    public void setDocumentation(String documentation)
162    {
163        this.setValue(DOCUMENTATION, documentation);
164    }
165    
166    /**
167     * Set the author of this cart.
168     * @param author the author
169     */
170    public void setAuthor(UserIdentity author)
171    {
172        this.setValue(AUTHOR, author);
173    }
174
175    /**
176     * Set the last contributor of this cart.
177     * @param contributor the last contributor
178     */
179    public void setContributor(UserIdentity contributor)
180    {
181        this.setValue(CONTRIBUTOR, contributor);
182    }
183    
184    /**
185     * Set the date of the creation.
186     * @param creationDate the last modification date to set.
187     */
188    public void setCreationDate(ZonedDateTime creationDate)
189    {
190        this.setValue(CREATIONDATE, creationDate);
191    }
192    
193    /**
194     * Set the date of the last modification.
195     * @param lastModificationDate the last modification date to set.
196     */
197    public void setLastModificationDate(ZonedDateTime lastModificationDate)
198    {
199        this.setValue(LASTMODIFICATIONDATE, lastModificationDate);
200    }
201    
202    /**
203     * Get the title of the cart
204     * @return The title
205     */
206    public String getTitle()
207    {        
208        return this.getValue(TITLE);
209    }
210        
211    /**
212     * Get the description of the cart
213     * @return The description
214     */
215    public String getDescription()
216    {
217        return this.getValue(DESCRIPTION);
218    }
219
220    /**
221     * Get the documentation of the cart
222     * @return The documentation
223     */
224    public String getDocumentation()
225    {
226        return this.getValue(DOCUMENTATION);
227    }
228    
229    /**
230     * Get the author of the cart
231     * @return The author
232     */
233    public UserIdentity getAuthor()
234    {
235        return this.getValue(AUTHOR);
236    }
237    
238    /**
239     * Get the last contributor of the cart
240     * @return The contributor
241     */
242    public UserIdentity getContributor()
243    {
244        return this.getValue(CONTRIBUTOR);
245    }
246    
247    /**
248     * Get the date of the last modification of the cart
249     * @return The date
250     */
251    public ZonedDateTime getCreationDate()
252    {
253        return this.getValue(CREATIONDATE);
254    }
255    
256    /**
257     * Get the date of the last modification of the cart
258     * @return The date
259     */
260    public ZonedDateTime getLastModificationDate()
261    {
262        return this.getValue(LASTMODIFICATIONDATE);
263    }
264        
265    /**
266     * Add a content to the cart
267     * @param contentId The content id
268     */
269    public void addContent (String contentId)
270    {
271        _addElementToCart(contentId, CONTENT_CART_ELEMENTS);
272    }
273    
274    /**
275     * Add a resource to the cart
276     * @param resourceId The resource id
277     */
278    public void addResource (String resourceId)
279    {
280        _addElementToCart(resourceId, RESOURCE_CART_ELEMENTS);
281    }
282    
283    /**
284     * Add a query from directory to the cart
285     * @param queryId The query id
286     */
287    public void addQueryFormDirectory (String queryId)
288    {
289        _addElementToCart(queryId, QUERIES_FROM_DIRECTORY_CART_ELEMENTS);
290    }
291
292    private void _addElementToCart(String elementId, String attributePath)
293    {
294        String[] elmtArray = getValue(attributePath, false, new String[0]);
295        Set<String> elmts = new HashSet<>();
296        Collections.addAll(elmts, elmtArray);
297        elmts.add(elementId);
298        setValue(attributePath, elmts.toArray(new String[elmts.size()]));
299    }
300    
301    /**
302     * Add a query to the cart
303     * @param author The author of the query
304     * @param title The title of the query 
305     * @param description The query as string
306     */
307    public void addQuery (UserIdentity author, String title, String description)
308    {
309        addQuery(org.ametys.core.util.StringUtils.generateKey(), author, title, description, ZonedDateTime.now());
310    }
311    
312    /**
313     * Add a query to the cart
314     * @param id The id of the query
315     * @param author The author of the query
316     * @param title The title of the query 
317     * @param description The query as string
318     * @param date The creation date of the query
319     */
320    public void addQuery (String id, UserIdentity author, String title, String description, ZonedDateTime date)
321    {
322        ModifiableModelAwareRepeater queries = getRepeater(QUERIES_CART_ELEMENTS, true);
323        ModifiableModelAwareRepeaterEntry query = queries.addEntry();
324        query.setValue(QUERY_ID_PROPERTY, id);
325        query.setValue(QUERY_TITLE_PROPERTY, title);
326        query.setValue(QUERY_DESCRIPTION_PROPERTY, description);
327        query.setValue(QUERY_AUTHOR_PROPERTY, author);
328        query.setValue(QUERY_DATE_PROPERTY, date);
329    }
330    
331    /**
332     * Delete an element
333     * @param elmtId The id of element to remove
334     * @param elmtType The type of element to remove
335     */
336    public void removeElement (String elmtId, CartElementType elmtType)
337    {
338        switch (elmtType)
339        {
340            case CONTENT:
341                _removeContent(elmtId);
342                break;
343                
344            case RESOURCE:
345                _removeResource(elmtId);
346                break;
347                
348            case CARTQUERY:
349                _removeQuery(elmtId);
350                break;
351                
352            case CARTQUERYFROMDIRECTORY:
353                _removeQueryFromDirectory(elmtId);
354                break;
355            
356            default:
357                break;
358        }
359    }
360    
361    /**
362     * Remove a resource from the cart
363     * @param resourceIdToRemove The id of the resource to remove
364     */
365    protected void _removeResource(String resourceIdToRemove)
366    {
367        _removeElementFromCart(resourceIdToRemove, RESOURCE_CART_ELEMENTS);
368    }
369    
370    /**
371     * Remove a content from the cart
372     * @param contentIdToRemove The id of the content to remove
373     */
374    protected void _removeContent(String contentIdToRemove)
375    {
376        _removeElementFromCart(contentIdToRemove, CONTENT_CART_ELEMENTS);
377    }
378    
379    /**
380     * Remove a query from directory from the cart
381     * @param queryIdToRemove The id of the query from directory to remove
382     */
383    protected void _removeQueryFromDirectory(String queryIdToRemove)
384    {
385        _removeElementFromCart(queryIdToRemove, QUERIES_FROM_DIRECTORY_CART_ELEMENTS);
386    }
387    
388    private void _removeElementFromCart(String elementId, String attributePath)
389    {
390        String[] elements = getValue(attributePath, false, new String[0]);
391        elements = ArrayUtils.removeElement(elements, elementId);
392        setValue(attributePath, elements);
393    }
394    
395    /**
396     * Remove a query from the cart
397     * @param queryIdToRemove The id of the query to remove
398     */
399    protected void _removeQuery(String queryIdToRemove)
400    {
401        ModifiableModelAwareRepeater queries = getRepeater(QUERIES_CART_ELEMENTS, true);
402        ModifiableModelAwareRepeaterEntry entryToRemove = queries.getEntries()
403                .stream()
404              .filter(entry -> entry.getValue(QUERY_ID_PROPERTY).equals(queryIdToRemove))
405              .findFirst()
406              .orElse(null);
407        
408        if (entryToRemove != null)
409        {
410            queries.removeEntry(entryToRemove.getPosition());
411        }
412    }
413    
414    /**
415     * Get the elements of the cart
416     * @return The elements of the cart
417     */
418    public List<CartElement> getElements ()
419    {
420        List<CartElement> elmts = new ArrayList<>();
421        
422        try
423        {
424            elmts.addAll(getContentCartElements());
425            elmts.addAll(getResourceCartElements());
426            elmts.addAll(getQueryCartElements());
427            elmts.addAll(getQueryFromDirectoryCartElements());
428        }
429        catch (RepositoryException e)
430        {
431            throw new AmetysRepositoryException("Error while getting cart elements", e);
432        }
433        
434        return elmts;
435    }
436    
437    /**
438     * Get the contents of the cart
439     * @return The elements of the cart
440     * @throws RepositoryException if an exception occurs while exploring the repository
441     */
442    public List<ContentElement> getContentCartElements () throws RepositoryException
443    {
444        List<String> unexistingElmts = new ArrayList<>();
445        
446        List<ContentElement> elmts = new ArrayList<>();
447        
448        String[] contents = getValue(CONTENT_CART_ELEMENTS, false, new String[0]);
449        
450        for (String contentId : contents)
451        {
452            try
453            {
454                Content content = _getFactory().getResolver().resolveById(contentId);
455                elmts.add(new ContentElement(content, _getFactory()._getContentTypesHelper(), _getFactory()._getContentTypeEP()));
456            }
457            catch (UnknownAmetysObjectException e)
458            {
459                _logger.error("The content of id '{}' does not exist anymore. It will be deleting from cart '{}'.", contentId, getId());
460                unexistingElmts.add(contentId);
461            }
462        }
463        // Delete unexisting contents
464        for (String contentId : unexistingElmts)
465        {
466            _removeContent(contentId);
467        }
468        
469        return elmts;
470    }
471    
472    /**
473     * Get the resources of the cart
474     * @return The elements of the cart
475     * @throws RepositoryException if an exception occurs while exploring the repository
476     */
477    public List<ResourceElement> getResourceCartElements () throws RepositoryException
478    {
479        List<String> unexistingElmts = new ArrayList<>();
480        List<ResourceElement> elmts = new ArrayList<>();
481
482        
483        String[] resources = getValue(RESOURCE_CART_ELEMENTS, false, new String[0]);
484        
485        for (String resourceId : resources)
486        {
487            try
488            {
489                Resource resource = _getFactory().getResolver().resolveById(resourceId);
490                elmts.add(new ResourceElement(resource));
491            }
492            catch (UnknownAmetysObjectException e)
493            {
494                _logger.error("The resource of id '{}' does not exist anymore. It will be deleting from cart '{}'.", resourceId, getId());
495                unexistingElmts.add(resourceId);
496            }
497        }
498        
499        // Delete unexisting resources
500        for (String resourceId : unexistingElmts)
501        {
502            _removeResource(resourceId);
503        }
504        
505        return elmts;
506    }
507    
508    /**
509     * Get the resources of the cart
510     * @return The elements of the cart
511     * @throws RepositoryException if an exception occurs while exploring the repository
512     */
513    public List<QueryFromDirectoryElement> getQueryFromDirectoryCartElements () throws RepositoryException
514    {
515        List<String> unexistingElmts = new ArrayList<>();
516        List<QueryFromDirectoryElement> elmts = new ArrayList<>();
517
518        
519        String[] queries = getValue(QUERIES_FROM_DIRECTORY_CART_ELEMENTS, false, new String[0]);
520        for (String queryId : queries)
521        {
522            try
523            {
524                Query query = _getFactory().getResolver().resolveById(queryId);
525                elmts.add(new QueryFromDirectoryElement(query));
526            }
527            catch (UnknownAmetysObjectException e)
528            {
529                _logger.error("The query of id '{}' does not exist anymore. It will be deleting from cart '{}'.", queryId, getId());
530                unexistingElmts.add(queryId);
531            }
532        }
533        
534        // Delete unexisting queries
535        for (String queryId : unexistingElmts)
536        {
537            _removeQueryFromDirectory(queryId);
538        }
539        
540        return elmts;
541    }
542    
543    /**
544     * <code>true</code> if the query cart element exist
545     * @param queryId the query id
546     * @return <code>true</code> if the query cart element exist
547     */
548    public boolean hasQueryFromDirectoryCartElement(String queryId)
549    {
550        String[] queries = getValue(QUERIES_FROM_DIRECTORY_CART_ELEMENTS, false, new String[0]);
551        return ArrayUtils.contains(queries, queryId);
552    }
553    
554    /**
555     * Get the queries of the cart
556     * @return The elements of the cart
557     * @throws RepositoryException if an exception occurs while exploring the repository
558     */
559    public List<QueryElement> getQueryCartElements () throws RepositoryException
560    {
561        ModifiableModelAwareRepeater queries = getRepeater(QUERIES_CART_ELEMENTS, true);
562        
563        List<QueryElement> elmts = queries.getEntries()
564                .stream()
565                .map(entry -> {
566                    String id = entry.getValue(QUERY_ID_PROPERTY);
567                    UserIdentity author = entry.getValue(QUERY_AUTHOR_PROPERTY);
568                    String query = entry.getValue(QUERY_DESCRIPTION_PROPERTY);
569                    String title = entry.getValue(QUERY_TITLE_PROPERTY);
570                    ZonedDateTime zonedDateTime = entry.getValue(QUERY_DATE_PROPERTY);
571                    Calendar creationDate = DateUtils.asCalendar(zonedDateTime);
572                           
573                    return new QueryElement(id, query, author, creationDate, title);
574                })
575                .collect(Collectors.toList());
576        
577        return elmts;
578    }
579
580    public ModifiableIndexableDataHolder getDataHolder()
581    {
582        JCRRepositoryData repositoryData = new JCRRepositoryData(getNode());
583        return new DefaultModifiableModelAwareDataHolder(repositoryData, this._getFactory().getModel());
584    }
585}