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