java / web — 12 comments
21
Nov 07
If you need to render a tree-like structure here’s a way of implementing recursion in JSP/JSTL:
${node.title}
<div class="children">
<c:forEach var="node" items="${node.children}">
<c:set var="node" value="${node}" scope="request"/>
<jsp:include page="node.jsp"/>
</c:forEach>
</div>
The tricky thing is that when you use jsp:include (and for recursion you have to use the dynamic inclusion), normally, you loose all the variables defined in the parent JSP. But if you redefine the variables you need in the “request” scope, then they will be visible in the included JSP too. I assumed that this very JSP is named “node.jsp” and omitted the declarations. Surprisingly, I found neither this nor any other solution on the Web, so I had to invent one.
perl / web — No comments
05
Nov 07
I needed to write a script which runs on the server, gets daily statistics from our rolling log files and sends them by email. The log is splitted into 64Mb files and the total size is limited by 1GB. I didn’t want to parse the whole gigabyte of logs, so I decided to start from the most recent file and read it in the backward order till the previous date. I found two approaches to read file backwards with perl. The first approach is slower but requires a fixed amount of memory (in my case about 2 mins and up to 16Mb RAM):
tie(@lines, "Tie::File", $fname, mode => 0) or die "Can't tie $fname: $!";
$max_lines = $#lines;
for ($i = $max_lines; $i; $i--) {
if (not &$apply($lines[$i])) {
return 0;
}
}
The second approach is faster but requires at least as much available memory as the size of the log file, in fact more than twice as much when processing several files one by one (in my case, 1:02 min and up to 150Mb RAM):
open(LOG, $fname) or die "Can't open $fname: $!";;
@lines = reverse <LOG>;
foreach $line (@lines) {
chomp $line;
if (not &$apply($line)) {
close(LOG);
return 0;
}
}
close(LOG);
So I chose the first approach, because I thought that stable memory footprint on the server is more important than the time the script takes to complete. Here is the full code for the package I wrote that provides handy functions for scanning rolling logfiles forwards and backwards: logscan_pl.txt
And here is a usage sample:
require 'logscan.pl';
$errors = 0;
logscan::scan("Joanna", sub {
$_ = shift;
if (/(.*) INFO.*Returning error by [([^]]*)].*user=[([^]]*)].*roles=[([^]]*)]/) {
my ($time, $uri, $user, $role) = ($1, $2, $3);
$errors++;
print "$time $uri $user $rolen";
}
return 1;
});
print "\\n\\nTotal errors: $errors\\n";
java / spring — No comments
15
Sep 07
If you make a web application that supports several languages with Spring you are definitely familiar with the handy locale resolvers Spring provides. Unfortunately, none of them allows to restrict the number of locales that can be set. We needed such restriction, because some of our database queries depended on the locale. So I wrote a wrapper locale resolver for it:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.LocaleResolver;
/**
* A wrapper for LocaleResolver. Allows to set only one of the predefined set of locales.
*
* @author Ilya Boyandin
*/
public class RestrictiveLocaleResolver implements LocaleResolver {
private final LocaleResolver localeResolver;
private final Locale defaultLocale;
private final List<Locale> supportedLocales;
private RestrictiveLocaleResolver(
LocaleResolver localeResolver, Locale defaultLocale,
Collection<String> supportedLocales) {
this.localeResolver = localeResolver;
this.defaultLocale = defaultLocale;
final List<Locale> locales = new ArrayList<Locale>();
for (String loc : supportedLocales) {
locales.add(new Locale(loc));
}
this.supportedLocales = locales;
}
public LocaleResolver getLocaleResolver() {
return localeResolver;
}
public Locale getDefaultLocale() {
return defaultLocale;
}
public Collection<Locale> getSupportedLocales() {
return Collections.unmodifiableCollection(supportedLocales);
}
@Override
public Locale resolveLocale(HttpServletRequest request) {
final Locale resolved = localeResolver.resolveLocale(request);
final Locale supported = findSupportedLocale(resolved); // this ensures that a locale with one
// of the predefined names will be used
if (supported != null) {
return supported;
} else {
return defaultLocale;
}
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
final Locale supported = findSupportedLocale(locale); // this ensures that a locale with one
// of the predefined names will be used
if (supported != null) {
localeResolver.setLocale(request, response, supported);
} else {
localeResolver.setLocale(request, response, defaultLocale);
}
}
private Locale findSupportedLocale(Locale locale) {
for (int i = 0, len = supportedLocales.size(); i < len; i++) {
final Locale loc = supportedLocales.get(i);
if (loc.equals(locale)) return loc;
}
return null;
}
}
And then you must have something like this in your config file (of course, you can use any other locale resolver instead of SessionLocaleResolver here):
<bean id="localeResolver" class="org.fhj.joanna.web.utils.RestrictiveLocaleResolver">
<constructor-arg><bean class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/></constructor-arg>
<constructor-arg value="de"/>
<constructor-arg><set><value>de</value><value>en</value></set></constructor-arg>
</bean>
db / debugging / java / spring — No comments
03
Sep 07
Few days ago we found out that passing in SQL parameters to a PreparedStatement can affect query optimization when we ran into a problem querying the database from our web app. We constantly got the following error from SQL Server: Adding a value to a ‘datetime’ column caused overflow. In the WHERE clause of the query we had something like DATEADD(month, 2, exam_date) and it went wrong, because there was an invalid exam_date entry in the database table (something like 0095-01-10).
The funny thing is that when we tried to run the very same query in Microsoft Query Analyzer it worked without an error. Then I wrote a short test that queried the database directly using PreparedStatement without Spring and iBatis to localize the problem and found out that when I passed parameters to it the error came, when I put the parameter values into the query itself it didn’t. So it seems that passing the parameters affected the order in which the conditions in the WHERE clause were evaluated: without the parameters another condition was evaluated first so that the errorneous date entry was filtered out before coming to the condition which used DATEADD.