1 | |
|
2 | |
|
3 | |
|
4 | |
|
5 | |
|
6 | |
|
7 | |
|
8 | |
|
9 | |
|
10 | |
|
11 | |
|
12 | |
|
13 | |
|
14 | |
|
15 | |
|
16 | |
|
17 | |
|
18 | |
|
19 | |
package org.apache.shiro.config; |
20 | |
|
21 | |
import org.apache.shiro.io.ResourceUtils; |
22 | |
import org.apache.shiro.util.CollectionUtils; |
23 | |
import org.apache.shiro.util.StringUtils; |
24 | |
import org.slf4j.Logger; |
25 | |
import org.slf4j.LoggerFactory; |
26 | |
|
27 | |
import java.io.*; |
28 | |
import java.util.*; |
29 | |
|
30 | |
|
31 | |
|
32 | |
|
33 | |
|
34 | |
|
35 | |
|
36 | |
|
37 | |
|
38 | |
|
39 | 35 | public class Ini implements Map<String, Ini.Section> { |
40 | |
|
41 | 1 | private static transient final Logger log = LoggerFactory.getLogger(Ini.class); |
42 | |
|
43 | |
public static final String DEFAULT_SECTION_NAME = ""; |
44 | |
public static final String DEFAULT_CHARSET_NAME = "UTF-8"; |
45 | |
|
46 | |
public static final String COMMENT_POUND = "#"; |
47 | |
public static final String COMMENT_SEMICOLON = ";"; |
48 | |
public static final String SECTION_PREFIX = "["; |
49 | |
public static final String SECTION_SUFFIX = "]"; |
50 | |
|
51 | |
protected static final char ESCAPE_TOKEN = '\\'; |
52 | |
|
53 | |
private final Map<String, Section> sections; |
54 | |
|
55 | |
|
56 | |
|
57 | |
|
58 | 39 | public Ini() { |
59 | 39 | this.sections = new LinkedHashMap<String, Section>(); |
60 | 39 | } |
61 | |
|
62 | |
|
63 | |
|
64 | |
|
65 | |
|
66 | |
|
67 | |
public Ini(Ini defaults) { |
68 | 0 | this(); |
69 | 0 | if (defaults == null) { |
70 | 0 | throw new NullPointerException("Defaults cannot be null."); |
71 | |
} |
72 | 0 | for (Section section : defaults.getSections()) { |
73 | 0 | Section copy = new Section(section); |
74 | 0 | this.sections.put(section.getName(), copy); |
75 | 0 | } |
76 | 0 | } |
77 | |
|
78 | |
|
79 | |
|
80 | |
|
81 | |
|
82 | |
|
83 | |
|
84 | |
|
85 | |
public boolean isEmpty() { |
86 | 124 | Collection<Section> sections = this.sections.values(); |
87 | 124 | if (!sections.isEmpty()) { |
88 | 122 | for (Section section : sections) { |
89 | 122 | if (!section.isEmpty()) { |
90 | 122 | return false; |
91 | |
} |
92 | 0 | } |
93 | |
} |
94 | 2 | return true; |
95 | |
} |
96 | |
|
97 | |
|
98 | |
|
99 | |
|
100 | |
|
101 | |
|
102 | |
|
103 | |
|
104 | |
public Set<String> getSectionNames() { |
105 | 0 | return Collections.unmodifiableSet(sections.keySet()); |
106 | |
} |
107 | |
|
108 | |
|
109 | |
|
110 | |
|
111 | |
|
112 | |
|
113 | |
|
114 | |
|
115 | |
public Collection<Section> getSections() { |
116 | 6 | return Collections.unmodifiableCollection(sections.values()); |
117 | |
} |
118 | |
|
119 | |
|
120 | |
|
121 | |
|
122 | |
|
123 | |
|
124 | |
|
125 | |
public Section getSection(String sectionName) { |
126 | 189 | String name = cleanName(sectionName); |
127 | 189 | return sections.get(name); |
128 | |
} |
129 | |
|
130 | |
|
131 | |
|
132 | |
|
133 | |
|
134 | |
|
135 | |
|
136 | |
public Section addSection(String sectionName) { |
137 | 30 | String name = cleanName(sectionName); |
138 | 30 | Section section = getSection(name); |
139 | 30 | if (section == null) { |
140 | 30 | section = new Section(name); |
141 | 30 | this.sections.put(name, section); |
142 | |
} |
143 | 30 | return section; |
144 | |
} |
145 | |
|
146 | |
|
147 | |
|
148 | |
|
149 | |
|
150 | |
|
151 | |
|
152 | |
public Section removeSection(String sectionName) { |
153 | 0 | String name = cleanName(sectionName); |
154 | 0 | return this.sections.remove(name); |
155 | |
} |
156 | |
|
157 | |
private static String cleanName(String sectionName) { |
158 | 283 | String name = StringUtils.clean(sectionName); |
159 | 283 | if (name == null) { |
160 | 4 | log.trace("Specified name was null or empty. Defaulting to the default section (name = \"\")"); |
161 | 4 | name = DEFAULT_SECTION_NAME; |
162 | |
} |
163 | 283 | return name; |
164 | |
} |
165 | |
|
166 | |
|
167 | |
|
168 | |
|
169 | |
|
170 | |
|
171 | |
|
172 | |
|
173 | |
|
174 | |
|
175 | |
public void setSectionProperty(String sectionName, String propertyName, String propertyValue) { |
176 | 53 | String name = cleanName(sectionName); |
177 | 53 | Section section = getSection(name); |
178 | 53 | if (section == null) { |
179 | 20 | section = addSection(name); |
180 | |
} |
181 | 53 | section.put(propertyName, propertyValue); |
182 | 53 | } |
183 | |
|
184 | |
|
185 | |
|
186 | |
|
187 | |
|
188 | |
|
189 | |
|
190 | |
|
191 | |
public String getSectionProperty(String sectionName, String propertyName) { |
192 | 0 | Section section = getSection(sectionName); |
193 | 0 | return section != null ? section.get(propertyName) : null; |
194 | |
} |
195 | |
|
196 | |
|
197 | |
|
198 | |
|
199 | |
|
200 | |
|
201 | |
|
202 | |
|
203 | |
|
204 | |
|
205 | |
|
206 | |
public String getSectionProperty(String sectionName, String propertyName, String defaultValue) { |
207 | 0 | String value = getSectionProperty(sectionName, propertyName); |
208 | 0 | return value != null ? value : defaultValue; |
209 | |
} |
210 | |
|
211 | |
|
212 | |
|
213 | |
|
214 | |
|
215 | |
|
216 | |
|
217 | |
|
218 | |
|
219 | |
|
220 | |
public static Ini fromResourcePath(String resourcePath) throws ConfigurationException { |
221 | 4 | if (!StringUtils.hasLength(resourcePath)) { |
222 | 0 | throw new IllegalArgumentException("Resource Path argument cannot be null or empty."); |
223 | |
} |
224 | 4 | Ini ini = new Ini(); |
225 | 4 | ini.loadFromPath(resourcePath); |
226 | 4 | return ini; |
227 | |
} |
228 | |
|
229 | |
|
230 | |
|
231 | |
|
232 | |
|
233 | |
|
234 | |
|
235 | |
|
236 | |
|
237 | |
public void loadFromPath(String resourcePath) throws ConfigurationException { |
238 | |
InputStream is; |
239 | |
try { |
240 | 4 | is = ResourceUtils.getInputStreamForPath(resourcePath); |
241 | 0 | } catch (IOException e) { |
242 | 0 | throw new ConfigurationException(e); |
243 | 4 | } |
244 | 4 | load(is); |
245 | 4 | } |
246 | |
|
247 | |
|
248 | |
|
249 | |
|
250 | |
|
251 | |
|
252 | |
|
253 | |
public void load(String iniConfig) throws ConfigurationException { |
254 | 4 | load(new Scanner(iniConfig)); |
255 | 4 | } |
256 | |
|
257 | |
|
258 | |
|
259 | |
|
260 | |
|
261 | |
|
262 | |
|
263 | |
|
264 | |
|
265 | |
public void load(InputStream is) throws ConfigurationException { |
266 | 4 | if (is == null) { |
267 | 0 | throw new NullPointerException("InputStream argument cannot be null."); |
268 | |
} |
269 | |
InputStreamReader isr; |
270 | |
try { |
271 | 4 | isr = new InputStreamReader(is, DEFAULT_CHARSET_NAME); |
272 | 0 | } catch (UnsupportedEncodingException e) { |
273 | 0 | throw new ConfigurationException(e); |
274 | 4 | } |
275 | 4 | load(isr); |
276 | 4 | } |
277 | |
|
278 | |
|
279 | |
|
280 | |
|
281 | |
|
282 | |
|
283 | |
|
284 | |
public void load(Reader reader) { |
285 | 4 | Scanner scanner = new Scanner(reader); |
286 | |
try { |
287 | 4 | load(scanner); |
288 | |
} finally { |
289 | 0 | try { |
290 | 4 | scanner.close(); |
291 | 0 | } catch (Exception e) { |
292 | 0 | log.debug("Unable to cleanly close the InputStream scanner. Non-critical - ignoring.", e); |
293 | 4 | } |
294 | 0 | } |
295 | 4 | } |
296 | |
|
297 | |
private void addSection(String name, StringBuilder content) { |
298 | 20 | if (content.length() > 0) { |
299 | 13 | String contentString = content.toString(); |
300 | 13 | String cleaned = StringUtils.clean(contentString); |
301 | 13 | if (cleaned != null) { |
302 | 13 | Section section = new Section(name, contentString); |
303 | 13 | if (!section.isEmpty()) { |
304 | 13 | sections.put(name, section); |
305 | |
} |
306 | |
} |
307 | |
} |
308 | 20 | } |
309 | |
|
310 | |
|
311 | |
|
312 | |
|
313 | |
|
314 | |
|
315 | |
|
316 | |
public void load(Scanner scanner) { |
317 | |
|
318 | 9 | String sectionName = DEFAULT_SECTION_NAME; |
319 | 9 | StringBuilder sectionContent = new StringBuilder(); |
320 | |
|
321 | 146 | while (scanner.hasNextLine()) { |
322 | |
|
323 | 137 | String rawLine = scanner.nextLine(); |
324 | 137 | String line = StringUtils.clean(rawLine); |
325 | |
|
326 | 137 | if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) { |
327 | |
|
328 | 2 | continue; |
329 | |
} |
330 | |
|
331 | 38 | String newSectionName = getSectionName(line); |
332 | 38 | if (newSectionName != null) { |
333 | |
|
334 | 11 | addSection(sectionName, sectionContent); |
335 | |
|
336 | |
|
337 | 11 | sectionContent = new StringBuilder(); |
338 | |
|
339 | 11 | sectionName = newSectionName; |
340 | |
|
341 | 11 | if (log.isDebugEnabled()) { |
342 | 11 | log.debug("Parsing " + SECTION_PREFIX + sectionName + SECTION_SUFFIX); |
343 | |
} |
344 | |
} else { |
345 | |
|
346 | 27 | sectionContent.append(rawLine).append("\n"); |
347 | |
} |
348 | 38 | } |
349 | |
|
350 | |
|
351 | 9 | addSection(sectionName, sectionContent); |
352 | 9 | } |
353 | |
|
354 | |
protected static boolean isSectionHeader(String line) { |
355 | 38 | String s = StringUtils.clean(line); |
356 | 38 | return s != null && s.startsWith(SECTION_PREFIX) && s.endsWith(SECTION_SUFFIX); |
357 | |
} |
358 | |
|
359 | |
protected static String getSectionName(String line) { |
360 | 38 | String s = StringUtils.clean(line); |
361 | 38 | if (isSectionHeader(s)) { |
362 | 11 | return cleanName(s.substring(1, s.length() - 1)); |
363 | |
} |
364 | 27 | return null; |
365 | |
} |
366 | |
|
367 | |
public boolean equals(Object obj) { |
368 | 0 | if (obj instanceof Ini) { |
369 | 0 | Ini ini = (Ini) obj; |
370 | 0 | return this.sections.equals(ini.sections); |
371 | |
} |
372 | 0 | return false; |
373 | |
} |
374 | |
|
375 | |
@Override |
376 | |
public int hashCode() { |
377 | 0 | return this.sections.hashCode(); |
378 | |
} |
379 | |
|
380 | |
public String toString() { |
381 | 21 | if (CollectionUtils.isEmpty(this.sections)) { |
382 | 0 | return "<empty INI>"; |
383 | |
} else { |
384 | 21 | StringBuilder sb = new StringBuilder("sections="); |
385 | 21 | int i = 0; |
386 | 21 | for (Ini.Section section : this.sections.values()) { |
387 | 26 | if (i > 0) { |
388 | 5 | sb.append(","); |
389 | |
} |
390 | 26 | sb.append(section.toString()); |
391 | 26 | i++; |
392 | 26 | } |
393 | 21 | return sb.toString(); |
394 | |
} |
395 | |
} |
396 | |
|
397 | |
public int size() { |
398 | 0 | return this.sections.size(); |
399 | |
} |
400 | |
|
401 | |
public boolean containsKey(Object key) { |
402 | 0 | return this.sections.containsKey(key); |
403 | |
} |
404 | |
|
405 | |
public boolean containsValue(Object value) { |
406 | 0 | return this.sections.containsValue(value); |
407 | |
} |
408 | |
|
409 | |
public Section get(Object key) { |
410 | 0 | return this.sections.get(key); |
411 | |
} |
412 | |
|
413 | |
public Section put(String key, Section value) { |
414 | 0 | return this.sections.put(key, value); |
415 | |
} |
416 | |
|
417 | |
public Section remove(Object key) { |
418 | 0 | return this.sections.remove(key); |
419 | |
} |
420 | |
|
421 | |
public void putAll(Map<? extends String, ? extends Section> m) { |
422 | 0 | this.sections.putAll(m); |
423 | 0 | } |
424 | |
|
425 | |
public void clear() { |
426 | 0 | this.sections.clear(); |
427 | 0 | } |
428 | |
|
429 | |
public Set<String> keySet() { |
430 | 0 | return Collections.unmodifiableSet(this.sections.keySet()); |
431 | |
} |
432 | |
|
433 | |
public Collection<Section> values() { |
434 | 0 | return Collections.unmodifiableCollection(this.sections.values()); |
435 | |
} |
436 | |
|
437 | |
public Set<Entry<String, Section>> entrySet() { |
438 | 0 | return Collections.unmodifiableSet(this.sections.entrySet()); |
439 | |
} |
440 | |
|
441 | |
|
442 | |
|
443 | |
|
444 | |
|
445 | 71 | public static class Section implements Map<String, String> { |
446 | |
private final String name; |
447 | |
private final Map<String, String> props; |
448 | |
|
449 | 30 | private Section(String name) { |
450 | 30 | if (name == null) { |
451 | 0 | throw new NullPointerException("name"); |
452 | |
} |
453 | 30 | this.name = name; |
454 | 30 | this.props = new LinkedHashMap<String, String>(); |
455 | 30 | } |
456 | |
|
457 | 13 | private Section(String name, String sectionContent) { |
458 | 13 | if (name == null) { |
459 | 0 | throw new NullPointerException("name"); |
460 | |
} |
461 | 13 | this.name = name; |
462 | |
Map<String,String> props; |
463 | 13 | if (StringUtils.hasText(sectionContent) ) { |
464 | 13 | props = toMapProps(sectionContent); |
465 | |
} else { |
466 | 0 | props = new LinkedHashMap<String,String>(); |
467 | |
} |
468 | 13 | if ( props != null ) { |
469 | 13 | this.props = props; |
470 | |
} else { |
471 | 0 | this.props = new LinkedHashMap<String,String>(); |
472 | |
} |
473 | 13 | } |
474 | |
|
475 | |
private Section(Section defaults) { |
476 | 0 | this(defaults.getName()); |
477 | 0 | putAll(defaults.props); |
478 | 0 | } |
479 | |
|
480 | |
|
481 | |
|
482 | |
protected static boolean isContinued(String line) { |
483 | 31 | if (!StringUtils.hasText(line)) { |
484 | 0 | return false; |
485 | |
} |
486 | 31 | int length = line.length(); |
487 | |
|
488 | |
|
489 | 31 | int backslashCount = 0; |
490 | 38 | for (int i = length - 1; i > 0; i--) { |
491 | 38 | if (line.charAt(i) == ESCAPE_TOKEN) { |
492 | 7 | backslashCount++; |
493 | |
} else { |
494 | |
break; |
495 | |
} |
496 | |
} |
497 | 31 | return backslashCount % 2 != 0; |
498 | |
} |
499 | |
|
500 | |
private static boolean isKeyValueSeparatorChar(char c) { |
501 | 627 | return Character.isWhitespace(c) || c == ':' || c == '='; |
502 | |
} |
503 | |
|
504 | |
private static boolean isCharEscaped(CharSequence s, int index) { |
505 | 97 | return index > 0 && s.charAt(index - 1) == ESCAPE_TOKEN; |
506 | |
} |
507 | |
|
508 | |
|
509 | |
protected static String[] splitKeyValue(String keyValueLine) { |
510 | 36 | String line = StringUtils.clean(keyValueLine); |
511 | 36 | if (line == null) { |
512 | 0 | return null; |
513 | |
} |
514 | 36 | StringBuilder keyBuffer = new StringBuilder(); |
515 | 36 | StringBuilder valueBuffer = new StringBuilder(); |
516 | |
|
517 | 36 | boolean buildingKey = true; |
518 | |
|
519 | 1040 | for (int i = 0; i < line.length(); i++) { |
520 | 1004 | char c = line.charAt(i); |
521 | |
|
522 | 1004 | if (buildingKey) { |
523 | 530 | if (isKeyValueSeparatorChar(c) && !isCharEscaped(line, i)) { |
524 | 35 | buildingKey = false; |
525 | |
} else { |
526 | 495 | keyBuffer.append(c); |
527 | |
} |
528 | |
} else { |
529 | 474 | if (valueBuffer.length() == 0 && isKeyValueSeparatorChar(c) && !isCharEscaped(line, i)) { |
530 | |
|
531 | |
} else { |
532 | 412 | valueBuffer.append(c); |
533 | |
} |
534 | |
} |
535 | |
} |
536 | |
|
537 | 36 | String key = StringUtils.clean(keyBuffer.toString()); |
538 | 36 | String value = StringUtils.clean(valueBuffer.toString()); |
539 | |
|
540 | 36 | if (key == null || value == null) { |
541 | 1 | String msg = "Line argument must contain a key and a value. Only one string token was found."; |
542 | 1 | throw new IllegalArgumentException(msg); |
543 | |
} |
544 | |
|
545 | 35 | log.trace("Discovered key/value pair: {}={}", key, value); |
546 | |
|
547 | 35 | return new String[]{key, value}; |
548 | |
} |
549 | |
|
550 | |
private static Map<String, String> toMapProps(String content) { |
551 | 13 | Map<String, String> props = new LinkedHashMap<String, String>(); |
552 | |
String line; |
553 | 13 | StringBuilder lineBuffer = new StringBuilder(); |
554 | 13 | Scanner scanner = new Scanner(content); |
555 | 40 | while (scanner.hasNextLine()) { |
556 | 27 | line = StringUtils.clean(scanner.nextLine()); |
557 | 27 | if (isContinued(line)) { |
558 | |
|
559 | 1 | line = line.substring(0, line.length() - 1); |
560 | 1 | lineBuffer.append(line); |
561 | 1 | continue; |
562 | |
} else { |
563 | 26 | lineBuffer.append(line); |
564 | |
} |
565 | 26 | line = lineBuffer.toString(); |
566 | 26 | lineBuffer = new StringBuilder(); |
567 | 26 | String[] kvPair = splitKeyValue(line); |
568 | 26 | props.put(kvPair[0], kvPair[1]); |
569 | 26 | } |
570 | |
|
571 | 13 | return props; |
572 | |
} |
573 | |
|
574 | |
public String getName() { |
575 | 28 | return this.name; |
576 | |
} |
577 | |
|
578 | |
public void clear() { |
579 | 0 | this.props.clear(); |
580 | 0 | } |
581 | |
|
582 | |
public boolean containsKey(Object key) { |
583 | 0 | return this.props.containsKey(key); |
584 | |
} |
585 | |
|
586 | |
public boolean containsValue(Object value) { |
587 | 0 | return this.props.containsValue(value); |
588 | |
} |
589 | |
|
590 | |
public Set<Entry<String, String>> entrySet() { |
591 | 18 | return this.props.entrySet(); |
592 | |
} |
593 | |
|
594 | |
public String get(Object key) { |
595 | 35 | return this.props.get(key); |
596 | |
} |
597 | |
|
598 | |
public boolean isEmpty() { |
599 | 225 | return this.props.isEmpty(); |
600 | |
} |
601 | |
|
602 | |
public Set<String> keySet() { |
603 | 23 | return this.props.keySet(); |
604 | |
} |
605 | |
|
606 | |
public String put(String key, String value) { |
607 | 73 | return this.props.put(key, value); |
608 | |
} |
609 | |
|
610 | |
public void putAll(Map<? extends String, ? extends String> m) { |
611 | 0 | this.props.putAll(m); |
612 | 0 | } |
613 | |
|
614 | |
public String remove(Object key) { |
615 | 0 | return this.props.remove(key); |
616 | |
} |
617 | |
|
618 | |
public int size() { |
619 | 2 | return this.props.size(); |
620 | |
} |
621 | |
|
622 | |
public Collection<String> values() { |
623 | 0 | return this.props.values(); |
624 | |
} |
625 | |
|
626 | |
public String toString() { |
627 | 26 | String name = getName(); |
628 | 26 | if (DEFAULT_SECTION_NAME.equals(name)) { |
629 | 0 | return "<default>"; |
630 | |
} |
631 | 26 | return name; |
632 | |
} |
633 | |
|
634 | |
@Override |
635 | |
public boolean equals(Object obj) { |
636 | 0 | if (obj instanceof Section) { |
637 | 0 | Section other = (Section) obj; |
638 | 0 | return getName().equals(other.getName()) && this.props.equals(other.props); |
639 | |
} |
640 | 0 | return false; |
641 | |
} |
642 | |
|
643 | |
@Override |
644 | |
public int hashCode() { |
645 | 0 | return this.name.hashCode() * 31 + this.props.hashCode(); |
646 | |
} |
647 | |
} |
648 | |
|
649 | |
} |