Samstag, 20. Juni 2009

Stripes Image Streamingresolution

I'm often asked how to stream an image from sources like a database blob or a folder which is not shared by the servlet container to the client using stripes. This is done by extending the class net.sourceforge.stripes.action.StreamingResolution. The most simple way:
public Resolution view( ) {
  ...
  String mimeType = getContext().getServletContext().getMimeType(fileName);
  final byte[] file = readFileToByteArray(absolutFilePath);//this method needs to be implemented
  return new StreamingResolution(mimeType) {
     @Override
     protected void stream(HttpServletResponse response) throws Exception {
        response.getOutputStream().write(file);
     }
  };
}
When streaming static content I'd recommend a more complex solution using headers to modify file names or caching behavior of the browser:
public Resolution view( ) {
  ...
  String mimeType = getContext().getServletContext().getMimeType(fileName);
  final byte[] file = readFileToByteArray(absolutFilePath);//this method needs to be implemented
  return new StreamingResolution(mimeType) {
     @Override
     protected void stream(HttpServletResponse response) throws Exception {
        setFilename("TheHolyHandGranade.gif");
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DAY_OF_YEAR, 30);
        Date expires = calendar.getTime();
        response.setDateHeader("Expires", expires.getTime());
        response.getOutputStream().write(file);
     }
  };
}
Hope this helps :-)

Donnerstag, 18. Juni 2009

Partially mocking the class under test

When writing a class it sometimes happens that you have to write a method which invokes methods of the same instance. I was wondering how to test those methods in isolation. Here is an abstract example demonstrating the problem: Class XYZ
  • method doThisIfCase1OrThatOtherwise
  • method doThis
  • method doThat
Testing doThis and doThat should not be a problem. But testing doThisIfCase1OrThatOtherwise without testing doThis and doThat again seemed impossible to me. Of course it would be possible to extract doThis and doThat into a separate class and then mock this class when testing doThisIfCase1OrThatOtherwise. But there are cases doing this smells a bit like over engineering. In cases I don't know what to do I ask Misko my test and clean code oracle. As always he provided me with a solution: Override the methods doThis and doThat and assert that they get called. This is an example from my "real" world: The class under test:
...
public void openAndCreateIfNotExists() throws IOException {
if (!destinationClassPathFile.exists()) {
 createEmptyClassPathFile();
} else {
 openExistingClassPathFile();
}
}

void createEmptyClassPathFile() {
eclipseClassPathDocument = DocumentHelper.createDocument();
eclipseClassPathDocument.addElement("classpath");
}

void openExistingClassPathFile() throws IOException {
SAXReader reader = new SAXReader();
try {
 eclipseClassPathDocument = reader.read(destinationClassPathFile);
} catch (DocumentException e) {
 throw new IOException("Invalid class path file " + destinationClassPathFile.getCanonicalPath(), e);
}
}
...
The test:
@Test
public void testOpenAndCreateIfNotExistsCreates() throws IOException {
File file = new File("src-test/testOpenAndCreateIfNotExistsCreates.testFile");
if (file.exists())
 file.delete();
file.deleteOnExit();
assertFalse(file.exists());
final StringBuilder stringBuilder = new StringBuilder();
EclipseClassPathManipulator eclipseClassPathManipulator = new EclipseClassPathManipulator(file) {
 @Override
 void createEmptyClassPathFile() {
  stringBuilder.append("igotcalled");
 }
};
eclipseClassPathManipulator.openAndCreateIfNotExists();
assertEquals("igotcalled", stringBuilder.toString());
}

@Test
public void testOpenAndCreateIfNotExistsOpens() throws IOException {
final StringBuilder stringBuilder = new StringBuilder();
File file = new File("src-test/testOpenAndCreateIfNotExistsOpens.testFile");
file.createNewFile();
file.deleteOnExit();
assertTrue(file.exists());
EclipseClassPathManipulator eclipseClassPathManipulator = new EclipseClassPathManipulator(file) {
 @Override
 void openExistingClassPathFile() throws IOException {
  stringBuilder.append("igotcalled");
 }
};
eclipseClassPathManipulator.openAndCreateIfNotExists();
assertEquals("igotcalled", stringBuilder.toString());
}

Mittwoch, 17. Juni 2009

java.io.FileFilter - a pragmatic and powerful way to select files

