Wednesday, March 30, 2011

Automatically delete Tomcat access log old files

On the post Enable Tomcat acess log we saw how Tomcat can be easily configured to log all HTTP requests coming to our server.
For some reason, Tomcat doesn't have a mechanism that also makes sure to delete old access log files. This is a bit weird, because I assume that Tomcat is using Log4j in order to log the requests, and Log4j has a built in mechanism for rotating and deleting old log files.
Anyway, since Tomcat doesn’t take care of all these access log files that are being added, and since a typical production server may produce access log files of several giga per day, your disk might get out of space pretty quick.
we will build a small class that will handle old access log files deletion.
First, we need to get the following information:
  • Tomcat access log files directory.
  • Access log file structure. The default structure is: localhost_access_log.yyyy-MM-dd.txt
  • Access log date format: The default structure is: yyyy-MM-dd
  • Number of access log files we would like to keep as backup. For example, if we choose: 10 it means that we would like to keep the last 10 access log files.
The idea is very simply:
  • We get all access log files on the directory.
  • We extract the date of each log file.
  • We sort all the files on ascending order.
  • We delete the oldest files making sure to keep the requested number of backups.
Let’s have a look at our access log files deletion class. It’s code is quite simple:
package com.bashan.blog.log;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.File;
import java.io.FilenameFilter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Bashan
*/
public class TomcatAccessLogCleaner extends TimerTask {
  private static final Log log = LogFactory.getLog(TomcatAccessLogCleaner.class);
  private static final String DEFAULT_LOG_FILE_PATTERN = "localhost_access_log\\.yyyy-MM-dd\\.txt";
  private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
  private String dateFormat;
  private Pattern logFilePat;
  private File logFileDir;
  private int numBackups;
 
  public TomcatAccessLogCleaner(File logFileDir, int numBackups) {
    this(logFileDir, DEFAULT_LOG_FILE_PATTERN, DEFAULT_DATE_FORMAT, numBackups);
}
 
  public TomcatAccessLogCleaner(File logFileDir, String logFilePattern, String dateFormat, int numBackups) {
    this.dateFormat = dateFormat;
    this.logFileDir = logFileDir;
String pat = logFilePattern.replace(dateFormat, "(.+?)");
logFilePat = Pattern.compile(pat);
    this.numBackups = numBackups;
}
 
