1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.shiro.realm.text;
20
21 import org.apache.shiro.lang.ShiroException;
22 import org.apache.shiro.lang.io.ResourceUtils;
23 import org.apache.shiro.lang.util.Destroyable;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 import java.io.File;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.util.Enumeration;
31 import java.util.Properties;
32 import java.util.concurrent.ExecutorService;
33 import java.util.concurrent.Executors;
34 import java.util.concurrent.ScheduledExecutorService;
35 import java.util.concurrent.TimeUnit;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 public class PropertiesRealm extends TextConfigurationRealm implements Destroyable, Runnable {
85
86
87
88
89
90
91 private static final int DEFAULT_RELOAD_INTERVAL_SECONDS = 10;
92 private static final String USERNAME_PREFIX = "user.";
93 private static final String ROLENAME_PREFIX = "role.";
94 private static final String DEFAULT_RESOURCE_PATH = "classpath:shiro-users.properties";
95
96
97
98
99 private static final Logger LOGGER = LoggerFactory.getLogger(PropertiesRealm.class);
100
101 protected ExecutorService scheduler;
102 protected boolean useXmlFormat;
103 protected String resourcePath = DEFAULT_RESOURCE_PATH;
104 protected long fileLastModified;
105 protected int reloadIntervalSeconds = DEFAULT_RELOAD_INTERVAL_SECONDS;
106
107 public PropertiesRealm() {
108 super();
109 }
110
111
112
113
114
115
116
117
118
119
120
121 public void setUseXmlFormat(boolean useXmlFormat) {
122 this.useXmlFormat = useXmlFormat;
123 }
124
125
126
127
128
129
130
131
132
133
134 public void setResourcePath(String resourcePath) {
135 this.resourcePath = resourcePath;
136 }
137
138
139
140
141
142
143
144
145
146 public void setReloadIntervalSeconds(int reloadIntervalSeconds) {
147 this.reloadIntervalSeconds = reloadIntervalSeconds;
148 }
149
150
151
152
153
154 @Override
155 public void onInit() {
156 super.onInit();
157
158 afterRoleCacheSet();
159 }
160
161 protected void afterRoleCacheSet() {
162 loadProperties();
163
164
165 if (this.resourcePath.startsWith(ResourceUtils.FILE_PREFIX) && scheduler == null) {
166 startReloadThread();
167 }
168 }
169
170
171
172
173 public void destroy() {
174 try {
175 if (scheduler != null) {
176 scheduler.shutdown();
177 }
178 } catch (Exception e) {
179 if (LOGGER.isInfoEnabled()) {
180 LOGGER.info("Unable to cleanly shutdown Scheduler. Ignoring (shutting down)...", e);
181 }
182 } finally {
183 scheduler = null;
184 }
185 }
186
187 protected void startReloadThread() {
188 if (this.reloadIntervalSeconds > 0) {
189 this.scheduler = Executors.newSingleThreadScheduledExecutor();
190 ((ScheduledExecutorService) this.scheduler)
191 .scheduleAtFixedRate(this, reloadIntervalSeconds, reloadIntervalSeconds, TimeUnit.SECONDS);
192 }
193 }
194
195 public void run() {
196 try {
197 reloadPropertiesIfNecessary();
198 } catch (Exception e) {
199 if (LOGGER.isErrorEnabled()) {
200 LOGGER.error("Error while reloading property files for realm.", e);
201 }
202 }
203 }
204
205 private void loadProperties() {
206 if (resourcePath == null || resourcePath.length() == 0) {
207 throw new IllegalStateException("The resourcePath property is not set. "
208 + "It must be set prior to this realm being initialized.");
209 }
210
211 if (LOGGER.isDebugEnabled()) {
212 LOGGER.debug("Loading user security information from file [" + resourcePath + "]...");
213 }
214
215 Properties properties = loadProperties(resourcePath);
216 createRealmEntitiesFromProperties(properties);
217 }
218
219 private Properties loadProperties(String resourcePath) {
220 Properties props = new Properties();
221
222 InputStream is = null;
223 try {
224
225 if (LOGGER.isDebugEnabled()) {
226 LOGGER.debug("Opening input stream for path [" + resourcePath + "]...");
227 }
228
229 is = ResourceUtils.getInputStreamForPath(resourcePath);
230 if (useXmlFormat) {
231
232 if (LOGGER.isDebugEnabled()) {
233 LOGGER.debug("Loading properties from path [" + resourcePath + "] in XML format...");
234 }
235
236 props.loadFromXML(is);
237 } else {
238
239 if (LOGGER.isDebugEnabled()) {
240 LOGGER.debug("Loading properties from path [" + resourcePath + "]...");
241 }
242
243 props.load(is);
244 }
245
246 } catch (IOException e) {
247 throw new ShiroException("Error reading properties path [" + resourcePath + "]. "
248 + "Initializing of the realm from this file failed.", e);
249 } finally {
250 ResourceUtils.close(is);
251 }
252
253 return props;
254 }
255
256
257 private void reloadPropertiesIfNecessary() {
258 if (isSourceModified()) {
259 restart();
260 }
261 }
262
263 private boolean isSourceModified() {
264
265 return this.resourcePath.startsWith(ResourceUtils.FILE_PREFIX) && isFileModified();
266 }
267
268 private boolean isFileModified() {
269
270 String fileNameWithoutPrefix = this.resourcePath.substring(this.resourcePath.indexOf(":") + 1);
271 File propertyFile = new File(fileNameWithoutPrefix);
272 long currentLastModified = propertyFile.lastModified();
273 if (currentLastModified > this.fileLastModified) {
274 this.fileLastModified = currentLastModified;
275 return true;
276 } else {
277 return false;
278 }
279 }
280
281 @SuppressWarnings("unchecked")
282 private void restart() {
283 if (resourcePath == null || resourcePath.length() == 0) {
284 throw new IllegalStateException("The resourcePath property is not set. "
285 + "It must be set prior to this realm being initialized.");
286 }
287
288 if (LOGGER.isDebugEnabled()) {
289 LOGGER.debug("Loading user security information from file [" + resourcePath + "]...");
290 }
291
292 try {
293 destroy();
294 } catch (Exception e) {
295
296 }
297 init();
298 }
299
300 @SuppressWarnings("unchecked")
301 private void createRealmEntitiesFromProperties(Properties properties) {
302
303 StringBuilder userDefs = new StringBuilder();
304 StringBuilder roleDefs = new StringBuilder();
305
306 Enumeration<String> propNames = (Enumeration<String>) properties.propertyNames();
307
308 while (propNames.hasMoreElements()) {
309
310 String key = propNames.nextElement().trim();
311 String value = properties.getProperty(key).trim();
312 if (LOGGER.isTraceEnabled()) {
313 LOGGER.trace("Processing properties line - key: [" + key + "], value: [" + value + "].");
314 }
315
316 if (isUsername(key)) {
317 String username = getUsername(key);
318 userDefs.append(username).append(" = ").append(value).append("\n");
319 } else if (isRolename(key)) {
320 String rolename = getRolename(key);
321 roleDefs.append(rolename).append(" = ").append(value).append("\n");
322 } else {
323 String msg = "Encountered unexpected key/value pair. All keys must be prefixed with either '"
324 + USERNAME_PREFIX + "' or '" + ROLENAME_PREFIX + "'.";
325 throw new IllegalStateException(msg);
326 }
327 }
328
329 setUserDefinitions(userDefs.toString());
330 setRoleDefinitions(roleDefs.toString());
331 processDefinitions();
332 }
333
334 protected String getName(String key, String prefix) {
335 return key.substring(prefix.length(), key.length());
336 }
337
338 protected boolean isUsername(String key) {
339 return key != null && key.startsWith(USERNAME_PREFIX);
340 }
341
342 protected boolean isRolename(String key) {
343 return key != null && key.startsWith(ROLENAME_PREFIX);
344 }
345
346 protected String getUsername(String key) {
347 return getName(key, USERNAME_PREFIX);
348 }
349
350 protected String getRolename(String key) {
351 return getName(key, ROLENAME_PREFIX);
352 }
353 }