001/*
002 *  Copyright 2019 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.Arrays;
021import java.util.Calendar;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.stream.Collectors;
027
028import org.apache.commons.lang3.StringUtils;
029import org.apache.commons.lang3.tuple.ImmutablePair;
030import org.apache.commons.lang3.tuple.Pair;
031
032import org.ametys.plugins.repository.AmetysRepositoryException;
033import org.ametys.plugins.repository.RepositoryConstants;
034import org.ametys.plugins.repository.data.UnknownDataException;
035import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
036import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
037
038/**
039 * Class for data values management in memory
040 */
041public class MemoryRepositoryData implements ModifiableRepositoryData
042{
043    private String _fullName;
044    private Map<String, Pair<String, Object>> _data;
045    private String _defaultPrefix;
046    
047    /**
048     * Creates a memory repository data with the given name
049     * @param name the name of the current data
050     */
051    public MemoryRepositoryData(String name)
052    {
053        this(name, RepositoryConstants.NAMESPACE_PREFIX);
054    }
055    
056    /**
057     * Creates a memory repository data with the given name
058     * @param name the name of the current data
059     * @param defaultPrefix prefix to use for properties and child nodes
060     */
061    public MemoryRepositoryData(String name, String defaultPrefix)
062    {
063        _fullName = _getFullName(name, defaultPrefix);
064        _defaultPrefix = defaultPrefix;
065        _data = new HashMap<>();
066    }
067    
068    public String getString(String name, String prefix)
069    {
070        if (hasValue(name, prefix))
071        {
072            Pair<String, Object> value = _data.get(_getFullName(name, prefix));
073            if (RepositoryData.STRING_REPOSITORY_DATA_TYPE.equals(value.getLeft()) && value.getRight() instanceof String)
074            {
075                return (String) value.getRight();
076            }
077            else
078            {
079                throw new AmetysRepositoryException("Unable to get the value of string data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
080            }
081        }
082        else
083        {
084            throw new UnknownDataException("Unknown data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
085        }
086    }
087
088    public String[] getStrings(String name, String prefix)
089    {
090        if (hasValue(name, prefix))
091        {
092            Pair<String, Object> value = _data.get(_getFullName(name, prefix));
093            if (RepositoryData.STRING_REPOSITORY_DATA_TYPE.equals(value.getLeft()) && value.getRight() instanceof String[])
094            {
095                return (String[]) value.getRight();
096            }
097            else
098            {
099                throw new AmetysRepositoryException("Unable to get the value of multiple string data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
100            }
101        }
102        else
103        {
104            throw new UnknownDataException("Unknown data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
105        }
106    }
107
108    public Calendar getDate(String name, String prefix)
109    {
110        if (hasValue(name, prefix))
111        {
112            Pair<String, Object> value = _data.get(_getFullName(name, prefix));
113            if (RepositoryData.CALENDAR_REPOSITORY_DATA_TYPE.equals(value.getLeft()) && value.getRight() instanceof Calendar)
114            {
115                return (Calendar) value.getRight();
116            }
117            else
118            {
119                throw new AmetysRepositoryException("Unable to get the value of date data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
120            }
121        }
122        else
123        {
124            throw new UnknownDataException("Unknown data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
125        }
126    }
127
128    public Calendar[] getDates(String name, String prefix)
129    {
130        if (hasValue(name, prefix))
131        {
132            Pair<String, Object> value = _data.get(_getFullName(name, prefix));
133            if (RepositoryData.CALENDAR_REPOSITORY_DATA_TYPE.equals(value.getLeft()) && value.getRight() instanceof Calendar[])
134            {
135                return (Calendar[]) value.getRight();
136            }
137            else
138            {
139                throw new AmetysRepositoryException("Unable to get the value of multiple date data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
140            }
141        }
142        else
143        {
144            throw new UnknownDataException("Unknown data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
145        }
146    }
147    
148    public Long getLong(String name, String prefix)
149    {
150        if (hasValue(name, prefix))
151        {
152            Pair<String, Object> value = _data.get(_getFullName(name, prefix));
153            if (RepositoryData.LONG_REPOSITORY_DATA_TYPE.equals(value.getLeft()) && value.getRight() instanceof Long)
154            {
155                return (Long) value.getRight();
156            }
157            else
158            {
159                throw new AmetysRepositoryException("Unable to get the value of long data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
160            }
161        }
162        else
163        {
164            throw new UnknownDataException("Unknown data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
165        }
166    }
167
168    public Long[] getLongs(String name, String prefix)
169    {
170        if (hasValue(name, prefix))
171        {
172            Pair<String, Object> value = _data.get(_getFullName(name, prefix));
173            if (RepositoryData.LONG_REPOSITORY_DATA_TYPE.equals(value.getLeft()) && value.getRight() instanceof Long[])
174            {
175                return (Long[]) value.getRight();
176            }
177            else
178            {
179                throw new AmetysRepositoryException("Unable to get the value of multiple long data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
180            }
181        }
182        else
183        {
184            throw new UnknownDataException("Unknown data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
185        }
186    }
187
188    public Double getDouble(String name, String prefix)
189    {
190        if (hasValue(name, prefix))
191        {
192            Pair<String, Object> value = _data.get(_getFullName(name, prefix));
193            if (RepositoryData.DOUBLE_REPOSITORY_DATA_TYPE.equals(value.getLeft()) && value.getRight() instanceof Double)
194            {
195                return (Double) value.getRight();
196            }
197            else
198            {
199                throw new AmetysRepositoryException("Unable to get the value of double data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
200            }
201        }
202        else
203        {
204            throw new UnknownDataException("Unknown data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
205        }
206    }
207
208    public Double[] getDoubles(String name, String prefix)
209    {
210        if (hasValue(name, prefix))
211        {
212            Pair<String, Object> value = _data.get(_getFullName(name, prefix));
213            if (RepositoryData.DOUBLE_REPOSITORY_DATA_TYPE.equals(value.getLeft()) && value.getRight() instanceof Double[])
214            {
215                return (Double[]) value.getRight();
216            }
217            else
218            {
219                throw new AmetysRepositoryException("Unable to get the value of multiple double data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
220            }
221        }
222        else
223        {
224            throw new UnknownDataException("Unknown data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
225        }
226    }
227
228    public Boolean getBoolean(String name, String prefix)
229    {
230        if (hasValue(name, prefix))
231        {
232            Pair<String, Object> value = _data.get(_getFullName(name, prefix));
233            if (RepositoryData.BOOLEAN_REPOSITORY_DATA_TYPE.equals(value.getLeft()) && value.getRight() instanceof Boolean)
234            {
235                return (Boolean) value.getRight();
236            }
237            else
238            {
239                throw new AmetysRepositoryException("Unable to get the value of boolean data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
240            }
241        }
242        else
243        {
244            throw new UnknownDataException("Unknown data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
245        }
246    }
247
248    public Boolean[] getBooleans(String name, String prefix)
249    {
250        if (hasValue(name, prefix))
251        {
252            Pair<String, Object> value = _data.get(_getFullName(name, prefix));
253            if (RepositoryData.BOOLEAN_REPOSITORY_DATA_TYPE.equals(value.getLeft()) && value.getRight() instanceof Boolean[])
254            {
255                return (Boolean[]) value.getRight();
256            }
257            else
258            {
259                throw new AmetysRepositoryException("Unable to get the value of multiple boolean data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
260            }
261        }
262        else
263        {
264            throw new UnknownDataException("Unknown data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
265        }
266    }
267
268    public ModifiableRepositoryData getRepositoryData(String name, String prefix)
269    {
270        if (hasValue(name, prefix))
271        {
272            Pair<String, Object> value = _data.get(_getFullName(name, prefix));
273            if (value.getRight() instanceof ModifiableRepositoryData[])
274            {
275                ModifiableRepositoryData[] allRepositoryData = (ModifiableRepositoryData[]) value.getRight();
276                if (allRepositoryData.length >= 1)
277                {
278                    return ((ModifiableRepositoryData[]) value.getRight())[0];
279                }
280                else
281                {
282                    throw new UnknownDataException("Unknown data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
283                }
284            }
285            else
286            {
287                throw new AmetysRepositoryException("Unable to get the value of repository data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
288            }
289        }
290        else
291        {
292            throw new UnknownDataException("Unknown data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
293        }
294    }
295    
296    public RepositoryData[] getAllRepositoryData(String name, String prefix)
297    {
298        if (hasValue(name, prefix))
299        {
300            Pair<String, Object> value = _data.get(_getFullName(name, prefix));
301            if (value.getRight() instanceof ModifiableRepositoryData[])
302            {
303                return (ModifiableRepositoryData[]) value.getRight();
304            }
305            else
306            {
307                throw new AmetysRepositoryException("Unable to get the value of multiple repository data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
308            }
309        }
310        else
311        {
312            throw new UnknownDataException("Unknown data '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
313        }
314    }
315
316    public Set<String> getDataNames(String prefix)
317    {
318        final String finalPrefix;
319        if (prefix == null)
320        {
321            finalPrefix = "*:";
322        }
323        else if (StringUtils.isEmpty(prefix))
324        {
325            finalPrefix = prefix;
326        }
327        else
328        {
329            finalPrefix = prefix + ":";
330        }
331        
332        return _data.keySet().stream()
333                             .filter(name -> name.startsWith(finalPrefix))
334                             .map(name -> name.substring(finalPrefix.length()))
335                             .collect(Collectors.toSet());
336    }
337    
338    public String getName()
339    {
340        int prefixLength = _fullName.indexOf(":") + 1;
341        return prefixLength > 0 ? _fullName.substring(prefixLength) : _fullName;
342    }
343
344    public boolean hasValue(String name, String prefix)
345    {
346        return _data.containsKey(_getFullName(name, prefix));
347    }
348
349    public String getType(String name, String prefix) throws UnknownDataException
350    {
351        Pair<String, Object> value = _data.get(_getFullName(name, prefix));
352        if (value != null)
353        {
354            return value.getLeft();
355        }
356        else
357        {
358            throw new UnknownDataException("No data found for '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
359        }
360    }
361
362    public boolean isMultiple(String name, String prefix) throws UnknownDataException
363    {
364        if (hasValue(name, prefix))
365        {
366            Pair<String, Object> value = _data.get(_getFullName(name, prefix));
367            if (RepositoryConstants.MULTIPLE_ITEM_NODETYPE.equals(value.getLeft()))
368            {
369                return true;
370            }
371            else if (value.getRight().getClass().isArray())
372            {
373                if (value.getRight() instanceof ModifiableRepositoryData[])
374                {
375                    return ((ModifiableRepositoryData[]) value.getRight()).length > 1;
376                }
377                else
378                {
379                    return true;
380                }
381            }
382            else
383            {
384                return false;
385            }
386        }
387        else
388        {
389            throw new UnknownDataException("No data found for '" + prefix + ":" + name + "' in repository data named '" + _fullName + "'.");
390        }
391    }
392
393    public String getDefaultPrefix()
394    {
395        return _defaultPrefix;
396    }
397
398    public ModifiableRepositoryData addRepositoryData(String name, String dataTypeName, String prefix)
399    {
400        ModifiableRepositoryData repositoryData = new MemoryRepositoryData(name, prefix);
401        List<ModifiableRepositoryData> allRepositoryData = new ArrayList<>();
402        
403        // Has value
404        if (hasValue(name, prefix))
405        {
406            Pair<String, Object> value = _data.get(_getFullName(name, prefix));
407            allRepositoryData.addAll(Arrays.asList((ModifiableRepositoryData[]) value.getRight()));
408        }
409        
410        allRepositoryData.add(repositoryData);
411        ModifiableRepositoryData[] result = allRepositoryData.toArray(new ModifiableRepositoryData[allRepositoryData.size()]);
412        _data.put(_getFullName(name, prefix), new ImmutablePair<>(dataTypeName, result));
413
414        return repositoryData;
415    }
416
417    public void rename(String newName, String prefix)
418    {
419        _fullName = _getFullName(newName, prefix);
420    }
421
422    public void setValue(String name, String value, String prefix)
423    {
424        _setValue(name, RepositoryData.STRING_REPOSITORY_DATA_TYPE, value, prefix);
425    }
426
427    public void setValues(String name, String[] values, String prefix)
428    {
429        _setValue(name, RepositoryData.STRING_REPOSITORY_DATA_TYPE, values, prefix);
430    }
431
432    public void setValue(String name, Calendar value, String prefix)
433    {
434        _setValue(name, RepositoryData.CALENDAR_REPOSITORY_DATA_TYPE, value, prefix);
435    }
436
437    public void setValues(String name, Calendar[] values, String prefix)
438    {
439        _setValue(name, RepositoryData.CALENDAR_REPOSITORY_DATA_TYPE, values, prefix);
440    }
441
442    public void setValue(String name, Long value, String prefix)
443    {
444        _setValue(name, RepositoryData.LONG_REPOSITORY_DATA_TYPE, value, prefix);
445    }
446
447    public void setValues(String name, Long[] values, String prefix)
448    {
449        _setValue(name, RepositoryData.LONG_REPOSITORY_DATA_TYPE, values, prefix);
450    }
451
452    public void setValue(String name, Double value, String prefix)
453    {
454        _setValue(name, RepositoryData.DOUBLE_REPOSITORY_DATA_TYPE, value, prefix);
455    }
456
457    public void setValues(String name, Double[] values, String prefix)
458    {
459        _setValue(name, RepositoryData.DOUBLE_REPOSITORY_DATA_TYPE, values, prefix);
460    }
461
462    public void setValue(String name, Boolean value, String prefix)
463    {
464        _setValue(name, RepositoryData.BOOLEAN_REPOSITORY_DATA_TYPE, value, prefix);
465    }
466
467    public void setValues(String name, Boolean[] values, String prefix)
468    {
469        _setValue(name, RepositoryData.BOOLEAN_REPOSITORY_DATA_TYPE, values, prefix);
470    }
471    
472    private void _setValue(String name, String dataTypeName, Object value, String prefix)
473    {
474        String fullName =  _getFullName(name, prefix);
475        
476        if (value == null)
477        {
478            throw new NullPointerException("Unable to set a 'null' data for '" + fullName + "' in repository data named '" + _fullName + "'.");
479        }
480
481        _checkDataName(name);
482     
483        // Has value
484        if (hasValue(name, prefix))
485        {
486            // Remove existing value to avoid values with same name (node + property)
487            removeValue(name, prefix);
488        }
489        
490        _data.put(fullName, new ImmutablePair<>(dataTypeName, value));
491    }
492    
493    private void _checkDataName(String name)
494    {
495        // Name null
496        if (name == null)
497        {
498            throw new AmetysRepositoryException("Unable to set a value with the invalid data name (null) in repository data named '" + _fullName + "'.");
499        }
500
501        // Valid name
502        if (!DATA_NAME_PATTERN.matcher(name).matches())
503        {
504            throw new AmetysRepositoryException("Unable to set a value with the invalid data name '" + name + "'. Only [a-zA-Z][a-zA-Z0-9-_]* are allowed.");
505        }
506    }
507
508    public void removeValue(String name, String prefix) throws UnknownDataException
509    {
510        if (hasValue(name, prefix))
511        {
512            _data.remove(_getFullName(name, prefix));
513        }
514        else
515        {
516            throw new UnknownDataException("No data found for '" + _getFullName(name, prefix) + "' in repository data named '" + _fullName + "'.");
517        }
518    }
519    
520    private String _getFullName(String name, String prefix)
521    {
522        return StringUtils.isEmpty(prefix) ? name : prefix + ":" + name;
523    }
524
525    public InputStream getStream(String name, String prefix)
526    {
527        throw new UnsupportedOperationException("Streams are not supported by the memory repository data");
528    }
529    
530    public Long getStreamLength(String name, String prefix)
531    {
532        throw new UnsupportedOperationException("Streams are not supported by the memory repository data");
533    }
534
535    public InputStream[] getStreams(String name, String prefix)
536    {
537        throw new UnsupportedOperationException("Streams are not supported by the memory repository data");
538    }
539    
540    public void setValue(String name, InputStream value, String prefix)
541    {
542        throw new UnsupportedOperationException("Streams are not supported by the memory repository data");
543    }
544    
545    public void setValues(String name, InputStream[] values, String prefix)
546    {
547        throw new UnsupportedOperationException("Streams are not supported by the memory repository data");
548    }
549    
550    @Override
551    public String toString()
552    {
553        return _fullName;
554    }
555}