  public void clean() {
log.info("Starting to clean old Tomcat access logs. Number of backups to keep: " + numBackups);
File[] files = logFileDir.listFiles(new FilenameFilter() {
      public boolean accept(File dir, String file) {
        return logFilePat.matcher(file).matches();
}
});
List<LogFile> logFiles = new ArrayList<LogFile>(files.length);
    for (File file : files) {
      try {
LogFile logFile = new LogFile(file, logFilePat, dateFormat);
logFiles.add(logFile);
}  
      catch (ParseException pe) {
}
 
Collections.sort(logFiles, new Comparator<LogFile>() { 
@Override
      public int compare(LogFile o1, LogFile o2) {
        return o1.getLogDate().compareTo(o2.getLogDate());
}
});
 
    int numFilesToClean = logFiles.size() - numBackups;
    int removed = 0;
    for (int i = 0; i < numFilesToClean; i++) {
LogFile logFile = logFiles.get(i);
log.debug("Deleting access log file: " + logFile);
      if (!logFile.getFile().delete()) {
log.warn("Failed deleting log file");
}
}
log.info("Finished cleaning old Tomcat access logs. Total log files: " +
logFiles.size() + ". Deleted: " + removed + " of " + Math.max(0, numFilesToClean));
}
 
public static class LogFile {
  private File file;
  private Date logDate;
  public LogFile(File file, Pattern pattern, String dateFormat) throws ParseException {
Matcher matcher = pattern.matcher(file.getName());
    if (matcher.find()) {
String dateStr = matcher.group(1);
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
logDate = sdf.parse(dateStr);
      this.file = file;
}
}
 
  public File getFile() {
    return file;
}
 
  public void setFile(File file) {
    this.file = file;
 
  public Date getLogDate() {
    return logDate;
}
 
  public void setLogDate(Date logDate) {
    this.logDate = logDate;
}
 
  public void run() {
clean();
}
}
Note that TomcatAccessLogCleaner extends TimeTask. This enables easily using this class with a timer, allowing it to run every fix interval, and clean Tomcat access log files.
You can download this class here.
Let’s have a look of an example, showing how this class can be used in a servlet. The class will be scheduled to run automatically every 24 hours and clean old access log files. The class will be scheduled once when servlet loads. Don’t forget that you have to map servlets on your web.xml:
package com.bashan.blog.log;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import java.io.File;
import java.util.Timer;
/**
* @author Bashan
*/
public class TomcatAccessLogCleanerServlet extends HttpServlet {
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
Timer time = new Timer();
TomcatAccessLogCleaner tomcatAccessLogCleaner = new TomcatAccessLogCleaner(new File("c:\\tomcat access log dir"), 10);
time.scheduleAtFixedRate(tomcatAccessLogCleaner, 0, 1000 * 60 * 60 * 24);
}
}
You can download the servlet here.

Monday, March 28, 2011

List of Mobile Devices and Brands as SQL inserts

I compiled a list of all mobile vendor (brands like Apple, Nokia) and models (mobile devices like iPhone, Galaxy S) as SQL inserts.
This list is a good place to start, but mobile devices are being added all the time. If you have a web application that needs to show list of mobile devices this list can help you. Note that the table contains both vendor and model, so it is best to show data in two drop downs: one for vendors and one for models. The drop down of the vendors is used as a filter to the drop down of the models.
This is the structure of the devices table (mySQL style):
CREATE TABLE `device` (
`vendor` varchar(100) NOT NULL,
`model` varchar(100) NOT NULL,
PRIMARY KEY (`vendor`,`model`)
) ENGINE=InnoDB
The list of the inserts if very long. You can download it by using this link.

Tuesday, March 22, 2011

How to Jump to an Anchor using JavaScript

Just like links/urls are a great way of moving between pages, anchors are great way of moving between locations on the same page.
Suppose we would like to move to a specific location on a page above some <div> tag. All we have to do is simply put an <a> tag above that div and give it some name. For example:
<a name="example"></a>
<div>
...
...
...
</div>

How do we jump to this specific location (named: example)? By putting an <a> on the place from which we would like to jump to this location. For example, somewhere else on the page we put:

<a href="#example">Press here to show example</a>

Note, that the name of the anchor for which we would like to jump contains the hash sign (#) as a prefix.

Now, suppose we would like to jump to some location by clicking something other than <a> (anchor) tag. For example by clicking a simple button. How can we jump to that location? Since we are not using anchor tag, we can no longer be directed to that location automatically.

We can use JavaScript in order to accomplish exactly the same behavior. Let’s see how it can be easily done with our button and by setting the “window.location.hash” property :

<input type="button" value="Press here to show example" onclick="window.location.hash = 'example';"/>

Note that on the JavaScript code we no longer need to use the hash (#) prefix.

Monday, March 21, 2011

Automatically Add “www” to your site on Tomcat or: Canonical Hostnames

Sometimes we would like that our website domain will always contain “www”.

I can this of two good reasons (beside of making all your site’s urls neat and unified) for adding “www” to your site urls:

  • To avoid cookie issues: Cookies are being stored for domains. www.example.com and example.com are 2 different domains when it comes to cookies. So if you would like to store some data in a cookie without wondering where did it disappear. Unify all the urls of your site to contain “www” and you are always working on the same domain.
  • To avoid security issues: Sometimes we would like to do some JavaScript coding on our site that may require us to be on the same domain (for example: running some JavaScript code from an iFrame on the parent window). Since url with “www” and url without “www” is considered to be a different domain, by making sure all of our urls contain the “www” prefix, we make sure we will not fall in all kind of cross domain security traps.

So, how do we make sure all our urls will always contain the “www” prefix" on Tomcat?

Luckily for us there is a great Java open source project named tuckey that can easily help us to accomplish that task. Tuckey is Url rewrite filter. It rewrites our urls according to set of predefined rules. As you noticed tuckey is doing much more than just adding “www” to our urls, but this is not for the scope of this post. You can learn more about tuckey and download it from this web site: http://www.tuckey.org/.

After you download and install tuckey on your web application (it is well details on the tuckey web site how this thing can be done), you simply have to add this rule to you urlrewrite.xml file:

<rule>
  <name>Canonical Hostnames</name>
  <condition name="host" operator="notequal">^www.mydomain.com</condition>
  <condition name="host" operator="notequal">^$</condition>
  <from>^/(.*)</from>
  <to type="redirect" last="true">http://www.mydomain.com/$1</to>
</rule>

Make sure that this rule is the first rule on your urlrewrite.xml file.

Tuesday, March 15, 2011

iPhone udid Structure

udid is Unique Device Identifier. Every iPhone device has it's own udid.
Other smart phones (Android, Blackberry) also has an id that uniquely identifies the device.
udid value can be easily taken from the class UIDevice by using this simple objective C line:
NSString* udid = [UIDevice currentDevice].uniqueIdentifier;
I am currently working on a mobile installation tracking process. In order to track installations you must compare between the udid of the device when the installed application is first launched.

While conducting tests with udid, I noticed that iPhone udid structure is different when running on X-Code simulator comparing to running on a real device.
Where running on X-Code simulator the value contains the following pattern:

HHHHHHHH-HHHH-HHHH-HHHH-HHHHHHHHHHHH
where "H" stands for Hex number. Note that if there are letters (A-F) they are capital.
For example:
79550EDC-2A86-5BDF-8EE2-219B7FCBD223
When running on a real device the udid value is 40 hex chars. The letters are small. Note that there are no minus ("-") characters used.
For example:

ce43446816952ff4513e1389c5e1d3ac3a856141
I think that the udid is different in the simulator and on a real device because the simulator simply return the udid of the Mac machine and the Mac machine udid string is different than the iPhone device udid.

Getting radio button value from radio group using jQuery

Getting radio button value from a group of radio buttons is an easy task using jQuery. All you have to do is to apply the correct selector. If we have a group of radio buttons named: radioGroup all if we have to do is:
$('input[name=radioGroup]:checked').val();
Let's have a look how it looks on a real HTML page containing 3 radio buttons and a check button. By clicking the “Check Value” button we will open an alert popup showing the selected radio value:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title> TEST </title>
<script type="text/javascript" src="jquery-1.3.2.min.js"></script>
</head>
<body>  
  <input type="radio" id="inpSmall" name="radioGroup" value="small" checked><label for="inpSmall">Small</label><br>
  <input type="radio" id="inpMedium" name="radioGroup" value="medium"><label for="inpMedium">Medium</label><br>
  <input type="radio" id="inpLarge" name="radioGroup" value="large"><label for="inpLarge">Large</label><br><br>
  <input value="Check Value" type="button" onclick="alert('Radio group value: ' + $('input[name=radioGroup]:checked').val());">
</body>
</html>

Tuesday, February 22, 2011

Generating random hex strings using MS SQL Server

In the post Generating random numbers in a specific range using MS SQL Server we saw how it is possible to generate random integer number for a given range. We will use the function dbo.random_range from this post in order to create a function that generates a random string of hex numbers of any length.
The idea is very simple. Suppose we would like to generate a string of length N, then all we have to do is loop N times and generate a random number POSITION in the range of 1 to 16. For each of the random values we take a substring of one character in the generated POSITION from a predefined string containing all Hex digits (“0” to “9” and “a” to “f”). After we done we will have a string containing N Hex digits.
Let’s have a look at the SQL SERVER code:

CREATE FUNCTION random_hex(@length int)
RETURNS varchar(2000)
AS
BEGIN
  DECLARE @index int
  DECLARE @seq varchar(16) 
  DECLARE @result varchar(2000)
  DECLARE @digit char(1)
  SET @seq = '0123456789abcdef'
  SET @index = 0
  SET @result = ''
  WHILE @index < @length
  BEGIN
    SET @digit = SUBSTRING(@seq, dbo.random_range(1, 16), 1)
    SET @result = @result + @digit
    SET @index = @index + 1
  END 
  RETURN @result
END
GO

Monday, February 21, 2011

Generating random numbers in a specific range using MS SQL Server

In the post Generating Random numbers in a SELECT statement using MS SQL Server we saw how random numbers can be generated using Microsoft MS SQL Server. We will use the dbo.random function from this blog in order to create a new function called dbo.random_range.
This function will generate a random number for a specific range. For example: we might need to generate a random number between 100 and 1000.
The dbo.random_range function will return a random number between @start and @end parameters. The dbo.random function generates a random number between 0 and 1. In order to create a random number for a given range we will use a small mathematical calculation which is pretty easy and straightforward. Therefore we will simply understand it by looking at the dbo.random_range function:

CREATE FUNCTION [dbo].[random_range](@start int, @end int)
RETURNS int
AS
BEGIN
  return @start + dbo.random() * (@end - @start + 1)
END

Sunday, February 13, 2011

Create Click to Call link on Mobile Phones

Mobile web browsers are also phones. Creating links that lead directly to a call can be a very easy and continent way.
Most mobile web browser support click to call links. For obvious reason, all of them will confirm with the user before making the call.
Creating click to call links is very easy. In general there are 2 main types of click to call links:
* Apple devices
* All the other phones, especially feature phones (old generation mobile phones).

Apple use a simple way of doing call links. All you have to do is use the "tel" protocol instead of "http" when creating a link. For example, if you would like to make a call to the phone number: 1234567 when use clicks your link, all you have to do is:

<a href="tel:1234567">Call me now!</a>

This will also work:

<a href="tel://1234567">Call me now!</a>
Feature phones do not support Apple method, but most of them do allow call links, by using a similar way:
<a href="wtai://wp/mc;1234567">Call me now!</a>

Of course, if you are advertising a service or a product worldwide and you are using click to call link, it is always good to put a full phone number (country code prefix) and make am end to end test in order to make sure the number really makes it to its destination.

Friday, January 21, 2011

Enable Tomcat access log

On Windows machines Tomcat access log is not turned on by default.

The Access log is used to to log all HTTP requests coming to your server.

In order to activate Tomcat access log you simply have to add the following “Valve” to your “server.xml” file located on: <TOMCAT INSTALLATION>/conf/server.xml

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" 
prefix="localhost_access_log." suffix=".txt" pattern="common" resolveHosts="false"/>

Note that the “Value” can already appear as remarked in you “server.xml” file, so you just have to unremark it.