001/*
002 *  Copyright 2012 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.linkdirectory.repository;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.time.ZonedDateTime;
021
022import javax.jcr.Node;
023import javax.jcr.PathNotFoundException;
024import javax.jcr.RepositoryException;
025import javax.jcr.Value;
026
027import org.apache.commons.lang3.ArrayUtils;
028import org.apache.commons.lang3.StringUtils;
029
030import org.ametys.cms.data.Binary;
031import org.ametys.plugins.linkdirectory.Link;
032import org.ametys.plugins.repository.AmetysObject;
033import org.ametys.plugins.repository.AmetysRepositoryException;
034import org.ametys.plugins.repository.MovableAmetysObject;
035import org.ametys.plugins.repository.RepositoryConstants;
036import org.ametys.plugins.repository.RepositoryIntegrityViolationException;
037import org.ametys.plugins.repository.data.ametysobject.ModifiableModelLessDataAwareAmetysObject;
038import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder;
039import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelLessDataHolder;
040import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData;
041import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject;
042import org.ametys.web.repository.SiteAwareAmetysObject;
043import org.ametys.web.repository.site.Site;
044
045/**
046 * Repository implementation of a directory link.
047 */
048public class DefaultLink extends DefaultTraversableAmetysObject<DefaultLinkFactory> implements Link, SiteAwareAmetysObject, MovableAmetysObject, ModifiableModelLessDataAwareAmetysObject
049{
050    /** Constant for URL property. */
051    public static final String PROPERTY_URL = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":url";
052
053    /** Constant for id of provider of dynamic information. */
054    public static final String PROPERTY_DYNAMIC_INFO_PROVIDER = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":dynamic-information";
055
056    /** Constant for internal URL property. */
057    public static final String PROPERTY_INTERNAL_URL = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":internal-url";
058
059    /** Constant for URL type property. */
060    public static final String PROPERTY_URLTYPE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":url-type";
061
062    /** Constant for title property. */
063    public static final String PROPERTY_TITLE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":title";
064
065    /** Constant for title property. */
066    public static final String PROPERTY_CONTENT = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":content";
067
068    /** Constant for URL alternative property. */
069    public static final String PROPERTY_URL_ALTERNATIVE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":url-alternative";
070
071    /** Constant for picture type property. */
072    public static final String PROPERTY_PICTURE_TYPE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":picture-type";
073
074    /** Constant for picture data property. */
075    public static final String PROPERTY_PICTURE = "picture";
076
077    /** Constant for picture id property. */
078    public static final String PROPERTY_PICTURE_ID = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":picture-id";
079    
080    /** Constant for picture glyph property. */
081    public static final String PROPERTY_PICTURE_GLYPH = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":picture-glyph";
082
083    /** Constant for picture alternative property. */
084    public static final String PROPERTY_PICTURE_ALTERNATIVE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":picture-alternative";
085
086    /** Constant for themes property. */
087    public static final String PROPERTY_THEMES = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":themes";
088    
089    /** Constant for color property. */
090    public static final String PROPERTY_COLOR = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":color";
091
092    /** Constant for page property. */
093    public static final String PROPERTY_PAGE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":page";
094
095    /** Constant for status property. */
096    public static final String PROPERTY_STATUS = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":status";
097    
098    /** Constant for default visibilty property. */
099    public static final String PROPERTY_DEFAULT_VISIBILITY = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":default-visibility";
100
101
102    /**
103     * Create a {@link DefaultLink}.
104     * 
105     * @param node the node backing this {@link AmetysObject}.
106     * @param parentPath the parent path in the Ametys hierarchy.
107     * @param factory the {@link DefaultLinkFactory} which creates the
108     *            AmetysObject.
109     */
110    public DefaultLink(Node node, String parentPath, DefaultLinkFactory factory)
111    {
112        super(node, parentPath, factory);
113    }
114
115    @Override
116    public String getUrl() throws AmetysRepositoryException
117    {
118        try
119        {
120            return getNode().getProperty(PROPERTY_URL).getString();
121        }
122        catch (PathNotFoundException e)
123        {
124            return null;
125        }
126        catch (RepositoryException e)
127        {
128            throw new AmetysRepositoryException("Error getting the URL property.", e);
129        }
130    }
131
132    @Override
133    public void setUrl(LinkType urlType, String url) throws AmetysRepositoryException
134    {
135        try
136        {
137            getNode().setProperty(PROPERTY_URLTYPE, urlType.toString());
138            getNode().setProperty(PROPERTY_URL, url);
139        }
140        catch (RepositoryException e)
141        {
142            throw new AmetysRepositoryException("Error setting the URL property.", e);
143        }
144    }
145
146    @Override
147    public String getInternalUrl() throws AmetysRepositoryException
148    {
149        try
150        {
151            return getNode().getProperty(PROPERTY_INTERNAL_URL).getString();
152        }
153        catch (PathNotFoundException e)
154        {
155            return null;
156        }
157        catch (RepositoryException e)
158        {
159            throw new AmetysRepositoryException("Error getting the internal URL property.", e);
160        }
161    }
162
163    @Override
164    public void setInternalUrl(String url) throws AmetysRepositoryException
165    {
166        try
167        {
168            getNode().setProperty(PROPERTY_INTERNAL_URL, url);
169        }
170        catch (RepositoryException e)
171        {
172            throw new AmetysRepositoryException("Error setting the internal URL property.", e);
173        }
174    }
175
176    @Override
177    public LinkType getUrlType() throws AmetysRepositoryException
178    {
179        try
180        {
181            return LinkType.valueOf(getNode().getProperty(PROPERTY_URLTYPE).getString());
182        }
183        catch (RepositoryException e)
184        {
185            throw new AmetysRepositoryException("Unable to get URL type property", e);
186        }
187    }
188
189    @Override
190    public String getTitle() throws AmetysRepositoryException
191    {
192        try
193        {
194            return getNode().getProperty(PROPERTY_TITLE).getString();
195        }
196        catch (PathNotFoundException e)
197        {
198            return null;
199        }
200        catch (RepositoryException e)
201        {
202            throw new AmetysRepositoryException("Error getting the title property.", e);
203        }
204    }
205
206    @Override
207    public void setTitle(String title) throws AmetysRepositoryException
208    {
209        try
210        {
211            getNode().setProperty(PROPERTY_TITLE, title);
212        }
213        catch (RepositoryException e)
214        {
215            throw new AmetysRepositoryException("Error setting the title property.", e);
216        }
217    }
218
219    @Override
220    public String getContent() throws AmetysRepositoryException
221    {
222        try
223        {
224            return getNode().getProperty(PROPERTY_CONTENT).getString();
225        }
226        catch (PathNotFoundException e)
227        {
228            return null;
229        }
230        catch (RepositoryException e)
231        {
232            throw new AmetysRepositoryException("Error getting the content property.", e);
233        }
234    }
235
236    @Override
237    public void setContent(String content) throws AmetysRepositoryException
238    {
239        try
240        {
241            getNode().setProperty(PROPERTY_CONTENT, content);
242        }
243        catch (RepositoryException e)
244        {
245            throw new AmetysRepositoryException("Error setting the content property.", e);
246        }
247    }
248
249    @Override
250    public String getAlternative() throws AmetysRepositoryException
251    {
252        try
253        {
254            return getNode().getProperty(PROPERTY_URL_ALTERNATIVE).getString();
255        }
256        catch (PathNotFoundException e)
257        {
258            return null;
259        }
260        catch (RepositoryException e)
261        {
262            throw new AmetysRepositoryException("Error getting the alternative property.", e);
263        }
264    }
265
266    @Override
267    public void setAlternative(String alternative) throws AmetysRepositoryException
268    {
269        try
270        {
271            getNode().setProperty(PROPERTY_URL_ALTERNATIVE, alternative);
272        }
273        catch (RepositoryException e)
274        {
275            throw new AmetysRepositoryException("Error setting the alternative property.", e);
276        }
277    }
278
279    @Override
280    public Binary getExternalPicture() throws AmetysRepositoryException
281    {
282        return getValue(PROPERTY_PICTURE);
283    }
284
285    @Override
286    public void setExternalPicture(String mimeType, String filename, InputStream stream) throws AmetysRepositoryException
287    {
288        try
289        {
290            removePictureMetas();
291
292            setPictureType("external");
293
294            Binary pic = new Binary();
295
296            pic.setLastModificationDate(ZonedDateTime.now());
297            pic.setInputStream(stream);
298            pic.setMimeType(mimeType);
299            pic.setFilename(filename);
300            
301            setValue(PROPERTY_PICTURE, pic);
302        }
303        catch (IOException | RepositoryException e)
304        {
305            throw new AmetysRepositoryException("Error setting the external picture property.", e);
306        }
307    }
308
309    @Override
310    public String getResourcePictureId() throws AmetysRepositoryException
311    {
312        try
313        {
314            return getNode().getProperty(PROPERTY_PICTURE_ID).getString();
315        }
316        catch (PathNotFoundException e)
317        {
318            return null;
319        }
320        catch (RepositoryException e)
321        {
322            throw new AmetysRepositoryException("Error getting the picture ID property.", e);
323        }
324    }
325
326    @Override
327    public void setResourcePicture(String resourceId) throws AmetysRepositoryException
328    {
329        try
330        {
331            removePictureMetas();
332
333            setPictureType("resource");
334
335            getNode().setProperty(PROPERTY_PICTURE_ID, resourceId);
336        }
337        catch (RepositoryException e)
338        {
339            throw new AmetysRepositoryException("Error setting the alternative property.", e);
340        }
341    }
342
343    @Override
344    public void setNoPicture() throws AmetysRepositoryException
345    {
346        try
347        {
348            setPictureType("");
349
350            removePictureMetas();
351        }
352        catch (RepositoryException e)
353        {
354            throw new AmetysRepositoryException("Error setting the alternative property.", e);
355        }
356    }
357
358    @Override
359    public String getPictureType() throws AmetysRepositoryException
360    {
361        try
362        {
363            return getNode().getProperty(PROPERTY_PICTURE_TYPE).getString();
364        }
365        catch (RepositoryException e)
366        {
367            throw new AmetysRepositoryException("Error getting the type property.", e);
368        }
369    }
370
371    @Override
372    public void setPictureType(String type) throws AmetysRepositoryException
373    {
374        try
375        {
376            getNode().setProperty(PROPERTY_PICTURE_TYPE, type);
377        }
378        catch (RepositoryException e)
379        {
380            throw new AmetysRepositoryException("Error setting the type property.", e);
381        }
382    }
383    
384    public String getPictureGlyph() throws AmetysRepositoryException
385    {
386        try
387        {
388            return getNode().getProperty(PROPERTY_PICTURE_GLYPH).getString();
389        }
390        catch (RepositoryException e)
391        {
392            throw new AmetysRepositoryException("Error getting the picture glyph property.", e);
393        }
394    }
395
396    public void setPictureGlyph(String glyph) throws AmetysRepositoryException
397    {
398        try
399        {
400            setPictureType("glyph");
401
402            getNode().setProperty(PROPERTY_PICTURE_GLYPH, glyph);
403        }
404        catch (RepositoryException e)
405        {
406            throw new AmetysRepositoryException("Error setting the picture glyph property.", e);
407        }
408    }
409
410    @Override
411    public String getPictureAlternative() throws AmetysRepositoryException
412    {
413        try
414        {
415            return getNode().getProperty(PROPERTY_PICTURE_ALTERNATIVE).getString();
416        }
417        catch (PathNotFoundException e)
418        {
419            return null;
420        }
421        catch (RepositoryException e)
422        {
423            throw new AmetysRepositoryException("Error getting the alternative property.", e);
424        }
425    }
426
427    @Override
428    public void setPictureAlternative(String alternative) throws AmetysRepositoryException
429    {
430        try
431        {
432            getNode().setProperty(PROPERTY_PICTURE_ALTERNATIVE, alternative);
433        }
434        catch (RepositoryException e)
435        {
436            throw new AmetysRepositoryException("Error setting the alternative property.", e);
437        }
438    }
439
440    @Override
441    public String[] getThemes() throws AmetysRepositoryException
442    {
443        return _getListFromMetadataName(PROPERTY_THEMES);
444    }
445
446    @Override
447    public void setThemes(String[] themes) throws AmetysRepositoryException
448    {
449        _setListWithMetadataName(themes, PROPERTY_THEMES);
450    }
451
452    @Override
453    public void removeTheme(String themeId) throws AmetysRepositoryException
454    {
455        try
456        {
457            String[] themes = getThemes();
458            String[] updatedThemes = ArrayUtils.removeElement(themes, themeId);
459            getNode().setProperty(PROPERTY_THEMES, updatedThemes);
460        }
461        catch (RepositoryException e)
462        {
463            throw new AmetysRepositoryException("Error removing theme of id " + themeId, e);
464        }
465    }
466
467    @Override
468    public Site getSite() throws AmetysRepositoryException
469    {
470        AmetysObject parent = getParent();
471        while (parent != null)
472        {
473            if (parent instanceof Site)
474            {
475                return (Site) parent;
476            }
477            parent = parent.getParent();
478        }
479
480        return null;
481    }
482
483    @Override
484    public String getSiteName() throws AmetysRepositoryException
485    {
486        return getSite().getName();
487    }
488
489    /**
490     * Get the theme language.
491     * 
492     * @return the theme language.
493     */
494    public String getLanguage()
495    {
496        return getParent().getParent().getName();
497    }
498
499    /**
500     * Remove all picture metas (picture ID and picture binary).
501     * 
502     * @throws RepositoryException if an error occurs.
503     */
504    protected void removePictureMetas() throws RepositoryException
505    {
506        getNode().setProperty(PROPERTY_PICTURE_ID, StringUtils.EMPTY);
507        removeValue(PROPERTY_PICTURE);
508    }
509
510    @Override
511    public void orderBefore(AmetysObject siblingObject) throws AmetysRepositoryException
512    {
513        if (siblingObject instanceof DefaultLink)
514        {
515            DefaultLink sibling = (DefaultLink) siblingObject;
516            sibling.getPath();
517            Node node = getNode();
518            String siblingPath = "";
519            try
520            {
521                siblingPath = sibling.getNode().getPath();
522                if (siblingPath.contains("/"))
523                {
524                    siblingPath = StringUtils.substringAfterLast(siblingPath, "/");
525                }
526                String path = node.getPath();
527                if (path.contains("/"))
528                {
529                    path = StringUtils.substringAfterLast(path, "/");
530                }
531                node.getParent().orderBefore(path, siblingPath);
532            }
533            catch (RepositoryException e)
534            {
535                throw new AmetysRepositoryException(String.format("Unable to order link '%s' before link '%s'", this, siblingPath), e);
536            }
537        }
538        else
539        {
540            throw new AmetysRepositoryException("A link can only be moved before another link.");
541        }
542    }
543
544    @Override
545    public void moveTo(AmetysObject newParent, boolean renameIfExist) throws AmetysRepositoryException, RepositoryIntegrityViolationException
546    {
547        if (getParent().equals(newParent))
548        {
549            try
550            {
551                // Just order current node to the end.
552                Node node = getNode();
553                String path = node.getPath();
554                if (path.contains("/"))
555                {
556                    path = StringUtils.substringAfterLast(path, "/");
557                }
558                node.getParent().orderBefore(path, null);
559            }
560            catch (RepositoryException e)
561            {
562                throw new AmetysRepositoryException(String.format("Unable to move link '%s' outside the root link node.", this), e);
563            }
564        }
565        else
566        {
567            throw new AmetysRepositoryException("A link can only be moved before another link.");
568        }
569    }
570
571    @Override
572    public boolean canMoveTo(AmetysObject newParent) throws AmetysRepositoryException
573    {
574        return getParent().equals(newParent);
575    }
576
577    /**
578     * Retrieves the list of values corresponding to the metadata name passed as
579     * parameter
580     * 
581     * @param metadataName the name of the metadata to retrieve
582     * @return the list corresponding to the metadata name
583     */
584    private String[] _getListFromMetadataName(String metadataName)
585    {
586        try
587        {
588            if (getNode().hasProperty(metadataName))
589            {
590                Value[] values = getNode().getProperty(metadataName).getValues();
591
592                String[] list = new String[values.length];
593
594                for (int i = 0; i < values.length; i++)
595                {
596                    list[i] = values[i].getString();
597                }
598
599                return list;
600            }
601            else
602            {
603                return new String[0];
604            }
605        }
606        catch (RepositoryException e)
607        {
608            throw new AmetysRepositoryException("An error occurred while trying to get the property '" + metadataName + "'.", e);
609        }
610    }
611
612    /**
613     * Sets the list of values to the node corresponding to the metadata name
614     * passed as a parameter
615     * 
616     * @param list the list of value to be set
617     * @param metadataName the name of the metadata
618     */
619    private void _setListWithMetadataName(String[] list, String metadataName)
620    {
621        try
622        {
623            getNode().setProperty(metadataName, list);
624        }
625        catch (RepositoryException e)
626        {
627            throw new AmetysRepositoryException("An error occurred while trying to set the property '" + metadataName + "'.", e);
628        }
629    }
630
631    @Override
632    public String getDynamicInformationProvider() throws AmetysRepositoryException
633    {
634        try
635        {
636            return getNode().getProperty(PROPERTY_DYNAMIC_INFO_PROVIDER).getString();
637        }
638        catch (PathNotFoundException e)
639        {
640            return null;
641        }
642        catch (RepositoryException e)
643        {
644            throw new AmetysRepositoryException("Error getting the URL property.", e);
645        }
646    }
647
648    @Override
649    public void setDynamicInformationProvider(String providerId) throws AmetysRepositoryException
650    {
651        try
652        {
653            getNode().setProperty(PROPERTY_DYNAMIC_INFO_PROVIDER, providerId);
654        }
655        catch (RepositoryException e)
656        {
657            throw new AmetysRepositoryException("Error setting the URL property.", e);
658        }
659    }
660    
661    @Override
662    public String getColor() throws AmetysRepositoryException
663    {
664        try
665        {
666            return getNode().getProperty(PROPERTY_COLOR).getString();
667        }
668        catch (PathNotFoundException e)
669        {
670            return null;
671        }
672        catch (RepositoryException e)
673        {
674            throw new AmetysRepositoryException("Error getting the color property.", e);
675        }
676    }
677
678    @Override
679    public void setColor(String color) throws AmetysRepositoryException
680    {
681        try
682        {
683            getNode().setProperty(PROPERTY_COLOR, color);
684        }
685        catch (RepositoryException e)
686        {
687            throw new AmetysRepositoryException("Error setting the color property.", e);
688        }
689    }
690    
691    @Override
692    public String getPage() throws AmetysRepositoryException
693    {
694        try
695        {
696            return getNode().getProperty(PROPERTY_PAGE).getString();
697        }
698        catch (PathNotFoundException e)
699        {
700            return null;
701        }
702        catch (RepositoryException e)
703        {
704            throw new AmetysRepositoryException("Error getting the page property.", e);
705        }
706    }
707
708    @Override
709    public void setPage(String page) throws AmetysRepositoryException
710    {
711        try
712        {
713            getNode().setProperty(PROPERTY_PAGE, page);
714        }
715        catch (RepositoryException e)
716        {
717            throw new AmetysRepositoryException("Error setting the page property.", e);
718        }
719    }
720    
721    @Override
722    public LinkStatus getStatus() throws AmetysRepositoryException
723    {
724        try
725        {
726            return LinkStatus.valueOf(getNode().getProperty(PROPERTY_STATUS).getString());
727        }
728        catch (PathNotFoundException e)
729        {
730            return null;
731        }
732        catch (RepositoryException e)
733        {
734            throw new AmetysRepositoryException("Error getting the status property.", e);
735        }
736    }
737
738    @Override
739    public void setStatus(LinkStatus status) throws AmetysRepositoryException
740    {
741        try
742        {
743            getNode().setProperty(PROPERTY_STATUS, status.name());
744        }
745        catch (RepositoryException e)
746        {
747            throw new AmetysRepositoryException("Error setting the status property.", e);
748        }
749    }
750    
751    @Override
752    public LinkVisibility getDefaultVisibility() throws AmetysRepositoryException
753    {
754        try
755        {
756            Node node = getNode();
757            if (node.hasProperty(PROPERTY_DEFAULT_VISIBILITY))
758            {
759                return LinkVisibility.valueOf(getNode().getProperty(PROPERTY_DEFAULT_VISIBILITY).getString());
760            }
761            else
762            {
763                return LinkVisibility.VISIBLE;
764            }
765        }
766        catch (RepositoryException e)
767        {
768            throw new AmetysRepositoryException("Error getting the visibility property.", e);
769        }
770    }
771
772    @Override
773    public void setDefaultVisibility(LinkVisibility visibility) throws AmetysRepositoryException
774    {
775        try
776        {
777            getNode().setProperty(PROPERTY_DEFAULT_VISIBILITY, visibility.name());
778        }
779        catch (RepositoryException e)
780        {
781            throw new AmetysRepositoryException("Error setting the visibility property.", e);
782        }
783    }
784    
785    public ModifiableModelLessDataHolder getDataHolder()
786    {
787        JCRRepositoryData repositoryData = new JCRRepositoryData(getNode());
788        return new DefaultModifiableModelLessDataHolder(_getFactory().getLinkDataTypeExtensionPoint(), repositoryData);
789    }
790}