View Javadoc

1   package com.atlassian.sal.core.pluginsettings;
2   
3   import java.io.*;
4   import java.util.*;
5   import java.util.Map.Entry;
6   
7   import com.atlassian.sal.api.pluginsettings.PluginSettings;
8   import com.google.common.collect.Lists;
9   import com.google.common.base.Function;
10  import org.apache.commons.lang.Validate;
11  import org.apache.log4j.Logger;
12  
13  /**
14   * PluginSettings implementation for datastores that only support Strings.  Handles converting Strings into Lists and
15   * Properties objects using a '#TYPE_IDENTIFIER' header on the string.
16   */
17  public abstract class AbstractStringPluginSettings implements PluginSettings
18  {
19      private static final Logger log = Logger.getLogger(AbstractStringPluginSettings.class);
20  
21      private static final String PROPERTIES_ENCODING = "ISO8859_1";
22      private static final String PROPERTIES_IDENTIFIER = "java.util.Properties";
23      private static final String LIST_IDENTIFIER = "#java.util.List";
24      private static final String MAP_IDENTIFIER = "#java.util.Map";
25  
26      /**
27       * Puts a setting value.
28       *
29       * @param key Setting key.  Cannot be null
30       * @param value Setting value.  Must be one of {@link String}, {@link List<String>}, {@link Properties}, {@link
31       * Map<String, String>}, or null. null will remove the item from the settings.
32       * @return The setting value that was over ridden. Null if none existed.
33       * @throws IllegalArgumentException if value is not {@link String}, {@link List<String>}, {@link Properties}, {@link
34       * Map<String, String>}, or null.
35       */
36      public Object put(String key, Object value)
37      {
38          Validate.notNull(key, "The plugin settings key cannot be null");
39          if (value == null)
40          {
41              return remove(key);
42          }
43  
44          final Object oldValue = get(key);
45          if (value instanceof Properties)
46          {
47              final ByteArrayOutputStream bout = new ByteArrayOutputStream();
48              try
49              {
50  
51                  final Properties properties = (Properties) value;
52                  properties.store(bout, PROPERTIES_IDENTIFIER);
53                  putActual(key, new String(bout.toByteArray(), PROPERTIES_ENCODING));
54              }
55              catch (final IOException e)
56              {
57                  throw new IllegalArgumentException("Unable to serialize properties", e);
58              }
59          }
60          else if (value instanceof String)
61          {
62              putActual(key, (String) value);
63          }
64          else if (value instanceof List)
65          {
66              final StringBuilder sb = new StringBuilder();
67              sb.append(LIST_IDENTIFIER).append(EscapeUtils.NEW_LINE);
68              for (final Iterator i = ((List) value).iterator(); i.hasNext();)
69              {
70                  sb.append(EscapeUtils.escape(i.next().toString()));
71                  if (i.hasNext())
72                      sb.append(EscapeUtils.NEW_LINE);
73              }
74              putActual(key, sb.toString());
75          }
76          else if (value instanceof Map)
77          {
78              final StringBuilder sb = new StringBuilder();
79              sb.append(MAP_IDENTIFIER).append(EscapeUtils.NEW_LINE);
80              for (final Iterator<Entry> i = ((Map) value).entrySet().iterator(); i.hasNext();)
81              {
82                  final Entry entry = i.next();
83                  sb.append(EscapeUtils.escape(entry.getKey().toString()));
84                  sb.append(EscapeUtils.VERTICAL_TAB);
85                  sb.append(EscapeUtils.escape(entry.getValue().toString()));
86                  if (i.hasNext())
87                      sb.append(EscapeUtils.NEW_LINE);
88              }
89              putActual(key, sb.toString());
90          }
91          else
92          {
93              throw new IllegalArgumentException("Property type: " + value.getClass() + " not supported");
94          }
95          return oldValue;
96      }
97  
98      /**
99       * Gets a setting value. The setting returned should be specific to this context settings object and not cascade the
100      * value to a global context.
101      *
102      * @param key The setting key.  Cannot be null
103      * @return The setting value. May be null
104      */
105     public Object get(String key)
106     {
107         Validate.notNull(key, "The plugin settings key cannot be null");
108         final String val = getActual(key);
109         if (val != null && val.startsWith("#" + PROPERTIES_IDENTIFIER))
110         {
111             final Properties p = new Properties();
112             try
113             {
114                 p.load(new ByteArrayInputStream(val.getBytes(PROPERTIES_ENCODING)));
115             }
116             catch (final IOException e)
117             {
118                 throw new IllegalArgumentException("Unable to deserialize properties", e);
119             }
120             return p;
121         }
122         else if (val != null && val.startsWith(LIST_IDENTIFIER))
123         {
124             final String[] lines = val.split(EscapeUtils.NEW_LINE + "");
125 
126             // remove the first item since it's the list identifier.
127             final List<String> rawItems = Arrays.asList(lines).subList(1, lines.length);
128 
129             // unescape each of the items.
130             final List<String> items = Lists.transform(rawItems, new Function<String, String>()
131                                                                  {
132                                                                         public String apply(String from)
133                                                                         {
134                                                                             return EscapeUtils.unescape(from);
135                                                                         }
136                                                                  });
137             return new ArrayList<String>(items);
138         }
139         else if (val != null && val.startsWith(MAP_IDENTIFIER))
140         {
141             String nval = val.substring(MAP_IDENTIFIER.length() + 1);
142             final HashMap<String, String> map = new HashMap<String, String>();
143             final String[] items = nval.split(EscapeUtils.NEW_LINE + "");
144             for (String item : items)
145             {
146                 if (item.length() > 0)
147                 {
148                     String[] pair = item.split(EscapeUtils.VERTICAL_TAB + "");
149                     if (pair.length != 2)
150                     {
151                         log.error("Could not parse map element: << " + item + " >> \n" +
152                             "Full list: \n" + nval);
153                     }
154                     else
155                     {
156                         map.put(EscapeUtils.unescape(pair[0]),
157                                 EscapeUtils.unescape(pair[1]));
158                     }    
159                 }
160             }
161 
162             return map;
163         }
164         else
165         {
166             return val;
167         }
168     }
169 
170     /**
171      * Removes a setting value
172      *
173      * @param key The setting key
174      * @return The setting value that was removed. Null if nothing was removed.
175      */
176     public Object remove(String key)
177     {
178         Validate.notNull(key, "The plugin settings key cannot be null");
179         Object oldValue = get(key);
180         if (oldValue != null)
181         {
182             removeActual(key);
183         }
184         return oldValue;
185     }
186 
187     /**
188      * Put the actual value.
189      *
190      * @param key The key to put it at.
191      * @param val The value
192      */
193     protected abstract void putActual(String key, String val);
194 
195     /**
196      * Get the actual value
197      *
198      * @param key The key to get
199      * @return The value
200      */
201     protected abstract String getActual(String key);
202 
203     /**
204      * Do the actual remove.  This will only be called if the value already exists.
205      *
206      * @param key The key to remove
207      */
208     protected abstract void removeActual(String key);
209 
210 }