/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wicket.security;
import java.util.HashMap;
import java.util.Map;
import junit.framework.TestCase;
import org.apache.wicket.Page;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.security.actions.ActionFactory;
import org.apache.wicket.security.hive.BasicHive;
import org.apache.wicket.security.hive.Hive;
import org.apache.wicket.security.hive.HiveMind;
import org.apache.wicket.security.hive.SimpleCachingHive;
import org.apache.wicket.security.hive.authorization.Principal;
import org.apache.wicket.security.hive.authorization.SimplePrincipal;
import org.apache.wicket.security.hive.authorization.permissions.ComponentPermission;
import org.apache.wicket.security.hive.config.HiveFactory;
import org.apache.wicket.security.pages.MockLoginPage;
import org.apache.wicket.security.pages.SpeedPage;
import org.apache.wicket.security.swarm.SwarmWebApplication;
import org.apache.wicket.security.swarm.actions.SwarmAction;
import org.apache.wicket.util.tester.FormTester;
import org.apache.wicket.util.tester.WicketTester;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author marrink
*/
public class SpeedTest extends TestCase
{
private static final Logger log = LoggerFactory.getLogger(SpeedTest.class);
/**
* Nr. of rows and columns in our repeater.
*/
public static final int ROWS = 50;
public static final int COLS = 50;
private boolean useCache = false;
private Map<Key, Result> results = new HashMap<Key, Result>();
/**
* number of denied permissions. 1=0% denied, 2 =50% denied, 3 =66% denied, etc
*/
private int denialFactor = 1;
/**
* The swarm application used for the test.
*/
protected WebApplication application;
/**
* Handle to the mock environment.
*/
protected WicketTester mock;
private void mySetUp()
{
mock = new WicketTester(application = new SwarmWebApplication()
{
@Override
protected Object getHiveKey()
{
// if we were using servlet-api 2.5 we could get the contextpath
// from the servletcontext
return "test";
}
@Override
protected void setUpHive()
{
HiveFactory factory = new DummyFactory(useCache, denialFactor, getActionFactory());
HiveMind.registerHive(getHiveKey(), factory);
}
@Override
public Class< ? extends Page> getHomePage()
{
return SpeedPage.class;
}
public Class< ? extends Page> getLoginPage()
{
return MockLoginPage.class;
}
}, "src/test/java/" + getClass().getPackage().getName().replace('.', '/'));
}
private void myTearDown()
{
mock.setupRequestAndResponse();
mock.getWicketSession().invalidate();
mock.processRequestCycle();
mock.destroy();
mock = null;
application = null;
HiveMind.unregisterHive("test");
}
/**
* Performance test with caching enabled, 50% denial rate.
*/
public void cachedpartialDenied()
{
denialFactor = 2;
useCache = true;
doTestRun();
}
/**
* Performance test, no caching, 50% denial rate.
*/
public void noCachePartialDenied()
{
useCache = false;
denialFactor = 2;
doTestRun();
}
/**
* Same performance test but now with caching enabled.
*/
public void cachedAllAllowed()
{
useCache = true;
denialFactor = 1;
doTestRun();
}
/**
* Test performance diff between secure page and "unsecure" page with lots of
* components.
*/
public void noCacheAllAllowed()
{
useCache = false;
denialFactor = 1;
doTestRun();
}
private void doTestRun()
{
mySetUp();
mock.startPage(SpeedPage.class);
mock.assertRenderedPage(MockLoginPage.class);
FormTester form = mock.newFormTester("form");
form.setValue("username", "speed");
form.submit();
mock.assertRenderedPage(SpeedPage.class);
mock.assertVisible("secure");
// weird no html????
// TagTester tag = mock.getTagById("secure");
// assertNotNull(tag);
// assertEquals("not secure", tag.getValue());
int warmup = 100;
// warmup
log.info("warmup");
for (int i = 0; i < warmup; i++)
{
mock.startPage(mock.getLastRenderedPage());
mock.assertRenderedPage(SpeedPage.class);
}
Key unsecured = new Key(denialFactor, useCache, false);
int count = 100;
log.info("measurement");
long start = System.currentTimeMillis();
for (int i = 0; i < count; i++)
mock.startPage(mock.getLastRenderedPage());
long end = System.currentTimeMillis();
results.put(unsecured, new Result(start, end, count, ROWS * COLS));
log.info("security now enabled");
// enable security checks
((SpeedPage) mock.getLastRenderedPage()).setSecured(true);
// warmup
log.info("warmup");
for (int i = 0; i < warmup; i++)
{
mock.startPage(mock.getLastRenderedPage());
mock.assertRenderedPage(SpeedPage.class);
}
Key secured = new Key(denialFactor, useCache, true);
log.info("measurement");
long start2 = System.currentTimeMillis();
for (int i = 0; i < count; i++)
mock.startPage(mock.getLastRenderedPage());
long end2 = System.currentTimeMillis();
results.put(secured, new Result(start2, end2, count, ROWS * COLS));
myTearDown();
}
/**
* Compare performance test results between caching and non caching.
*/
public void testPerformance()
{
cachedAllAllowed();
noCacheAllAllowed();
noCachePartialDenied();
cachedpartialDenied();
assertEquals(8, results.size());
Result noCache1 = results.get(printResults(new Key(1, false, false)));
Result noCache2 = results.get(printResults(new Key(1, false, true)));
assertTrue("secure components are faster than normal components",
(noCache1.end - noCache1.start) / noCache1.runs < (noCache2.end - noCache2.start)
/ noCache2.runs);
long diffNoCache =
(((noCache2.end - noCache2.start) / noCache2.runs) - ((noCache1.end - noCache1.start) / noCache1.runs));
Result cache1 = results.get(printResults(new Key(1, true, false)));
Result cache2 = results.get(printResults(new Key(1, true, true)));
assertTrue((cache1.end - cache1.start) / cache1.runs < (cache2.end - cache2.start)
/ cache2.runs);
long diffCache =
(((cache2.end - cache2.start) / cache2.runs) - ((cache1.end - cache1.start) / cache1.runs));
assertTrue("caching is actually bad for performance", diffCache < diffNoCache);
// 50 % permissions denied
noCache1 = results.get(printResults(new Key(2, false, false)));
noCache2 = results.get(printResults(new Key(2, false, true)));
assertTrue((noCache1.end - noCache1.start) / noCache1.runs < (noCache2.end - noCache2.start)
/ noCache2.runs);
diffNoCache =
(((noCache2.end - noCache2.start) / noCache2.runs) - ((noCache1.end - noCache1.start) / noCache1.runs));
cache1 = results.get(printResults(new Key(2, true, false)));
cache2 = results.get(printResults(new Key(2, true, true)));
assertTrue((cache1.end - cache1.start) / cache1.runs < (cache2.end - cache2.start)
/ cache2.runs);
diffCache =
(((cache2.end - cache2.start) / cache2.runs) - ((cache1.end - cache1.start) / cache1.runs));
assertTrue("caching is actually bad for performance", diffCache < diffNoCache);
}
private Key printResults(Key key)
{
Result result = results.get(key);
log.info("Test results with cache enabled = " + key.caching);
log.info("Testing " + result.components + " components");
if (key.secured)
log.info(result.components / key.denialRate + " component permissions granted");
log.info((key.secured ? "secured" : "unsecured") + " page took "
+ (result.end - result.start) + " ms total, "
+ ((result.end - result.start) / result.runs) + " ms on average over " + result.runs
+ " requests");
if (key.secured)
{
Result result2 = results.get(new Key(key.denialRate, key.caching, false));
long diff =
(((result.end - result.start) / result.runs) - ((result2.end - result2.start) / result2.runs));
log.info("each component security check took " + ((double) diff / result.components)
+ " ms");
}
return key;
// 2500 components, diff per request = 27 ms, time per component =
// 0.0108 ms
// no caching, no permission / principal inheritance, all components
// allowed
// 2500 components, diff per request = 917 ms, time per component =
// 0.3668 ms
// no caching, no permission / principal inheritance, 1250 components
// allowed, 1250 components denied
// caching should dramatically improve situations where no permission is
// found
// 2500 components, diff per request = 23 ms, time per component =
// 0.0092 ms
// with caching, no permission / principal inheritance, all components
// allowed
// 2500 components, diff per request = 18 ms, time per component =
// 0.0072 ms
// with caching, no permission / principal inheritance, 1250 components
// allowed, 1250 components denied
}
private static final class DummyFactory implements HiveFactory
{
private final boolean cache;
private final int denialFactor;
private ActionFactory actionFactory;
/**
* Construct.
*
* @param cache
* use caching or not
* @param deny
* factor for % of permission denied
* @param actionFactory
* factory to create the actions
*/
public DummyFactory(boolean cache, int deny, ActionFactory actionFactory)
{
super();
this.cache = cache;
this.denialFactor = deny;
this.actionFactory = actionFactory;
}
/**
*
* @see org.apache.wicket.security.hive.config.HiveFactory#createHive()
*/
public Hive createHive()
{
BasicHive hive;
if (cache)
hive = new SimpleCachingHive();
else
hive = new BasicHive();
Principal principal = new SimplePrincipal("speed");
SwarmAction action = (SwarmAction) actionFactory.getAction("access, render");
hive.addPermission(principal, new ComponentPermission(
"org.apache.wicket.security.pages.SpeedPage", action));
for (int i = 0; i < ROWS; i++)
{
for (int j = 0; j < COLS / denialFactor; j++)
{
// not granting a permission for each component will add
// linear time to check, the more permissions the more time
// will be added
hive.addPermission(principal, new ComponentPermission(
"org.apache.wicket.security.pages.SpeedPage:rows:" + i + ":cols:" + j
+ ":label", action));
}
}
return hive;
}
}
/**
* Key to store results in a hashMap.
*
* @author marrink
*/
private static final class Key
{
public final int denialRate;
public final boolean caching;
public final boolean secured;
/**
* Construct.
*
* @param denialRate
* @param caching
* @param secured
*/
public Key(int denialRate, boolean caching, boolean secured)
{
super();
this.denialRate = denialRate;
this.caching = caching;
this.secured = secured;
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + (caching ? 1231 : 1237);
result = prime * result + denialRate;
result = prime * result + (secured ? 1231 : 1237);
return result;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Key other = (Key) obj;
if (caching != other.caching)
return false;
if (denialRate != other.denialRate)
return false;
if (secured != other.secured)
return false;
return true;
}
}
/**
* Helper class to store results.
*
* @author marrink
*/
private static final class Result
{
public final long start;
public final long end;
public final int runs;
public final long components;
/**
* Construct.
*
* @param start
* @param end
* @param runs
* @param components
*/
public Result(long start, long end, int runs, long components)
{
super();
this.start = start;
this.end = end;
this.runs = runs;
this.components = components;
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + (int) (components ^ (components >>> 32));
result = prime * result + (int) (end ^ (end >>> 32));
result = prime * result + runs;
result = prime * result + (int) (start ^ (start >>> 32));
return result;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Result other = (Result) obj;
if (components != other.components)
return false;
if (end != other.end)
return false;
if (runs != other.runs)
return false;
if (start != other.start)
return false;
return true;
}
}
}