Skip to content

Commit 18c99e4

Browse files
committed
ensuring thread safe framework reload
1 parent 8b1918b commit 18c99e4

File tree

6 files changed

+163
-42
lines changed

6 files changed

+163
-42
lines changed

src/org/opensolaris/opengrok/authorization/AuthorizationEntity.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
*
5252
* @author Krystof Tulinger
5353
*/
54-
public abstract class AuthorizationEntity implements Nameable, Serializable {
54+
public abstract class AuthorizationEntity implements Nameable, Serializable, Cloneable {
5555

5656
private static final long serialVersionUID = 1L;
5757
/**
@@ -63,6 +63,32 @@ public abstract class AuthorizationEntity implements Nameable, Serializable {
6363

6464
protected transient boolean working = true;
6565

66+
public AuthorizationEntity() {
67+
}
68+
69+
/**
70+
* Copy constructor for the entity:
71+
* <ul>
72+
* <li>copy flag</li>
73+
* <li>copy name</li>
74+
* <li>deep copy of the setup</li>
75+
* <li>copy the working attribute</li>
76+
* </ul>
77+
*
78+
* @param x the entity to be copied
79+
*/
80+
public AuthorizationEntity(AuthorizationEntity x) {
81+
flag = x.flag;
82+
name = x.name;
83+
setup = new TreeMap<>(x.setup);
84+
working = x.working;
85+
}
86+
87+
public AuthorizationEntity(AuthControlFlag flag, String name) {
88+
this.flag = flag;
89+
this.name = name;
90+
}
91+
6692
/**
6793
* Load this entity with given parameters.
6894
*
@@ -102,6 +128,14 @@ public abstract class AuthorizationEntity implements Nameable, Serializable {
102128
*/
103129
abstract public boolean setPlugin(IAuthorizationPlugin plugin);
104130

131+
/**
132+
* Perform a deep copy of the entity.
133+
*
134+
* @return the new instance of this entity
135+
*/
136+
@Override
137+
abstract public AuthorizationEntity clone();
138+
105139
/**
106140
* Get the value of flag
107141
*

src/org/opensolaris/opengrok/authorization/AuthorizationFramework.java

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ protected String getClassName(IAuthorizationPlugin plugin) {
203203
*
204204
* @return the stack containing plugins/other stacks
205205
*/
206-
protected synchronized AuthorizationStack getStack() {
206+
public synchronized AuthorizationStack getStack() {
207207
return stack;
208208
}
209209

@@ -212,29 +212,31 @@ protected synchronized AuthorizationStack getStack() {
212212
*
213213
* @param s new stack to be used
214214
*/
215-
protected synchronized void setStack(AuthorizationStack s) {
215+
public synchronized void setStack(AuthorizationStack s) {
216216
this.stack = s;
217217
}
218218

219219
/**
220220
* Add an entity into the plugin stack.
221221
*
222+
* @param stack the stack
222223
* @param entity the authorization entity (stack or plugin)
223224
*/
224-
protected synchronized void addPlugin(AuthorizationEntity entity) {
225+
protected void addPlugin(AuthorizationStack stack, AuthorizationEntity entity) {
225226
if (stack != null) {
226227
stack.add(entity);
227228
}
228229
}
229230

230231
/**
231232
* Add a plugin into the plugin stack. This has the same effect as invoking
232-
* addPlugin(IAuthorizationPlugin, REQUIRED).
233+
* addPlugin(stack, IAuthorizationPlugin, REQUIRED).
233234
*
235+
* @param stack the stack
234236
* @param plugin the authorization plugin
235237
*/
236-
public synchronized void addPlugin(IAuthorizationPlugin plugin) {
237-
addPlugin(plugin, AuthControlFlag.REQUIRED);
238+
public void addPlugin(AuthorizationStack stack, IAuthorizationPlugin plugin) {
239+
addPlugin(stack, plugin, AuthControlFlag.REQUIRED);
238240
}
239241

240242
/**
@@ -252,44 +254,49 @@ public synchronized void addPlugin(IAuthorizationPlugin plugin) {
252254
* <b>The plugin's load method is NOT invoked at this point</b></p>
253255
*
254256
* This has the same effect as invoking addPlugin(new
255-
* AuthorizationEntity(flag, getClassName(plugin), plugin).
257+
* AuthorizationEntity(stack, flag, getClassName(plugin), plugin).
256258
*
259+
* @param stack the stack
257260
* @param plugin the authorization plugin
258261
* @param flag the flag for the new plugin
259262
*/
260-
public synchronized void addPlugin(IAuthorizationPlugin plugin, AuthControlFlag flag) {
263+
public void addPlugin(AuthorizationStack stack, IAuthorizationPlugin plugin, AuthControlFlag flag) {
261264
if (stack != null) {
262265
LOGGER.log(Level.INFO, "Plugin class \"{0}\" was not found in configuration."
263266
+ " Appending the plugin at the end of the list with flag \"{1}\"",
264267
new Object[]{getClassName(plugin), flag});
265-
addPlugin(new AuthorizationPlugin(flag, getClassName(plugin), plugin));
268+
addPlugin(stack, new AuthorizationPlugin(flag, getClassName(plugin), plugin));
266269
}
267270
}
268271

269272
/**
270-
* Remove and unload all plugins.
273+
* Remove and unload all plugins from the stack.
271274
*
275+
* @param stack the stack
272276
* @see AuthorizationEntity#unload()
273277
*/
274-
public synchronized void removeAll() {
275-
unloadAllPlugins();
276-
stack = RuntimeEnvironment.getInstance().getPluginStack();
278+
public void removeAll(AuthorizationStack stack) {
279+
unloadAllPlugins(stack);
277280
}
278281

279282
/**
280-
* Load all plugins. If any plugin has not been loaded yet it is marked as
281-
* failed.
283+
* Load all plugins in the stack. If any plugin has not been loaded yet it
284+
* is marked as failed.
285+
*
286+
* @param stack the stack
282287
*/
283-
public synchronized void loadAllPlugins() {
288+
public void loadAllPlugins(AuthorizationStack stack) {
284289
if (stack != null) {
285290
stack.load(new TreeMap<>());
286291
}
287292
}
288293

289294
/**
290-
* Unload all plugins
295+
* Unload all plugins in the stack
296+
*
297+
* @param stack the stack
291298
*/
292-
public synchronized void unloadAllPlugins() {
299+
public void unloadAllPlugins(AuthorizationStack stack) {
293300
if (stack != null) {
294301
stack.unload();
295302
}
@@ -363,17 +370,18 @@ private IAuthorizationPlugin loadClass(String classname) throws ClassNotFoundExc
363370
/**
364371
* Traverse list of files which possibly contain a java class and then
365372
* traverse a list of jar files to load all classes which are contained
366-
* within them. Each class is loaded with {@link #handleLoadClass(String)}
367-
* which delegates the loading to the custom class loader
368-
* {@link #loadClass(String)}.
373+
* within them into the given stack. Each class is loaded with
374+
* {@link #handleLoadClass(String)} which delegates the loading to the
375+
* custom class loader {@link #loadClass(String)}.
369376
*
377+
* @param stack the stack where to add the loaded classes
370378
* @param classfiles list of files which possibly contain a java class
371379
* @param jarfiles list of jar files containing java classes
372380
*
373381
* @see #handleLoadClass(String)
374382
* @see #loadClass(String)
375383
*/
376-
private void loadClasses(List<File> classfiles, List<File> jarfiles) {
384+
private void loadClasses(AuthorizationStack stack, List<File> classfiles, List<File> jarfiles) {
377385
IAuthorizationPlugin pf;
378386
for (File file : classfiles) {
379387
String classname = getClassName(file);
@@ -383,7 +391,7 @@ private void loadClasses(List<File> classfiles, List<File> jarfiles) {
383391
// load the class in memory and try to find a configured space for this class
384392
if ((pf = handleLoadClass(classname)) != null && !stack.setPlugin(pf)) {
385393
// if there is not configured space -> append it to the stack
386-
addPlugin(pf);
394+
addPlugin(stack, pf);
387395
}
388396
}
389397

@@ -399,7 +407,7 @@ private void loadClasses(List<File> classfiles, List<File> jarfiles) {
399407
// load the class in memory and try to find a configured space for this class
400408
if ((pf = handleLoadClass(classname)) != null && !stack.setPlugin(pf)) {
401409
// if there is not configured space -> append it to the stack
402-
addPlugin(pf);
410+
addPlugin(stack, pf);
403411
}
404412
}
405413
} catch (IOException ex) {
@@ -433,7 +441,7 @@ private String getClassName(JarEntry f) {
433441
* @see Configuration#getPluginDirectory()
434442
*/
435443
@SuppressWarnings("unchecked")
436-
public synchronized void reload() {
444+
public void reload() {
437445
if (pluginDirectory == null || !pluginDirectory.isDirectory() || !pluginDirectory.canRead()) {
438446
LOGGER.log(Level.WARNING, "Plugin directory not found or not readable: {0}. "
439447
+ "All requests allowed.", pluginDirectory);
@@ -455,22 +463,35 @@ public Object run() {
455463
}
456464
});
457465

458-
// clean the stack
459-
removeAll();
466+
// clone a new stack not interfering with the current stack
467+
AuthorizationStack newStack = RuntimeEnvironment.getInstance().getPluginStack().clone();
460468

461469
// increase the current plugin version tracked by the framework
462470
increasePluginVersion();
463471

464-
// refresh the current configuration if there was any change
465-
stack = RuntimeEnvironment.getInstance().getPluginStack();
466-
467472
// load all other possible plugin classes
468-
loadClasses(
473+
loadClasses(newStack,
469474
IOUtils.listFilesRec(pluginDirectory, ".class"),
470475
IOUtils.listFiles(pluginDirectory, ".jar"));
471476

472477
// fire load events
473-
loadAllPlugins();
478+
loadAllPlugins(newStack);
479+
480+
/**
481+
* Replace the stack in a synchronized block to avoid inconsistent state
482+
* between the stack change and currently executing requests performing
483+
* some authorization on the same stack.
484+
*
485+
* @see #performCheck is also marked as synchronized
486+
*/
487+
AuthorizationStack oldStack = stack;
488+
synchronized (this) {
489+
stack = newStack;
490+
}
491+
492+
// clean the old stack
493+
removeAll(oldStack);
494+
oldStack = null;
474495
}
475496

476497
/**
@@ -578,7 +599,15 @@ private boolean checkAll(HttpServletRequest request, String cache, Nameable enti
578599
return overallDecision;
579600
}
580601

581-
private boolean performCheck(Nameable entity, Predicate<IAuthorizationPlugin> predicate) {
602+
/**
603+
* Perform the actual check for the entity.
604+
*
605+
* @param entity either a project or a group
606+
* @param predicate a predicate that decides if the authorization is
607+
* successful for the given plugin
608+
* @return true if entity is allowed; false otherwise
609+
*/
610+
private synchronized boolean performCheck(Nameable entity, Predicate<IAuthorizationPlugin> predicate) {
582611
return stack.isAllowed(entity, predicate);
583612
}
584613
}

src/org/opensolaris/opengrok/authorization/AuthorizationPlugin.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,20 @@ public class AuthorizationPlugin extends AuthorizationStack {
4949
public AuthorizationPlugin() {
5050
}
5151

52+
/**
53+
* Clone the plugin:
54+
* <ul>
55+
* <li>copy the superclass {@link AuthorizationStack}</li>
56+
* <li>sets the plugin to {@code null}</li>
57+
* </ul>
58+
*
59+
* @param x the plugin to be copied
60+
*/
61+
public AuthorizationPlugin(AuthorizationPlugin x) {
62+
super(x);
63+
plugin = null;
64+
}
65+
5266
public AuthorizationPlugin(AuthControlFlag flag, IAuthorizationPlugin plugin) {
5367
this(flag, plugin.getClass().getCanonicalName() == null ? plugin.getClass().getName() : plugin.getClass().getCanonicalName(), plugin);
5468
}
@@ -217,4 +231,18 @@ public boolean isWorking() {
217231
public boolean hasPlugin() {
218232
return plugin != null;
219233
}
234+
235+
/**
236+
* Clone the plugin:
237+
* <ul>
238+
* <li>copy the superclass {@link AuthorizationStack}</li>
239+
* <li>sets the plugin to {@code null}</li>
240+
* </ul>
241+
*
242+
* @return new instance of {@link AuthorizationPlugin}
243+
*/
244+
@Override
245+
public AuthorizationPlugin clone() {
246+
return new AuthorizationPlugin(this);
247+
}
220248
}

src/org/opensolaris/opengrok/authorization/AuthorizationStack.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,26 @@ public class AuthorizationStack extends AuthorizationEntity {
5151
public AuthorizationStack() {
5252
}
5353

54+
/**
55+
* Copy constructor from another stack
56+
* <ul>
57+
* <li>copy the superclass {@link AuthorizationEntity}</li>
58+
* <li>perform a deep copy of the contained stack (using
59+
* {@link AuthorizationEntity#clone()}</li>
60+
* </ul>
61+
*
62+
* @param x the stack to be copied
63+
*/
64+
public AuthorizationStack(AuthorizationStack x) {
65+
super(x);
66+
stack = new ArrayList<>(x.stack.size());
67+
for (AuthorizationEntity e : x.getStack()) {
68+
stack.add(e.clone());
69+
}
70+
}
71+
5472
public AuthorizationStack(AuthControlFlag flag, String name) {
55-
this.flag = flag;
56-
this.name = name;
73+
super(flag, name);
5774
}
5875

5976
/**
@@ -241,4 +258,18 @@ public boolean setPlugin(IAuthorizationPlugin plugin) {
241258
}
242259
return ret;
243260
}
261+
262+
/**
263+
* Clone the stack:
264+
* <ul>
265+
* <li>copy the superclass {@link AuthorizationEntity}</li>
266+
* <li>perform a deep copy of the contained stack</li>
267+
* </ul>
268+
*
269+
* @return new instance of {@link AuthorizationStack}
270+
*/
271+
@Override
272+
public AuthorizationStack clone() {
273+
return new AuthorizationStack(this);
274+
}
244275
}

test/org/opensolaris/opengrok/authorization/AuthorizationFrameworkTest.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ public static StackSetup[][] params() {
653653
public void testPluginsGeneric() {
654654
AuthorizationFramework instance = getInstance();
655655
instance.setStack(setup.stack);
656-
instance.loadAllPlugins();
656+
instance.loadAllPlugins(setup.stack);
657657

658658
boolean actual;
659659
String format = "%s <%s> was <%s> for entity %s";
@@ -685,8 +685,7 @@ public static void tearDownClass() {
685685
}
686686

687687
static private AuthorizationFramework getInstance() {
688-
AuthorizationFramework.getInstance().removeAll();
689-
RuntimeEnvironment.getInstance().setPluginStack(new AuthorizationStack(AuthControlFlag.REQUIRED, "default stack"));
688+
AuthorizationFramework.getInstance().removeAll(AuthorizationFramework.getInstance().getStack());
690689
return AuthorizationFramework.getInstance();
691690
}
692691

test/org/opensolaris/opengrok/web/ProjectHelperTestBase.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,12 @@ public static void tearDownClass() {
256256
}
257257

258258
protected void invokeRemoveAll() {
259-
env.setPluginStack(new AuthorizationStack(AuthControlFlag.REQUIRED, "default-stack"));
260-
AuthorizationFramework.getInstance().removeAll();
259+
AuthorizationFramework.getInstance().removeAll(AuthorizationFramework.getInstance().getStack());
260+
AuthorizationFramework.getInstance().setStack(new AuthorizationStack(AuthControlFlag.REQUIRED, "default-stack"));
261261
}
262262

263263
protected void invokeAddPlugin(IAuthorizationPlugin plugin) {
264-
AuthorizationFramework.getInstance().addPlugin(plugin);
264+
AuthorizationFramework.getInstance().addPlugin(AuthorizationFramework.getInstance().getStack(), plugin);
265265
}
266266

267267
protected AuthorizationFramework getInstance() {

0 commit comments

Comments
 (0)