1 package com.atlassian.sal.core.pluginsettings;
2
3 import com.atlassian.sal.api.pluginsettings.PluginSettings;
4 import org.apache.commons.lang3.Validate;
5 import org.slf4j.Logger;
6 import org.slf4j.LoggerFactory;
7
8 import java.io.ByteArrayInputStream;
9 import java.io.ByteArrayOutputStream;
10 import java.io.IOException;
11 import java.util.Arrays;
12 import java.util.HashMap;
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Map.Entry;
17 import java.util.Properties;
18 import java.util.stream.Collectors;
19
20
21
22
23
24 public abstract class AbstractStringPluginSettings implements PluginSettings {
25 private static final Logger log = LoggerFactory.getLogger(AbstractStringPluginSettings.class);
26
27 private static final String PROPERTIES_ENCODING = "ISO8859_1";
28 private static final String PROPERTIES_IDENTIFIER = "java.util.Properties";
29 private static final String LIST_IDENTIFIER = "#java.util.List";
30 private static final String MAP_IDENTIFIER = "#java.util.Map";
31
32
33
34
35
36
37
38
39 private final boolean isDeveloperMode = Boolean.getBoolean("atlassian.dev.mode");
40
41
42
43
44
45
46
47
48
49 public Object put(String key, Object value) {
50 Validate.isTrue(key != null, "The plugin settings key cannot be null");
51 Validate.isTrue(key.length() <= 255, "The plugin settings key cannot be more than 255 characters");
52 if (isDeveloperMode) {
53 Validate.isTrue(key.length() <= 100, "The plugin settings key cannot be more than 100 characters in developer mode");
54 }
55 if (value == null) {
56 return remove(key);
57 }
58
59 final Object oldValue = get(key);
60 if (value instanceof Properties) {
61 final ByteArrayOutputStream bout = new ByteArrayOutputStream();
62 try {
63
64 final Properties properties = (Properties) value;
65 properties.store(bout, PROPERTIES_IDENTIFIER);
66 putActual(key, new String(bout.toByteArray(), PROPERTIES_ENCODING));
67 } catch (final IOException e) {
68 throw new IllegalArgumentException("Unable to serialize properties", e);
69 }
70 } else if (value instanceof String) {
71 putActual(key, (String) value);
72 } else if (value instanceof List) {
73 final StringBuilder sb = new StringBuilder();
74 sb.append(LIST_IDENTIFIER).append(EscapeUtils.NEW_LINE);
75 for (final Iterator i = ((List) value).iterator(); i.hasNext(); ) {
76 sb.append(EscapeUtils.escape(i.next().toString()));
77 if (i.hasNext())
78 sb.append(EscapeUtils.NEW_LINE);
79 }
80 putActual(key, sb.toString());
81 } else if (value instanceof Map) {
82 final StringBuilder sb = new StringBuilder();
83 sb.append(MAP_IDENTIFIER).append(EscapeUtils.NEW_LINE);
84 for (final Iterator<Entry> i = ((Map) value).entrySet().iterator(); i.hasNext(); ) {
85 final Entry entry = i.next();
86 sb.append(EscapeUtils.escape(entry.getKey().toString()));
87 sb.append(EscapeUtils.VERTICAL_TAB);
88 sb.append(EscapeUtils.escape(entry.getValue().toString()));
89 if (i.hasNext())
90 sb.append(EscapeUtils.NEW_LINE);
91 }
92 putActual(key, sb.toString());
93 } else {
94 throw new IllegalArgumentException("Property type: " + value.getClass() + " not supported");
95 }
96 return oldValue;
97 }
98
99
100
101
102
103
104
105
106 public Object get(String key) {
107 Validate.isTrue(key != null, "The plugin settings key cannot be null");
108 if (isDeveloperMode && key.length() > 100) {
109 log.warn("PluginSettings.get with excessive key length: {}", key);
110 }
111 final String val = getActual(key);
112 if (val != null && val.startsWith("#" + PROPERTIES_IDENTIFIER)) {
113 final Properties p = new Properties();
114 try {
115 p.load(new ByteArrayInputStream(val.getBytes(PROPERTIES_ENCODING)));
116 } catch (final IOException e) {
117 throw new IllegalArgumentException("Unable to deserialize properties", e);
118 }
119 return p;
120 } else if (val != null && val.startsWith(LIST_IDENTIFIER)) {
121 final String[] lines = val.split(EscapeUtils.NEW_LINE + "");
122
123
124 final List<String> rawItems = Arrays.asList(lines).subList(1, lines.length);
125
126
127 return rawItems.stream().map(EscapeUtils::unescape).collect(Collectors.toList());
128 } else if (val != null && val.startsWith(MAP_IDENTIFIER)) {
129 String nval = val.substring(MAP_IDENTIFIER.length() + 1);
130 final HashMap<String, String> map = new HashMap<>();
131 final String[] items = nval.split(EscapeUtils.NEW_LINE + "");
132 for (String item : items) {
133 if (item.length() > 0) {
134 int tabLocation = item.indexOf(EscapeUtils.VERTICAL_TAB);
135
136 if (tabLocation == -1) {
137 log.error("Could not parse map element: '{}'\nFull list: '{}'\n", item, nval);
138 } else {
139 String keyPart = item.substring(0, tabLocation);
140 String valuePart = item.substring(tabLocation + 1, item.length());
141 map.put(EscapeUtils.unescape(keyPart),
142 EscapeUtils.unescape(valuePart));
143 }
144 }
145 }
146
147 return map;
148 } else {
149 return val;
150 }
151 }
152
153
154
155
156
157
158
159 public Object remove(String key) {
160 Validate.isTrue(key != null, "The plugin settings key cannot be null");
161 if (isDeveloperMode && key.length() > 100) {
162 log.warn("PluginSettings.get with excessive key length: " + key);
163 }
164 Object oldValue = get(key);
165 if (oldValue != null) {
166 removeActual(key);
167 }
168 return oldValue;
169 }
170
171
172
173
174
175
176
177 protected abstract void putActual(String key, String val);
178
179
180
181
182
183
184
185 protected abstract String getActual(String key);
186
187
188
189
190
191
192 protected abstract void removeActual(String key);
193
194 }