Browsing the 2009 May archive
I would be sitting on a gold mine.
SLOC Directory SLOC-by-Language (Sorted) 37890 src java=37890 5026 contrib-utilities java=5026 4457 contrib-crud java=4457 2259 contrib-mootools java=2259 1235 contrib-generaldao java=1235 1185 contrib-emailmanager java=1185 986 jetty-memcache java=986 961 contrib-cache java=961 787 jmemcached java=786,sh=1 640 contrib-thumbnail java=640 503 contrib-blueprint java=503 181 contrib-snapshot java=181 32 contrib-nicedit java=32 Totals grouped by language (dominant language first): java: 56141 (100.00%) sh: 1 (0.00%) Total Physical Source Lines of Code (SLOC) = 56,142 Development Effort Estimate, Person-Years (Person-Months) = 13.73 (164.80) (Basic COCOMO model, Person-Months = 2.4 * (KSLOC**1.05)) Schedule Estimate, Years (Months) = 1.45 (17.39) (Basic COCOMO model, Months = 2.5 * (person-months**0.38)) Estimated Average Number of Developers (Effort/Schedule) = 9.47 Total Estimated Cost to Develop = $ 1,855,214 (average salary = $56,286/year, overhead = 2.40). SLOCCount, Copyright (C) 2001-2004 David A. Wheeler SLOCCount is Open Source Software/Free Software, licensed under the GNU GPL. SLOCCount comes with ABSOLUTELY NO WARRANTY, and you are welcome to redistribute it under certain conditions as specified by the GNU GPL license; see the documentation for details. Please credit this data as "generated using David A. Wheeler's 'SLOCCount'."
One of the first few things I have tackled while creating fabulously40 was stateless pagination. I wanted to keep all the public facing pages stateless to avoid session overhead. Wicket makes pagination brain dead simple at the cost of session use. I wanted to keep the simple programming model yet be completely stateless. Here is my solution to the problem.
Wicket a stateful framework, is actually really good at being stateless.
Example code on how to use it.
update: I would suggest using MixedParamHybridCodingStrategy with the stateless paging navigation to keep links clean.
add(dv = new StatelessDataView<Photo>("pics", getPhotoProvider(), amount, pageParams) {
private static final long serialVersionUID = 1L;
@Override
protected void populateItem(final Item<Photo> arg0) {
}
});
// call back page class, page paramaters passed, the dataview and 12 pagination links.
add(new StatelessSimplePagingNavigator("nav", PhotosPage.class, pageParams, dataView, 12));
The dataview
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.IDataProvider;
public abstract class StatelessDataView<T> extends DataView<T> {
private static final long serialVersionUID = 1L;
private PageParameters pp;
@Override
protected boolean getStatelessHint() {
return true;
}
protected int getPageNumber(final String param) {
String numResult = param;
if(numResult.contains(".wicket-")) {
numResult = numResult.substring(0,numResult.indexOf(".wicket-"));
}
return Integer.valueOf(numResult);
}
public StatelessDataView(final String id, final IDataProvider dataProvider, final PageParameters pp) {
super(id, dataProvider);
this.pp = pp;
if (pp.getString(id) != null) {
int pageNum = getPageNumber(pp.getString(id));
if(pageNum != -1 && pageNum >= 0 && pageNum <= getPageCount()) {
setCurrentPage(getPageNumber(pp.getString(id)));
}
}
}
public StatelessDataView(final String id, final IDataProvider dataProvider, final int itemsPerPage,
final PageParameters pp) {
super(id, dataProvider, itemsPerPage);
this.pp = pp;
if (pp.getString(id) != null) {
int pageNum = getPageNumber(pp.getString(id));
if(pageNum != -1 && pageNum >= 0 && pageNum <= getPageCount()) {
setCurrentPage(getPageNumber(pp.getString(id)));
}
}
}
}
The paging navigator
import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.navigation.paging.IPageable;
import org.apache.wicket.markup.html.navigation.paging.IPagingLabelProvider;
import org.apache.wicket.markup.html.navigation.paging.PagingNavigation;
public class StatelessSimplePagingNavigator extends SimplePagingNavigator {
private static final long serialVersionUID = 1L;
private Class clazz;
private PageParameters pp;
private int current_page;
private int page_count;
private String pageable_id;
private String anchor = null;
@Override
protected boolean getStatelessHint() {
return true;
}
public StatelessSimplePagingNavigator(final String id, final Class clazz, final PageParameters pp,
final IPageable pageable, final int viewsize) {
this(id, clazz, pp, pageable, viewsize, false);
}
public StatelessSimplePagingNavigator(final String id, final Class clazz, final PageParameters pp,
final IPageable pageable, final int viewsize, final boolean anchorSelf) {
super(id, pageable, viewsize, anchorSelf);
this.clazz = clazz;
this.pp = pp;
this.current_page = pageable.getCurrentPage();
this.page_count = pageable.getPageCount();
this.pageable_id = ((Component) pageable).getId();
}
public StatelessSimplePagingNavigator(final String id, final Class clazz, final PageParameters pp,
final IPageable pageable, final int viewsize, final String anchor) {
super(id, pageable, viewsize, false);
this.clazz = clazz;
this.pp = pp;
this.current_page = pageable.getCurrentPage();
this.page_count = pageable.getPageCount();
this.pageable_id = ((Component) pageable).getId();
this.anchor = anchor;
}
@Override
protected void onBeforeRender() {
super.onBeforeRender();
addOrReplace(newStatelessPagingNavigationLink("next", pageable_id, current_page, 1));
addOrReplace(newStatelessPagingNavigationLink("prev", pageable_id, current_page, -1));
}
@Override
protected PagingNavigation newNavigation(final IPageable pageable, final IPagingLabelProvider labelProvider) {
PagingNavigation pg = new PagingNavigation("navigation", pageable, labelProvider) {
private static final long serialVersionUID = 1L;
@Override
protected Link newPagingNavigationLink(final String id, final IPageable pageable, final int pageIndex) {
Component c = (Component) pageable;
// PageParameters p1 = new PageParameters();
// p1.putAll(pp);
pp.put(c.getId(), String.valueOf(pageIndex));
BookmarkablePageLink<Object> lnk = new BookmarkablePageLink<Object>(id, clazz, pp) {
private static final long serialVersionUID = 1L;
@Override
protected void onComponentTag(final ComponentTag arg0) {
super.onComponentTag(arg0);
if (anchor != null) {
if(arg0.getString("href") != null) {
String href = arg0.getString("href").toString();
String atag = anchor.contains("#") ? anchor : "#" + anchor;
arg0.put("href", href + atag);
}
}
}
@Override
public boolean isEnabled() {
return (current_page != pageIndex);
}
};
if (isAnchorSelf()) {
lnk.setAnchor(StatelessSimplePagingNavigator.this);
}
return lnk;
}
};
pg.setViewSize(getViewsize());
return pg;
}
@Override
protected Link<Object> newPagingNavigationIncrementLink(final String id, final IPageable pageable, final int increment) {
Component c = (Component) pageable;
final int page = Integer.valueOf(pp.getString(c.getId()));
pp.put(c.getId(), String.valueOf(page + increment));
return new BookmarkablePageLink<Object>(id, clazz, pp) {
private static final long serialVersionUID = 1L;
@Override
public boolean isVisible() {
return (page + increment) >= 0;
}
};
}
protected Link<Object> newStatelessPagingNavigationLink(final String id, final String pageableId, final int currentPage,
final int increment) {
// PageParameters p1 = new PageParameters();
// p1.putAll(pp);
pp.put(pageableId, String.valueOf(currentPage + increment));
return new BookmarkablePageLink<Object>(id, clazz, pp) {
private static final long serialVersionUID = 1L;
@Override
public boolean isVisible() {
return (currentPage + increment) < page_count && (currentPage + increment) >= 0;
}
};
}
@Override
protected Link<Object> newPagingNavigationLink(final String id, final IPageable pageable, final int pageNumber) {
Component c = (Component) pageable;
// PageParameters p1 = new PageParameters();
// p1.putAll(pp);
pp.put(c.getId(), String.valueOf(pageNumber));
return new BookmarkablePageLink(id, clazz, pp);
}
}
Which inherits from
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.navigation.paging.IPageable;
import org.apache.wicket.markup.html.navigation.paging.IPagingLabelProvider;
import org.apache.wicket.markup.html.navigation.paging.PagingNavigation;
import org.apache.wicket.markup.html.navigation.paging.PagingNavigator;
public class SimplePagingNavigator extends PagingNavigator {
private static final long serialVersionUID = 1L;
private int viewsize = 0;
private boolean anchorSelf = false;
public SimplePagingNavigator(final String id, final IPageable pageable, final int viewsize) {
this(id, pageable, viewsize, false);
}
public SimplePagingNavigator(final String id, final IPageable pageable, final int viewsize, final boolean anchorSelf) {
super(id, pageable);
setOutputMarkupId(true);
this.setViewsize(viewsize);
this.setAnchorSelf(anchorSelf);
}
@Override
protected void onBeforeRender() {
if (get("navigation") != null) {
remove("navigation");
}
if (get("prev") != null) {
remove("prev");
}
if (get("next") != null) {
remove("next");
}
super.onBeforeRender();
if (get("first") != null) {
remove("first");
}
if (get("last") != null) {
remove("last");
}
if (getViewsize() != 0) {
getPagingNavigation().setViewSize(getViewsize());
}
}
@Override
protected PagingNavigation newNavigation(final IPageable pageable, final IPagingLabelProvider labelProvider) {
PagingNavigation pg = new PagingNavigation("navigation", pageable, labelProvider) {
private static final long serialVersionUID = 1L;
@Override
protected Link newPagingNavigationLink(final String id, final IPageable pageable, final int pageIndex) {
Link lnk = (Link) super.newPagingNavigationLink(id, pageable, pageIndex);
if (isAnchorSelf()) {
lnk.setAnchor(SimplePagingNavigator.this);
}
return lnk;
}
};
pg.setViewSize(getViewsize());
return pg;
}
@Override
public boolean isVisible() {
if (getPageable() != null) {
return (getPageable().getPageCount() > 1);
}
return true;
}
public void setAnchorSelf(final boolean anchorSelf) {
this.anchorSelf = anchorSelf;
}
public boolean isAnchorSelf() {
return anchorSelf;
}
public void setViewsize(final int viewsize) {
this.viewsize = viewsize;
}
public int getViewsize() {
return viewsize;
}
}
Update: Just an update, All the issues with Varnish on Solaris have been fixed with the 2.1.4 release. We have been using Varnish on our Solaris production servers since the release with great stability and performance. A big thanks to the Varnish devs and slink for the eventport fixes.
I have dumped varnish as our primary cache due to multiple failures of service. I have tried to make it work but varnish kept insisting on producing 503 XID backend failures on perfectly healthy backends. I have tried doing all types of crazy configuration hacks such as forcing varnish to retry backends via a round-robin director. It did not work out all too well since the round trip added latency when varnish had to re-fetch the document multiple times. The final straw that broke the camel’s back was when varnish configured for a 256mb malloc store grew to an astonishing size of 780mb+ RSS.
I have switched to squid-3 and so far it has been stable and fast. I will later post a matching squid configuration to the one below that does the same thing.
Squid-3 will require this patch for it to compile on Solaris.
Varnish on Solaris is a dud.
List of failures
1. Producing 503 responses for perfectly healthy backends. Backend never even gets contacted.
2. Growing to a crazy size when using the malloc implementation.
3. Segfaulting every hour on the hour with the newest trunk r4080+
Here is the configuration I have used. Feel free to use it if varnish works for you.
#
# This is a basic VCL configuration file for varnish. See the vcl(7)
# man page for details on VCL syntax and semantics.
#
# $Id: default.vcl 1818 2007-08-09 12:12:12Z des $
#
# Default backend definition. Set this to point to your content
# server.
# my wonderful re-try hack, that kinda works.
director www_dir round-robin {
{ .backend = { .connect_timeout = 2s; .host="127.0.0.1"; .port="8001"; } }
{ .backend = { .connect_timeout = 2s; .host="127.0.0.1"; .port="8001"; } }
{ .backend = { .connect_timeout = 2s; .host="127.0.0.1"; .port="8001"; } }
{ .backend = { .connect_timeout = 2s; .host="127.0.0.1"; .port="8001"; } }
}
#backend default { .host = "127.0.0.1"; .port = "8089"; .connect_timeout = 2s; }
sub vcl_recv {
remove req.http.X-Forwarded-For;
set req.http.X-Forwarded-For = client.ip;
set req.grace = 2m;
if (req.http.Accept-Encoding) {
if (req.url ~ "\.(jpeg|jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg|ico|swf|flv|dmg)") {
# No point in compressing these
remove req.http.Accept-Encoding;
} elsif (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} elsif (req.http.Accept-Encoding ~ "deflate") {
set req.http.Accept-Encoding = "deflate";
} else {
# unkown algorithm
remove req.http.Accept-Encoding;
}
}
# don't trust MSIE6
# if (req.http.user-agent ~ "MSIE [1-6]\.") {
# remove req.http.Accept-Encoding;
# }
if (req.http.host == "jira.fabulously40.com") {
pipe;
}
if (req.request == "GET" || req.request == "HEAD") {
if ( req.url ~ "\.(xml|gif|jpg|swf|css|png|jpeg|tiff|tif|svg|ico|pdf|ico|swf)") {
remove req.http.cookie;
lookup;
}
# avoid caching jsps
if ( req.url ~ "\.js([^p]|$)" ) {
remove req.http.cookie;
lookup;
}
}
# don't bother caching large files
if(req.url ~ "\.(mp3|flv|mov|mp4|mpg|mpeg|avi|dmg)") {
pipe;
}
if (req.request != "GET" && req.request != "HEAD") {
pipe;
}
if (req.request == "POST") {
pipe;
}
if (req.http.Expect || req.http.Authorization || req.http.Authenticate || req.http.WWW-Authenticate) {
pipe;
}
# pipe pages with these cookies set
if (req.http.cookie && req.http.cookie ~ "_.*_session=") {
pipe;
}
if (req.http.cookie && req.http.cookie ~ "JSESSIONID=") {
pipe;
}
if (req.http.cookie && req.http.cookie ~ "PHPSESSID=") {
pipe;
}
if (req.http.cookie && req.http.cookie ~ "wordpress_logged_in") {
pipe;
}
lookup;
}
sub vcl_error {
# retry on errors
if (obj.status == 503) {
if ( req.restarts < 12 ) {
restart;
}
}
}
sub vcl_fetch {
# don't cache when these cookies are in place
if(beresp.http.Location || beresp.http.WWW-Authenticate) {
pass;
}
if(beresp.http.cookie && beresp.http.cookie ~ "JSESSIONID=") {
pass;
}
if(beresp.http.Set-Cookie && beresp.http.Set-Cookie ~ "JSESSIONID=") {
pass;
}
if(beresp.http.cookie && beresp.http.cookie ~ "_.*_session=") {
pass;
}
if(beresp.http.Set-Cookie && beresp.http.Set-Cookie ~ "_.*_session=") {
pass;
}
if(beresp.http.cookie && beresp.http.cookie ~ "PHPSESSID=") {
pass;
}
if(beresp.http.Set-Cookie && beresp.http.Set-Cookie ~ "PHPSESSID=") {
pass;
}
if(beresp.http.cookie && beresp.http.cookie ~ "wordpress_logged_in") {
pass;
}
if(beresp.http.Set-Cookie && beresp.http.Set-Cookie ~ "wordpress_logged_in") {
pass;
}
if(beresp.http.Cache-Control && beresp.http.Cache-Control ~ "no-cache") {
pass;
}
if(beresp.http.Pragma && beresp.http.Pragma ~ "no-cache") {
pass;
}
# avoid defaults since we *want* pages cached with cookies
# if (!beresp.cacheable) {
# pass;
# }
# if (beresp.http.Set-Cookie) {
# pass;
# }
#cache for 30 minutes..
if((beresp.http.Cache-Control !~ "max-age" || beresp.http.Cache-Control !~ "s-maxage") && beresp.ttl < 1800s) {
set beresp.ttl = 1800s;
}
set beresp.grace = 2m;
# anonymous users get 10 min delay
if(beresp.http.Content-Type && beresp.http.Content-Type ~ "html" && (beresp.http.Cache-Control !~ "max-age" || beresp.http.Cache-Control !~ "s-maxage")) {
set beresp.ttl = 600s;
}
# remove server affinity cookie from cached pages.
if(beresp.http.Set-Cookie && beresp.http.Set-Cookie ~ "X-SERVERID=") {
remove beresp.http.Set-Cookie;
}
if(beresp.http.Set-Cookie && beresp.http.Set-Cookie ~ "SERVERID=") {
remove beresp.http.Set-Cookie;
}
if(beresp.http.X-Backend) {
remove beresp.http.X-Backend;
}
deliver;
}
I am about to run out the house for the Labor Day weekend, quickie post on my new coding url strategy.
MixedParamHybridUrlCodingStrategy lets you keep stateful multi-pagemap URLs clean while using mixed parameters.
Example…
This will mount “/questions/stupid-category” and convert it to…
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.wicket.IRequestTarget;
import org.apache.wicket.PageParameters;
import org.apache.wicket.protocol.http.request.WebRequestCodingStrategy;
import org.apache.wicket.request.target.coding.HybridUrlCodingStrategy;
import org.apache.wicket.util.string.AppendingStringBuffer;
import org.apache.wicket.util.value.ValueMap;
public class MixedParamHybridUrlCodingStrategy extends HybridUrlCodingStrategy {
private final String[] parameterNames;
public MixedParamHybridUrlCodingStrategy(final String mountPath, final Class pageClass) {
super(mountPath, pageClass);
this.parameterNames = new String[] {};
}
public MixedParamHybridUrlCodingStrategy(final String mountPath, final Class pageClass,final boolean refresh) {
super(mountPath,pageClass,refresh);
this.parameterNames = new String[] {};
}
public MixedParamHybridUrlCodingStrategy(final String mountPath, final Class pageClass,final String[] params) {
super(mountPath,pageClass,false);
this.parameterNames = params;
}
public MixedParamHybridUrlCodingStrategy(final String mountPath, final Class pageClass,final boolean refresh,final String[] params) {
super(mountPath,pageClass,refresh);
this.parameterNames = params;
}
@Override
protected void appendParameters(final AppendingStringBuffer url, final Map parameters)
{
Set parameterNamesToAdd = new HashSet(parameters.keySet());
// Find index of last specified parameter
boolean foundParameter = false;
int lastSpecifiedParameter = parameterNames.length;
while (lastSpecifiedParameter != 0 && !foundParameter)
{
foundParameter = parameters.containsKey(parameterNames[–lastSpecifiedParameter]);
}
if (foundParameter)
{
for (int i = 0; i <= lastSpecifiedParameter; i++)
{
String parameterName = parameterNames[i];
final Object param = parameters.get(parameterName);
String value = param instanceof String[] ? ((String[])param)[0] : (String)param;
if (value == null)
{
value = "";
}
if (!url.endsWith("/"))
{
url.append("/");
}
url.append(urlEncodePathComponent(value));
parameterNamesToAdd.remove(parameterName);
}
}
if (!parameterNamesToAdd.isEmpty())
{
boolean first = true;
final Iterator iterator = parameterNamesToAdd.iterator();
while (iterator.hasNext())
{
url.append(first ? ‘?’ : ‘&’);
String parameterName = (String)iterator.next();
final Object param = parameters.get(parameterName);
String value = param instanceof String[] ? ((String[])param)[0] : (String)param;
url.append(urlEncodeQueryComponent(parameterName)).append("=").append(
urlEncodeQueryComponent(value));
first = false;
}
}
String pageMap = (String)parameters.get(WebRequestCodingStrategy.PAGEMAP);
if (pageMap != null)
{
pageMap = WebRequestCodingStrategy.encodePageMapName(pageMap);
if (!url.endsWith("/"))
{
url.append("/");
}
url.append(WebRequestCodingStrategy.PAGEMAP).append("/").append(
urlEncodePathComponent(pageMap));
}
}
@Override
public CharSequence encode(final IRequestTarget requestTarget) {
String url = String.valueOf(super.encode(requestTarget));
if(url.contains(".wicket")) {
// Rewrite URL from.. /foo?bar=5.wicket-xxx to /foo.wicket-xxx?bar=5
return url.replaceAll("(.*)\\?(.*)\\.(wicket-[0-9]+)$", "$1.$3?$2");
}
return url;
}
@Override
protected ValueMap decodeParameters(final String urlFragment, final Map urlParameters)
{
PageParameters params = new PageParameters();
if(urlFragment == null) {
return params;
}
// Add all url parameters and normalize
for(Iterator<Map.Entry> itr = urlParameters.entrySet().iterator();itr.hasNext();) {
Map.Entry<String,String[]> e = itr.next();
if(e.getValue() != null) {
String val = (e.getValue())[0];
if(val != null) {
if(val.contains(".wicket")) {
val = val.substring(0,val.indexOf(".wicket"));
urlParameters.put(e.getKey(), val);
}
}
}
}
params.putAll(urlParameters);
String urlPath = urlFragment;
if (urlPath.startsWith("/"))
{
urlPath = urlPath.substring(1);
}
if (urlPath.length() > 0)
{
String[] pathParts = urlPath.split("/");
if (pathParts.length > parameterNames.length)
{
throw new IllegalArgumentException(
"Too many path parts, please provide sufficient number of path parameter names");
}
for (int i = 0; i < pathParts.length; i++)
{
if(pathParts[i].contains(".wicket")) {
pathParts[i].substring(0,pathParts[i].indexOf(".wicket"));
}
if (WebRequestCodingStrategy.PAGEMAP.equals(pathParts[i]))
{
params.put(WebRequestCodingStrategy.PAGEMAP,
WebRequestCodingStrategy.decodePageMapName(urlDecodePathComponent(pathParts[i])));
}
params.put(parameterNames[i], urlDecodePathComponent(pathParts[i]));
}
}
return params;
}
}
GrizzlyConnector patch for Jetty to work with QueuedThreadPool
1 Comment | Filed under administration programmingThis is a late night post, so I am just going to make it short. This patch lets you use QueuedThreadPool with the Grizzly Connector. This is a monkey patch, getMaxThreads() should be moved up into the Thread Interface.
--- GrizzlyConnection-old.java Sat May 2 01:08:02 2009 +++ GrizzlyConnector.java Sat May 2 00:56:37 2009 @@ -51,6 +51,8 @@ import org.mortbay.jetty.webapp.WebAppContext; import org.mortbay.log.Log; import org.mortbay.thread.BoundedThreadPool; +import org.mortbay.thread.QueuedThreadPool; +import org.mortbay.thread.ThreadPool; /* ------------------------------------------------------------------------------- */ /** @@ -178,8 +180,13 @@ controller.setProtocolChainInstanceHandler(instanceHandler); Pipeline pipeline = new DefaultPipeline(); - pipeline.setMaxThreads( - ((BoundedThreadPool)getServer().getThreadPool()).getMaxThreads()); + if(getServer().getThreadPool() instanceof BoundedThreadPool) { + pipeline.setMaxThreads( + ((BoundedThreadPool)getServer().getThreadPool()).getMaxThreads()); + } else { + pipeline.setMaxThreads( + ((QueuedThreadPool)getServer().getThreadPool()).getMaxThreads()); + } controller.setPipeline(pipeline); }


(3 votes, average: 4.67 out of 5)