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