001/*
002 *  Copyright 2018 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.repository.data.repositorydata.impl;
017
018import java.io.InputStream;
019import java.util.ArrayList;
020import java.util.Calendar;
021import java.util.Date;
022import java.util.GregorianCalendar;
023import java.util.LinkedHashMap;
024import java.util.LinkedHashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import javax.jcr.Binary;
030import javax.jcr.Node;
031import javax.jcr.NodeIterator;
032import javax.jcr.PathNotFoundException;
033import javax.jcr.Property;
034import javax.jcr.PropertyIterator;
035import javax.jcr.PropertyType;
036import javax.jcr.RepositoryException;
037import javax.jcr.Session;
038import javax.jcr.Value;
039import javax.jcr.lock.Lock;
040import javax.jcr.lock.LockManager;
041
042import org.apache.commons.lang3.StringUtils;
043
044import org.ametys.plugins.repository.AmetysObjectResolver;
045import org.ametys.plugins.repository.AmetysRepositoryException;
046import org.ametys.plugins.repository.RepositoryConstants;
047import org.ametys.plugins.repository.data.DataComment;
048import org.ametys.plugins.repository.data.UnknownDataException;
049import org.ametys.plugins.repository.data.repositorydata.ModifiableCommentableRepositoryData;
050import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
051import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
052import org.ametys.plugins.repository.jcr.NodeHelper;
053import org.ametys.plugins.repository.jcr.NodeTypeHelper;
054
055/**
056 * Class for data values management in JCR repository
057 */
058public class JCRRepositoryData implements ModifiableRepositoryData, ModifiableCommentableRepositoryData
059{
060    private static final Map<Integer, String> __DATA_TYPES;
061    
062    static
063    {
064        __DATA_TYPES = new LinkedHashMap<>(6);
065        __DATA_TYPES.put(PropertyType.STRING, RepositoryData.STRING_REPOSITORY_DATA_TYPE);
066        __DATA_TYPES.put(PropertyType.LONG, RepositoryData.LONG_REPOSITORY_DATA_TYPE);
067        __DATA_TYPES.put(PropertyType.DOUBLE, RepositoryData.DOUBLE_REPOSITORY_DATA_TYPE);
068        __DATA_TYPES.put(PropertyType.BOOLEAN, RepositoryData.BOOLEAN_REPOSITORY_DATA_TYPE);
069        __DATA_TYPES.put(PropertyType.DATE, RepositoryData.CALENDAR_REPOSITORY_DATA_TYPE);
070        __DATA_TYPES.put(PropertyType.BINARY, RepositoryData.STREAM_REPOSITORY_DATA_TYPE);
071    }
072    
073    private Node _node;
074    private boolean _lockAlreadyChecked;
075    private String _defaultPrefix;
076    
077    /**
078     * Creates a JCR repository data from the given node
079     * @param node the Node supporting this repository data
080     */
081    public JCRRepositoryData(Node node)
082    {
083        this(node, RepositoryConstants.NAMESPACE_PREFIX);
084    }
085    
086    /**
087     * Creates a JCR repository data from the given node
088     * @param node the Node supporting this repository data
089     * @param defaultPrefix prefix to use for properties and child nodes
090     */
091    public JCRRepositoryData(Node node, String defaultPrefix)
092    {
093        _node = node;
094        _defaultPrefix = defaultPrefix;
095    }
096
097    public String getString(String name, String prefix)
098    {
099        try
100        {
101            Value value = _getValue(name, prefix);
102            return value.getString();
103        }
104        catch (RepositoryException e)
105        {
106            throw new AmetysRepositoryException("Unable to get the value of string data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
107        }
108    }
109    
110    public String[] getStrings(String name, String prefix)
111    {
112        try
113        {
114            Value[] values = _getValues(name, prefix);
115            String[] results = new String[values.length];
116            
117            for (int i = 0; i < values.length; i++)
118            {
119                Value value = values[i];
120                results[i] = value.getString();
121            }
122          
123            return results;
124        }
125        catch (RepositoryException e)
126        {
127            throw new AmetysRepositoryException("Unable to get the values of multiple string data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
128        }
129    }
130    
131    public Calendar getDate(String name, String prefix)
132    {
133        try
134        {
135            Value value = _getValue(name, prefix);
136            return value.getDate();
137        }
138        catch (RepositoryException e)
139        {
140            throw new AmetysRepositoryException("Unable to get the value of date data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
141        }
142    }
143
144    public Calendar[] getDates(String name, String prefix)
145    {
146        try
147        {
148            Value[] values = _getValues(name, prefix);
149            Calendar[] results = new Calendar[values.length];
150
151            for (int i = 0; i < values.length; i++)
152            {
153                Value value = values[i];
154                results[i] = value.getDate();
155            }
156          
157            return results;
158        }
159        catch (RepositoryException e)
160        {
161            throw new AmetysRepositoryException("Unable to get the values of multiple date data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
162        }
163    }
164    
165    public Long getLong(String name, String prefix)
166    {
167        try
168        {
169            Value value = _getValue(name, prefix);
170            return value.getLong();
171        }
172        catch (RepositoryException e)
173        {
174            throw new AmetysRepositoryException("Unable to get the value on long data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
175        }
176    }
177    
178    public Long[] getLongs(String name, String prefix)
179    {
180        try
181        {
182            Value[] values = _getValues(name, prefix);
183            Long[] results = new Long[values.length];
184            
185            for (int i = 0; i < values.length; i++)
186            {
187                Value value = values[i];
188                results[i] = value.getLong();
189            }
190          
191            return results;
192        }
193        catch (RepositoryException e)
194        {
195            throw new AmetysRepositoryException("Unable to get the values of multiple long data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
196        }
197    }
198    
199    public Double getDouble(String name, String prefix)
200    {
201        try
202        {
203            Value value = _getValue(name, prefix);
204            return value.getDouble();
205        }
206        catch (RepositoryException e)
207        {
208            throw new AmetysRepositoryException("Unable to get the value of double data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
209        }
210    }
211    
212    public Double[] getDoubles(String name, String prefix)
213    {
214        try
215        {
216            Value[] values = _getValues(name, prefix);
217            Double[] results = new Double[values.length];
218            
219            for (int i = 0; i < values.length; i++)
220            {
221                Value value = values[i];
222                results[i] = value.getDouble();
223            }
224          
225            return results;
226        }
227        catch (RepositoryException e)
228        {
229            throw new AmetysRepositoryException("Unable to get the values of multiple double data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
230        }
231    }
232    
233    public Boolean getBoolean(String name, String prefix)
234    {
235        try
236        {
237            Value value = _getValue(name, prefix);
238            return value.getBoolean();
239        }
240        catch (RepositoryException e)
241        {
242            throw new AmetysRepositoryException("Unable to get the value of boolean data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
243        }
244    }
245    
246    public Boolean[] getBooleans(String name, String prefix)
247    {
248        try
249        {
250            Value[] values = _getValues(name, prefix);
251            Boolean[] results = new Boolean[values.length];
252            
253            for (int i = 0; i < values.length; i++)
254            {
255                Value value = values[i];
256                results[i] = value.getBoolean();
257            }
258          
259            return results;
260        }
261        catch (RepositoryException e)
262        {
263            throw new AmetysRepositoryException("Unable to get the values of multiple boolean data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
264        }
265    }
266    
267    public InputStream[] getStreams(String name, String prefix)
268    {
269        try
270        {
271            Value[] values = _getValues(name, prefix);
272            InputStream[] results = new InputStream[values.length];
273            
274            for (int i = 0; i < values.length; i++)
275            {
276                Value value = values[i];
277                results[i] = value.getBinary().getStream();
278            }
279          
280            return results;
281        }
282        catch (RepositoryException e)
283        {
284            throw new AmetysRepositoryException("Unable to get the values of multiple stream data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
285        }
286    }
287    
288    public InputStream getStream(String name, String prefix)
289    {
290        try
291        {
292            Value value = _getValue(name, prefix);
293            return value.getBinary().getStream();
294        }
295        catch (RepositoryException e)
296        {
297            throw new AmetysRepositoryException("Unable to get the value of stream data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
298        }
299    }
300    
301    public Long getStreamLength(String name, String prefix)
302    {
303        try
304        {
305            return _node.getProperty(_getFullName(name, prefix)).getLength();
306        }
307        catch (RepositoryException e)
308        {
309            throw new AmetysRepositoryException("Unable to get the length of the value of stream data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
310        }
311    }
312    
313    private Value _getValue(String name, String prefix)
314    {
315        try
316        {
317            Property property = _node.getProperty(_getFullName(name, prefix));
318
319            if (property.getDefinition().isMultiple())
320            {
321                Value[] values = property.getValues();
322
323                if (values.length > 0)
324                {
325                    return values[0];
326                }
327                else
328                {
329                    throw new AmetysRepositoryException("Cannot get the value of data '" + _getFullName(name, prefix) + "' from an empty array in repository data at path '" + _getPathForLogs() + "'.");
330                }
331            }
332
333            return property.getValue();
334        }
335        catch (PathNotFoundException e)
336        {
337            throw new UnknownDataException("Unknown data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
338        }
339        catch (RepositoryException e)
340        {
341            throw new AmetysRepositoryException("Unable to get the value of data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
342        }
343    }
344    
345    private Value[] _getValues(String name, String prefix)
346    {
347        try
348        {
349            Property property = _node.getProperty(_getFullName(name, prefix));
350
351            if (property.getDefinition().isMultiple())
352            {
353                return property.getValues();
354            }
355
356            Value value = property.getValue();
357            return new Value[] {value};
358        }
359        catch (PathNotFoundException e)
360        {
361            throw new UnknownDataException("Unknown data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
362        }
363        catch (RepositoryException e)
364        {
365            throw new AmetysRepositoryException("Unable to get the multiple value of data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
366        }
367    }
368    
369    public ModifiableRepositoryData getRepositoryData(String name, String prefix)
370    {
371        try
372        {
373            if (_node.hasNode(_getFullName(name, prefix)))
374            {
375                Node node = _node.getNode(_getFullName(name, prefix));
376                return new JCRRepositoryData(node);
377            }
378            else
379            {
380                throw new UnknownDataException("Unknown repository data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.");
381            }
382        }
383        catch (RepositoryException e)
384        {
385            throw new AmetysRepositoryException("Unable to get the repository data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
386        }
387    }
388
389    public Set<String> getDataNames(String prefix) throws AmetysRepositoryException
390    {
391        final String finalPrefix;
392        if (prefix == null)
393        {
394            finalPrefix = "*:";
395        }
396        else if (StringUtils.isEmpty(prefix))
397        {
398            finalPrefix = prefix;
399        }
400        else
401        {
402            finalPrefix = prefix + ":";
403        }
404        
405        try
406        {
407            PropertyIterator propertiesIterator = _node.getProperties(finalPrefix + "*");
408            NodeIterator itNode = _node.getNodes(finalPrefix + "*");
409
410            Set<String> names = new LinkedHashSet<>();
411
412            while (propertiesIterator.hasNext())
413            {
414                Property property = propertiesIterator.nextProperty();
415                String propertyName = property.getName();
416                
417                if (!StringUtils.isEmpty(finalPrefix) || !propertyName.contains(":"))
418                {
419                    names.add(propertyName.substring(finalPrefix.length()));
420                }
421            }
422
423            while (itNode.hasNext())
424            {
425                Node node = itNode.nextNode();
426                
427                // Filtering out AmetysObjects: they're not data (except users).
428                if (!NodeTypeHelper.isNodeType(node, AmetysObjectResolver.OBJECT_TYPE) || NodeTypeHelper.isNodeType(node, RepositoryConstants.USER_NODETYPE))
429                {
430                    names.add(node.getName().substring(finalPrefix.length()));
431                }
432            }
433
434            return names;
435        }
436        catch (RepositoryException e)
437        {
438            throw new AmetysRepositoryException("Unable to get the data names of the repository data at path '" + _getPathForLogs() + "'.", e);
439        }
440    }
441    
442    public String getFullName()
443    {
444        try
445        {
446            return _node.getName();
447        }
448        catch (RepositoryException e)
449        {
450            throw new AmetysRepositoryException("Unable to get the name of the repository data at path '" + _getPathForLogs() + "'.", e);
451        }
452    }
453    
454    public boolean hasValue(String name, String prefix)
455    {
456        try
457        {
458            return _node.hasProperty(_getFullName(name, prefix)) || _node.hasNode(_getFullName(name, prefix));
459        }
460        catch (RepositoryException e)
461        {
462            throw new AmetysRepositoryException("Unable to determine if there is a value for data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
463        }
464    }
465    
466    public String getType(String name, String prefix)
467    {
468        try
469        {
470            String dataName = _getFullName(name, prefix);
471            
472            if (_node.hasProperty(dataName))
473            {
474                Property property = _node.getProperty(dataName);
475                int jcrType = property.getType();
476                
477                String dataType = __DATA_TYPES.get(jcrType);
478                if (dataType == null)
479                {
480                    throw new AmetysRepositoryException("Unable to get the type of data '" + dataName + "' in repository data at path '" + _getPathForLogs() + "'.");
481                }
482                
483                return dataType;
484            }
485            
486            if (_node.hasNode(dataName))
487            {
488                return _node.getNode(dataName).getPrimaryNodeType().getName();
489            }
490            
491            throw new UnknownDataException("No data found for '" + dataName + "' in repository data at path '" + _getPathForLogs() + "'.");
492        }
493        catch (RepositoryException e)
494        {
495            throw new AmetysRepositoryException("Unable to get the type of data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
496        }
497    }
498
499    public boolean isMultiple(String name, String prefix)
500    {
501        try
502        {
503            String dataName = _getFullName(name, prefix);
504            
505            if (_node.hasProperty(dataName))
506            {
507                Property property = _node.getProperty(dataName);
508    
509                if (property.getDefinition().isMultiple())
510                {
511                    return true;
512                }
513    
514                return false;
515            }
516            
517            if (_node.hasNode(dataName))
518            {
519                String nodeName = _node.getNode(dataName).getPrimaryNodeType().getName();
520                return RepositoryConstants.MULTIPLE_ITEM_NODETYPE.equals(nodeName);
521            }
522            
523            throw new UnknownDataException("No data found for '" + dataName + "' in repository data at path '" + _getPathForLogs() + "'.");
524        }
525        catch (RepositoryException e)
526        {
527            throw new AmetysRepositoryException("Unable to determine if data '" + _getFullName(name, prefix) + "' is multiple in repository data at path '" + _getPathForLogs() + "'.", e);
528        }
529    }
530    
531    public void rename(String newName, String prefix)
532    {
533        NodeHelper.rename(_node, prefix + ":" + newName);
534    }
535    
536    public void setValue(String name, String value, String prefix)
537    {
538        if (value == null)
539        {
540            throw new NullPointerException("Unable to set a 'null' data for '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.");
541        }
542
543        _checkDataName(name, prefix);
544
545        try
546        {
547            _checkLock();
548            _node.setProperty(_getFullName(name, prefix), value);
549        }
550        catch (RepositoryException e)
551        {
552            throw new AmetysRepositoryException("Unable to set the value '" + value + "' for data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
553        }
554    }
555    
556    public void setValues(String name, String[] values, String prefix)
557    {
558        if (values == null)
559        {
560            throw new NullPointerException("Unable to set a 'null' data for '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.");
561        }
562
563        _checkDataName(name, prefix);
564
565        try
566        {
567            _checkLock();
568            _node.setProperty(_getFullName(name, prefix), values);
569        }
570        catch (RepositoryException e)
571        {
572            throw new AmetysRepositoryException("Unable to set the values '" + values + "' for data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
573        }
574    }
575    
576    public void setValue(String name, Calendar value, String prefix)
577    {
578        if (value == null)
579        {
580            throw new NullPointerException("Unable to set a 'null' data for '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.");
581        }
582
583        _checkDataName(name, prefix);
584
585        try
586        {
587            _checkLock();
588            _node.setProperty(_getFullName(name, prefix), value);
589        }
590        catch (RepositoryException e)
591        {
592            throw new AmetysRepositoryException("Unable to set the value '" + value + "' for data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
593        }
594    }
595    
596    public void setValues(String name, Calendar[] values, String prefix)
597    {
598        if (values == null)
599        {
600            throw new NullPointerException("Unable to set a 'null' data for '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.");
601        }
602
603        _checkDataName(name, prefix);
604
605        try
606        {
607            _checkLock();
608            
609            Value[] jcrValues = new Value[values.length];
610            for (int i = 0; i < values.length; i++)
611            {
612                Calendar value = values[i];
613                jcrValues[i] = _node.getSession().getValueFactory().createValue(value);
614            }
615            
616            _node.setProperty(_getFullName(name, prefix), jcrValues);
617        }
618        catch (RepositoryException e)
619        {
620            throw new AmetysRepositoryException("Unable to set the values '" + values + "' for data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
621        }
622    }
623    
624    public void setValue(String name, Long value, String prefix)
625    {
626        if (value == null)
627        {
628            throw new NullPointerException("Unable to set a 'null' data for '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.");
629        }
630
631        _checkDataName(name, prefix);
632
633        try
634        {
635            _checkLock();
636            _node.setProperty(_getFullName(name, prefix), value);
637        }
638        catch (RepositoryException e)
639        {
640            throw new AmetysRepositoryException("Unable to set the value '" + value + "' for data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
641        }
642    }
643    
644    public void setValues(String name, Long[] values, String prefix)
645    {
646        if (values == null)
647        {
648            throw new NullPointerException("Unable to set a 'null' data for '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.");
649        }
650
651        _checkDataName(name, prefix);
652
653        try
654        {
655            _checkLock();
656            
657            Value[] jcrValues = new Value[values.length];
658            for (int i = 0; i < values.length; i++)
659            {
660                long value = values[i];
661                jcrValues[i] = _node.getSession().getValueFactory().createValue(value);
662            }
663            
664            _node.setProperty(_getFullName(name, prefix), jcrValues);
665        }
666        catch (RepositoryException e)
667        {
668            throw new AmetysRepositoryException("Unable to set the values '" + values + "' for data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
669        }
670    }
671    
672    public void setValue(String name, Double value, String prefix)
673    {
674        if (value == null)
675        {
676            throw new NullPointerException("Unable to set a 'null' data for '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.");
677        }
678
679        _checkDataName(name, prefix);
680
681        try
682        {
683            _checkLock();
684            _node.setProperty(_getFullName(name, prefix), value);
685        }
686        catch (RepositoryException e)
687        {
688            throw new AmetysRepositoryException("Unable to set the value '" + value + "' for data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
689        }
690    }
691    
692    public void setValues(String name, Double[] values, String prefix)
693    {
694        if (values == null)
695        {
696            throw new NullPointerException("Unable to set a 'null' data for '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.");
697        }
698
699        _checkDataName(name, prefix);
700
701        try
702        {
703            _checkLock();
704            
705            Value[] jcrValues = new Value[values.length];
706            for (int i = 0; i < values.length; i++)
707            {
708                double value = values[i];
709                jcrValues[i] = _node.getSession().getValueFactory().createValue(value);
710            }
711            
712            _node.setProperty(_getFullName(name, prefix), jcrValues);
713        }
714        catch (RepositoryException e)
715        {
716            throw new AmetysRepositoryException("Unable to set the values '" + values + "' for data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
717        }
718    }
719    
720    public void setValue(String name, Boolean value, String prefix)
721    {
722        if (value == null)
723        {
724            throw new NullPointerException("Unable to set a 'null' data for '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.");
725        }
726
727        _checkDataName(name, prefix);
728
729        try
730        {
731            _checkLock();
732            _node.setProperty(_getFullName(name, prefix), value);
733        }
734        catch (RepositoryException e)
735        {
736            throw new AmetysRepositoryException("Unable to set the value '" + value + "' for data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
737        }
738    }
739    
740    public void setValues(String name, Boolean[] values, String prefix)
741    {
742        if (values == null)
743        {
744            throw new NullPointerException("Unable to set a 'null' data for '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.");
745        }
746
747        _checkDataName(name, prefix);
748
749        try
750        {
751            _checkLock();
752            
753            Value[] jcrValues = new Value[values.length];
754            for (int i = 0; i < values.length; i++)
755            {
756                boolean value = values[i];
757                jcrValues[i] = _node.getSession().getValueFactory().createValue(value);
758            }
759            
760            _node.setProperty(_getFullName(name, prefix), jcrValues);
761        }
762        catch (RepositoryException e)
763        {
764            throw new AmetysRepositoryException("Unable to set the values '" + values + "' for data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
765        }
766    }
767    
768    public void setValue(String name, InputStream value, String prefix)
769    {
770        if (value == null)
771        {
772            throw new NullPointerException("Unable to set a 'null' data for '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.");
773        }
774
775        _checkDataName(name, prefix);
776
777        try
778        {
779            _checkLock();
780            Binary binary = _node.getSession().getValueFactory().createBinary(value);
781            _node.setProperty(_getFullName(name, prefix), binary);
782        }
783        catch (RepositoryException e)
784        {
785            throw new AmetysRepositoryException("Unable to set the value '" + value + "' for data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
786        }
787    }
788    
789    public void setValues(String name, InputStream[] values, String prefix)
790    {
791        if (values == null)
792        {
793            throw new NullPointerException("Unable to set a 'null' data for '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.");
794        }
795
796        _checkDataName(name, prefix);
797
798        try
799        {
800            _checkLock();
801            
802            Value[] jcrValues = new Value[values.length];
803            for (int i = 0; i < values.length; i++)
804            {
805                InputStream value = values[i];
806                Binary binary = _node.getSession().getValueFactory().createBinary(value);
807                jcrValues[i] = _node.getSession().getValueFactory().createValue(binary);
808            }
809            
810            _node.setProperty(_getFullName(name, prefix), jcrValues);
811        }
812        catch (RepositoryException e)
813        {
814            throw new AmetysRepositoryException("Unable to set the values '" + values + "' for data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
815        }
816    }
817    
818    public ModifiableRepositoryData addRepositoryData(String name, String dataTypeName, String prefix)
819    {
820        try
821        {
822            _checkLock();
823            Node node = _node.addNode(_getFullName(name, prefix), dataTypeName);
824            return new JCRRepositoryData(node);
825        }
826        catch (RepositoryException e)
827        {
828            throw new AmetysRepositoryException("Unable to create repository data '" + _getFullName(name, prefix) + "' with type '" + dataTypeName + "' in repository data at path '" + _getPathForLogs() + "'.", e);
829        }
830    }
831
832    private void _checkDataName(String name, String prefix)
833    {
834        // Name null
835        if (name == null)
836        {
837            throw new AmetysRepositoryException("Unable to set a value with the invalid data name (null) in repository data at path '" + _getPathForLogs() + "'.");
838        }
839
840        // Valid name
841        if (!DATA_NAME_PATTERN.matcher(name).matches())
842        {
843            throw new AmetysRepositoryException("Unable to set a value with the invalid data name '" + name + "'. Only [a-zA-Z][a-zA-Z0-9-_]* are allowed.");
844        }
845        
846        // Has value
847        if (hasValue(name, prefix))
848        {
849            // Remove existing value to avoid values with same name (node + property)
850            removeValue(name, prefix);
851        }
852    }
853    
854    public void removeValue(String name, String prefix)
855    {
856        try
857        {
858            _checkLock();
859            String dataName = _getFullName(name, prefix);
860            
861            if (_node.hasProperty(dataName))
862            {
863                _node.getProperty(dataName).remove();
864            }
865            else if (_node.hasNode(dataName))
866            {
867                // Remove all existing nodes
868                NodeIterator it = _node.getNodes(dataName);
869                while (it.hasNext())
870                {
871                    Node next = it.nextNode();
872                    next.remove();
873                }
874            }
875            else
876            {
877                throw new UnknownDataException("No data found for '" + dataName + "' in repository data at path '" + _getPathForLogs() + "'.");
878            }
879        }
880        catch (RepositoryException e)
881        {
882            throw new AmetysRepositoryException("Unable to remove data '" + _getFullName(name, prefix) + "' in repository data at path '" + _getPathForLogs() + "'.", e);
883        }
884    }
885    
886    private String _getFullName(String name, String prefix)
887    {
888        return StringUtils.isEmpty(prefix) ? name : prefix + ":" + name;
889    }
890
891    public List<DataComment> getComments(String name)
892    {
893        List<DataComment> comments = new ArrayList<>();
894        String commentsNodeName = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + name + RepositoryConstants.COMMENTS_SUFFIX;
895        
896        try
897        {
898            if (_node.hasNode(commentsNodeName))
899            {
900                Node commentsNode = _node.getNode(commentsNodeName);
901                NodeIterator iterator = commentsNode.getNodes();
902                
903                while (iterator.hasNext())
904                {
905                    Node node = iterator.nextNode();
906                    
907                    String text = node.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + "comment").getString();
908                    Date date = node.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + "date").getDate().getTime();
909                    String author = node.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + "author").getString();
910                    
911                    comments.add(new DataComment(text, date, author));
912                }
913            }
914            
915            return comments;
916        }
917        catch (RepositoryException e)
918        {
919            throw new AmetysRepositoryException("Unable to retrieve comments of data '" + name + "' in repository data at path '" + _getPathForLogs() + "'.", e);
920        }
921    }
922    
923    public boolean hasComments(String name)
924    {
925        String commentsNodeName = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + name + RepositoryConstants.COMMENTS_SUFFIX;
926        
927        try
928        {
929            if (_node.hasNode(commentsNodeName))
930            {
931                Node commentsNode = _node.getNode(commentsNodeName);
932                return commentsNode.getNodes().hasNext();
933            }
934            
935            return false;
936        }
937        catch (RepositoryException e)
938        {
939            throw new AmetysRepositoryException("Unable to determine if the repository at path '" + _getPathForLogs() + "' has at least one comment for data '" + name + "'.", e);
940        }
941    }
942
943    public boolean hasComment(String name, int index)
944    {
945        String commentNodeName = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + name + RepositoryConstants.COMMENTS_SUFFIX + '/' + RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + index;
946        try
947        {
948            return _node.hasNode(commentNodeName);
949        }
950        catch (RepositoryException e)
951        {
952            throw new AmetysRepositoryException("Unable to determine if the repository at path '" + _getPathForLogs() + "' has a comment for data '" + name + "' at index '" + index + "'.", e);
953        }
954    }
955
956    public void addComment(String name, String text, String author, Date date)
957    {
958        if (text == null)
959        {
960            throw new NullPointerException("Unable to add a comment to data '" + name + "' in repository data at path '" + _getPathForLogs() + "'. Text of the comment cannot be null.");
961        }
962        
963        String commentsNodeName = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + name + RepositoryConstants.COMMENTS_SUFFIX;
964        
965        try
966        {
967            _checkLock();
968            if (!_node.hasNode(commentsNodeName))
969            {
970                _node.addNode(commentsNodeName, "ametys:compositeMetadata");
971            }
972            
973            Node commentsNode = _node.getNode(commentsNodeName);
974            String nodeName = null;
975            int i = 0;
976            
977            // Loop over existing comments
978            do
979            {
980                nodeName = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + ++i;
981            } while (commentsNode.hasNode(nodeName));
982            
983            // Add the new comment node at the end
984            Node commentNode = commentsNode.addNode(nodeName, "ametys:compositeMetadata");
985            
986            GregorianCalendar calendar = new GregorianCalendar();
987            if (date != null)
988            {
989                calendar.setTime(date);
990            }
991            
992            commentNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + "comment", text);
993            commentNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + "date", calendar);
994            commentNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + "author", author);
995        }
996        catch (RepositoryException e)
997        {
998            throw new AmetysRepositoryException("Unable to add a comment to data '" + name + "' in repository data at path '" + _getPathForLogs() + "'.", e);
999        }
1000    }
1001
1002    public void editComment(String name, int index, String text, String author, Date date)
1003    {
1004        if (text == null)
1005        {
1006            throw new NullPointerException("Unable to edit the comment at index '" + index + "' of data '" + name + "' in repository data at path '" + _getPathForLogs() + "'. Text of the comment cannot be null.");
1007        }
1008        
1009        String commentNodeName = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + name + RepositoryConstants.COMMENTS_SUFFIX + '/' + RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + index;
1010        
1011        try
1012        {
1013            _checkLock();
1014            Node commentNode = _node.getNode(commentNodeName);
1015            
1016            GregorianCalendar calendar = new GregorianCalendar();
1017            if (date != null)
1018            {
1019                calendar.setTime(date);
1020            }
1021            
1022            commentNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + "comment", text);
1023            commentNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + "date", calendar);
1024            commentNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + "author", author);
1025        }
1026        catch (RepositoryException e)
1027        {
1028            throw new AmetysRepositoryException("Unable to edit the comment at index '" + index + "' of data '" + name + "' in repository data at path '" + _getPathForLogs() + "'.", e);
1029        }
1030    }
1031
1032    public void deleteComment(String name, int index)
1033    {
1034        String commentNodeName = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + name + RepositoryConstants.COMMENTS_SUFFIX + '/' + RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + index;
1035        
1036        try
1037        {
1038            _checkLock();
1039            _node.getNode(commentNodeName).remove();
1040        }
1041        catch (RepositoryException e)
1042        {
1043            throw new AmetysRepositoryException("Unable to remove the comment at index '" + index + "' of data '" + name + "' in repository data at path '" + _getPathForLogs() + "'.", e);
1044        }
1045    }
1046    
1047    private void _checkLock() throws RepositoryException
1048    {
1049        if (!_lockAlreadyChecked && _node.isLocked())
1050        {
1051            LockManager lockManager = _node.getSession().getWorkspace().getLockManager();
1052            
1053            Lock lock = lockManager.getLock(_node.getPath());
1054            Node lockHolder = lock.getNode();
1055            
1056            lockManager.addLockToken(lockHolder.getProperty(RepositoryConstants.METADATA_LOCKTOKEN).getString());
1057            _lockAlreadyChecked = true;
1058        }
1059    }
1060    
1061    /**
1062     * Retrieves the underlying node.
1063     * @return the underlying node.
1064     */
1065    public Node getNode()
1066    {
1067        return _node;
1068    }
1069    
1070    /**
1071     * Retrieves the current session
1072     * @return he session
1073     * @throws AmetysRepositoryException if an error occurs
1074     */
1075    public Session getSession() throws AmetysRepositoryException
1076    {
1077        try
1078        {
1079            return _node.getSession();
1080        }
1081        catch (RepositoryException e)
1082        {
1083            throw new AmetysRepositoryException("unable to retrieve the session of the repository data at path '" + _getPathForLogs() + "'.", e);
1084        }
1085    }
1086    
1087    public String getDefaultPrefix()
1088    {
1089        return _defaultPrefix;
1090    }
1091    
1092    public String getPath()
1093    {
1094        try
1095        {
1096            List<String> path = new ArrayList<>();
1097            
1098            if (!_node.isNodeType("ametys:object"))
1099            {
1100                RepositoryData parentData = new JCRRepositoryData(_node.getParent());
1101                path.add(parentData.getPath());
1102            }
1103            
1104            path.add(_node.getName());
1105            return StringUtils.join(path, "/");
1106        }
1107        catch (RepositoryException e)
1108        {
1109            throw new AmetysRepositoryException("Unable to get the path of the repository data of node '" + _node + "'.", e);
1110        }
1111    }
1112    
1113    private String _getPathForLogs()
1114    {
1115        try
1116        {
1117            return _node.getPath();
1118        }
1119        catch (RepositoryException e)
1120        {
1121            throw new AmetysRepositoryException("Unable to get the path of the repository data of node '" + _node + "'.", e);
1122        }
1123    }
1124}