Today I discovered the FileFilter interface. A little bit late maybe - but better late than never!
File[] libs = libDir.listFiles(new FileFilter() {
  public boolean accept(File pathname) {
     return !pathname.isDirectory() && pathname.getName().endsWith(".jar");
  }
});
This example is a very simple one but since this is a method instead of a pattern like *.jar there are no limitations how the filtering is done. Regular expressions, web services, databases, relations of file names, ... I do like that approach since even it is powerful it's still simple and easy to use. If all API's were designed like this software development would be simpler and faster.

Dienstag, 2. Juni 2009

Providing user specific style sheets (using Stripes)

Customers often want to have a web application in their own CI. Stripes doesn't ship with a ready to use solution for this problem - but that would be impossible since every application has its own requirements for such a feature. But Stripes ships with a feature called user friendly urls. IMHO there are a lot of other web frameworks providing this feature so the following solution is not strictly tied the Stripes framework. The main idea behind this is to include standard css files in a common way:
<link rel="stylesheet" type="text/css" href="/css/ci.css">
and override special style definitions with user specific ones:
<link rel="stylesheet" type="text/css" href="/user-styles/css/ci.css">
The folder css is a real existing one. The folder user-styles is an action bean bound to the url /user-styles/ with a default handler method. The part after /user-styles(/css/default.css) is mapped to a property called "requestedFile". The default handler now builds a real path in some way like this: "<userSpecificStyleFolder>/<userId>/requestedFile" Then the default handler loads this file using the real path(java.nio provides a fast way to read files) and streams it out. If all the images are defined in the css files using a relative path, these images will also be requested using the default handler. So even user specific images are supported. If the users are organized in companies a company wide style could be achieved if the userId is replaced with the companyId when building the real path("<userSpecificStyleFolder>/<companyId>/requestedFile"). In Stripes the action bean would look like this:
@StrictBinding
@UrlBinding("/user-styles/{requestedFile}")
public class UserStyleAction implements ActionBean {
 private static final Log log = LogFactory.getLog(UserStyleAction.class);
 static final String FILE_SEPARATOR = System.getProperty("file.separator");
 private ActionBeanContext actionBeanContext;
 private String requestedFile;
 private String basePath;
 private User user;

 public UserStyleAction() {}

 public UserStyleAction(String basePath) {
  this.basePath = basePath;
 }

 @Before(on="serveFile")
 public Resolution loadUserFromSessionAndSendErrorIfNotLoggedIn() {
  user = (User) actionBeanContext.getRequest().getSession().getAttribute(User.class.toString());
  if (user == null)
   return new ErrorResolution(404);
  return null;
 }

 @Before
 public void initBasePath() throws IOException {
  // This is done to make this project run in every eclipse --> use a config
  // file in real world apps
  String realPathOfClass = actionBeanContext.getServletContext().getRealPath("UserStyleAction.class");
  String webAppFolder = realPathOfClass.replace("UserStyleAction.class", "");
  basePath = new File(webAppFolder + FILE_SEPARATOR + "WEB-INF" + FILE_SEPARATOR + "user-styles").getCanonicalPath();
 }

 @DefaultHandler
 public Resolution serveFile() throws IOException {
  try {
   String mimeType = actionBeanContext.getServletContext().getMimeType(requestedFile);
   File file = new File(buildAbsoluteFilePath(basePath, user));
   throwExceptionInCaseOfDirectoryTraversalAttack(basePath, file);
   FileInputStream fileInputStream = new FileInputStream(file);
   StreamingResolution streamingResolution = new StreamingResolution(mimeType, fileInputStream);
   return streamingResolution;
  } catch (Exception e) {
   if (log.isDebugEnabled())
    log.debug("Error reading file " + requestedFile + " requested by user " + user, e);
   return new ErrorResolution(404);
  }
 }

 String buildAbsoluteFilePath(String basePath, User user) {
  return basePath + FILE_SEPARATOR + user.getId() + FILE_SEPARATOR + requestedFile;
 }

 void throwExceptionInCaseOfDirectoryTraversalAttack(String basePath, File file) throws IOException {
  if (!file.getCanonicalPath().startsWith(basePath + FILE_SEPARATOR + user.getId()))
   throw new RuntimeException("Attempt to hack the application");
 }

 @Validate
 public void setRequestedFile(String requestedFile) {
  this.requestedFile = requestedFile;
 }

 @Override
 public ActionBeanContext getContext() {
  return actionBeanContext;
 }

 @Override
 public void setContext(ActionBeanContext context) {
  this.actionBeanContext = context;
 }

}
A full sample is available at the svn repository

  © Blogger template 'Morning Drink' by Ourblogtemplates.com 2008

Back to TOP