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