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