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        removeValue(PROPERTY_PICTURE);
505    }
506
507    @Override
508    public void orderBefore(AmetysObject siblingObject) throws AmetysRepositoryException
509    {
510        if (siblingObject instanceof DefaultLink)
511        {
512            DefaultLink sibling = (DefaultLink) siblingObject;
513            sibling.getPath();
514            Node node = getNode();
515            String siblingPath = "";
516            try
517            {
518                siblingPath = sibling.getNode().getPath();
519                if (siblingPath.contains("/"))
520                {
521                    siblingPath = StringUtils.substringAfterLast(siblingPath, "/");
522                }
523                String path = node.getPath();
524                if (path.contains("/"))
525                {
526                    path = StringUtils.substringAfterLast(path, "/");
527                }
528                node.getParent().orderBefore(path, siblingPath);
529            }
530            catch (RepositoryException e)
531            {
532                throw new AmetysRepositoryException(String.format("Unable to order link '%s' before link '%s'", this, siblingPath), e);
533            }
534        }
535        else
536        {
537            throw new AmetysRepositoryException("A link can only be moved before another link.");
538        }
539    }
540
541    @Override
542    public void moveTo(AmetysObject newParent, boolean renameIfExist) throws AmetysRepositoryException, RepositoryIntegrityViolationException
543    {
544        if (getParent().equals(newParent))
545        {
546            try
547            {
548                // Just order current node to the end.
549                Node node = getNode();
550                String path = node.getPath();
551                if (path.contains("/"))
552                {
553                    path = StringUtils.substringAfterLast(path, "/");
554                }
555                node.getParent().orderBefore(path, null);
556            }
557            catch (RepositoryException e)
558            {
559                throw new AmetysRepositoryException(String.format("Unable to move link '%s' outside the root link node.", this), e);
560            }
561        }
562        else
563        {
564            throw new AmetysRepositoryException("A link can only be moved before another link.");
565        }
566    }
567
568    @Override
569    public boolean canMoveTo(AmetysObject newParent) throws AmetysRepositoryException
570    {
571        return getParent().equals(newParent);
572    }
573
574    /**
575     * Retrieves the list of values corresponding to the metadata name passed as
576     * parameter
577     * 
578     * @param metadataName the name of the metadata to retrieve
579     * @return the list corresponding to the metadata name
580     */
581    private String[] _getListFromMetadataName(String metadataName)
582    {
583        try
584        {
585            if (getNode().hasProperty(metadataName))
586            {
587                Value[] values = getNode().getProperty(metadataName).getValues();
588
589                String[] list = new String[values.length];
590
591                for (int i = 0; i < values.length; i++)
592                {
593                    list[i] = values[i].getString();
594                }
595
596                return list;
597            }
598            else
599            {
600                return new String[0];
601            }
602        }
603        catch (RepositoryException e)
604        {
605            throw new AmetysRepositoryException("An error occurred while trying to get the property '" + metadataName + "'.", e);
606        }
607    }
608
609    /**
610     * Sets the list of values to the node corresponding to the metadata name
611     * passed as a parameter
612     * 
613     * @param list the list of value to be set
614     * @param metadataName the name of the metadata
615     */
616    private void _setListWithMetadataName(String[] list, String metadataName)
617    {
618        try
619        {
620            getNode().setProperty(metadataName, list);
621        }
622        catch (RepositoryException e)
623        {
624            throw new AmetysRepositoryException("An error occurred while trying to set the property '" + metadataName + "'.", e);
625        }
626    }
627
628    @Override
629    public String getDynamicInformationProvider() throws AmetysRepositoryException
630    {
631        try
632        {
633            return getNode().getProperty(PROPERTY_DYNAMIC_INFO_PROVIDER).getString();
634        }
635        catch (PathNotFoundException e)
636        {
637            return null;
638        }
639        catch (RepositoryException e)
640        {
641            throw new AmetysRepositoryException("Error getting the URL property.", e);
642        }
643    }
644
645    @Override
646    public void setDynamicInformationProvider(String providerId) throws AmetysRepositoryException
647    {
648        try
649        {
650            getNode().setProperty(PROPERTY_DYNAMIC_INFO_PROVIDER, providerId);
651        }
652        catch (RepositoryException e)
653        {
654            throw new AmetysRepositoryException("Error setting the URL property.", e);
655        }
656    }
657    
658    @Override
659    public String getColor() throws AmetysRepositoryException
660    {
661        try
662        {
663            return getNode().getProperty(PROPERTY_COLOR).getString();
664        }
665        catch (PathNotFoundException e)
666        {
667            return null;
668        }
669        catch (RepositoryException e)
670        {
671            throw new AmetysRepositoryException("Error getting the color property.", e);
672        }
673    }
674
675    @Override
676    public void setColor(String color) throws AmetysRepositoryException
677    {
678        try
679        {
680            getNode().setProperty(PROPERTY_COLOR, color);
681        }
682        catch (RepositoryException e)
683        {
684            throw new AmetysRepositoryException("Error setting the color property.", e);
685        }
686    }
687    
688    @Override
689    public String getPage() throws AmetysRepositoryException
690    {
691        try
692        {
693            return getNode().getProperty(PROPERTY_PAGE).getString();
694        }
695        catch (PathNotFoundException e)
696        {
697            return null;
698        }
699        catch (RepositoryException e)
700        {
701            throw new AmetysRepositoryException("Error getting the page property.", e);
702        }
703    }
704
705    @Override
706    public void setPage(String page) throws AmetysRepositoryException
707    {
708        try
709        {
710            getNode().setProperty(PROPERTY_PAGE, page);
711        }
712        catch (RepositoryException e)
713        {
714            throw new AmetysRepositoryException("Error setting the page property.", e);
715        }
716    }
717    
718    @Override
719    public LinkStatus getStatus() throws AmetysRepositoryException
720    {
721        try
722        {
723            return LinkStatus.valueOf(getNode().getProperty(PROPERTY_STATUS).getString());
724        }
725        catch (PathNotFoundException e)
726        {
727            return null;
728        }
729        catch (RepositoryException e)
730        {
731            throw new AmetysRepositoryException("Error getting the status property.", e);
732        }
733    }
734
735    @Override
736    public void setStatus(LinkStatus status) throws AmetysRepositoryException
737    {
738        try
739        {
740            getNode().setProperty(PROPERTY_STATUS, status.name());
741        }
742        catch (RepositoryException e)
743        {
744            throw new AmetysRepositoryException("Error setting the status property.", e);
745        }
746    }
747    
748    public ModifiableModelLessDataHolder getDataHolder()
749    {
750        JCRRepositoryData repositoryData = new JCRRepositoryData(getNode());
751        return new DefaultModifiableModelLessDataHolder(_getFactory().getLinkDataTypeExtensionPoint(), repositoryData);
752    }
753}