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