пятница, 24 сентября 2010 г.

Wrote test, use mockito...drink mockito!

Hi all!

In our project we have parts that are written by our outsource partners. We are using their services in our code. Services injected from spring to our beans.

Yesterday, they made a weekly commit, and I've found out that my tests were broken :( One of their developer had commented my stub that implemented their interface. They said that he didn't want to change any code although I needed my stub to collect and analyze input in it.

I started solving this problem learning about mock objects. On my previous work, one of my coworkers used mockito and he liked it a lot :) I've found the information about it here: site. I suppose it's good that the last version was released in may. Also, I like projects that have good documentation and have *-all.jar. Mockito has all of that!

I needed only one method from two interfaces. And I've written simple mock objects and inject this mocks in spring.


SomeService mockService = Mockito.mock(SomeService.class);
Mockito.doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
String[] users = (String[])invocation.getArguments()[0];
String title = (String)invocation.getArguments()[1];
//Collect information here.
return null;
}
}).when(mockService).
someMethod(Mockito.<string[]>any(), Mockito.anyString());

Also, I have got some troubles when injecting this service in spring: I could not do something like this:
myService.setSomeService(mockService);

because I am using @Transactional and having Proxy object. After some google searches I've found solution for this(I think, that it's not ideal, because I found some info about @Configurable beans and wrote TODO ;))

((MyServiceImpl)( ((Advised)myService).getTargetSource().getTarget())).setSomeService(mockService);


I commited this and...drink mockito :)!

среда, 1 сентября 2010 г.

Authentication problems in multithreading

In my application, I have a tree widget that using ajax for loading nodes. Tomcat create some threads to service requests. Each requests validate security ticket that stores in http session:

authenticationService.validate(ticket, null);

And some threads can not validate this ticket and service throws AuthenticationException. If you have particular problem, then you should look at alfresco's jira: ALF-3789. This bug was fixed in revision 21065 at trunk. To fix it in my code, I simple override bean:

<!--Override alfresco-->
    <bean name="ticketsCache" class="org.alfresco.repo.cache.EhCacheAdapter">
       <property name="cache">
          <bean class="org.springframework.cache.ehcache.EhCacheFactoryBean" >
             <property name="cacheManager">
                <ref bean="internalEHCacheManager" />
             </property>
             <property name="cacheName">
                <value>org.alfresco.cache.ticketsCache</value>
             </property>
          </bean>
       </property>
    </bean>

After this all works fine :)

пятница, 20 августа 2010 г.

Fun with alfresco types.

Good day!

Yesterday, I had some fun with alfresco types and permissions. I've created simple type in my model that looks like this:
<type name="my:simpleType">
  <title>Simple type</title>
  <properties>
    <property name="my:prop">
       <type>d:int</type>
    </property>
  </properties>
</type>

I've included it as child-association to the other type and tried to create it:
Node simpleProp = parentNode.addProperty("my:parentProp", "my:simpleType");
  simpleProp.setProperty("my:prop", 1);//AccessDeniedException here!

But, when I ran JUnit test, it threw AccessDeniedException on setProperty for that node. I started debugging alfresco sources, but still I couldn't understand what is going on. Why can I create parent node, add node to association, but can not set property for this node???!!! I tried to use NodeService without JCR API, but I've got the same trouble. Then, I discovered all sources of alfresco's permission subsystem(PermissionServiceImpl, SecurityProvider, etc) with acegi, but still in vain :(

By the end of the day, I have got two cigarettes and zero ideas. After first cigarette, I was looking at my model trying to find problem and finally I've got it! My new type had no parent type! When I added
<parent>cm:content</parent>

everything worked fine! That was hard day: one day - one string...fast work :)

среда, 4 августа 2010 г.

Search content by dates in alfresco using CMIS API

Hi all!

I have a new project that use Alfresco as a backend to store content. I need select content by date. User specifies dates and the service should find and display it. I've chosen CMIS API. It supports query language like SQL to get content. But I've got a problem. I need to specify the date in my query, that looks like this:
SELECT * FROM my:content WHERE my:modifyDate < 01.01.2010
My problem is a date format. In alfresco wiki, I found this description:
datetime literals Based on SQL-92 with 'T' replacing in ,
But it doesn't work. Why???!!! Because I had simply copy-pasted the format for SimpleDateFormat: yyyy-MM-dd'T'HH.mm.ss.SSS. Next hour I could not get why it doesn't work. That format is wrong and must be: yyyy-MM-dd'T'HH:mm:ss.SSS'Z' Also, in alfresco SVN and I've found a good helper class for this: org.alfresco.util.CachingDateFormat. But I didn't hav it in my build of alfresco(I use community build version 3.3, but it was build early). When I upgrade, I will use this class, but for now I wrote simple type helper that looks like this:
package com.blogspot.jajatips.alfresco.util;

import org.apache.commons.lang.time.FastDateFormat;
import java.util.Date;

/**
 * Date: 03.08.2010
 * @author Dmitry N. Pokidov
 */
public class TypeHelper {
    public static String formatDate(Date date) {
        //TODO: CachingDateFormat.getCmisSqlDatetimeFormat()
        FastDateFormat cmisDateFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
        return new StringBuilder("TIMESTAMP '").append(cmisDateFormat.format(date)).append("'").toString();
    }
}
As you can see, I use FastDateFormat from common-lang, that cache date formats. Also, their formats are thread safe, which is very important if you have some static queries.

Well, I'm going to request to make changes in SimpleDateFormat in alfresco wiki, so no one would have this trouble :)

UPD: Good news, I have fixed alfresco wiki page :)

понедельник, 18 января 2010 г.

Getting changes from collection

Good morning :)

Usually to update some entities in database I use service that must be calculate changes from new and old object and update changes in database(using DAO, for example). And the common task is update collections. I wrote simple class that calculate changes from old and new collections and create three collections: to delete, to update and to create. You can see class below and some description for it

package com.blogspot.jajatips.utils;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.*;

public class ChangesCollections<T> {
 private static class CollectionFactory<T> {
  private static final Log log = LogFactory.getLog(CollectionFactory.class);
  private Class<? extends Collection> classCollection = ArrayList.class;

  private CollectionFactory(Class<? extends Collection> classCollection) {
   this.classCollection = classCollection;
  }

  @SuppressWarnings("unchecked")
  public Collection<T> getNewCollection() {
   try {
    return classCollection.newInstance();
   } catch (Exception e) {
    log.warn("Can not create collection for class [" + classCollection.getName() + "], create java.util.ArrayList");
    return new ArrayList<T>();
   }
  }
 }

 private Collection<T> deleteCollection;
 private Collection<T> addCollection;
 private Collection<T> updateCollection;

 public ChangesCollections(Collection<T> oldCollection, Collection<T> newCollection) {
  CollectionFactory<T> factory = new CollectionFactory<T>(oldCollection.getClass());

  this.deleteCollection = factory.getNewCollection();
  this.addCollection = factory.getNewCollection();
  this.updateCollection = factory.getNewCollection();

  addCollection.addAll(newCollection);
  deleteCollection.addAll(oldCollection);
  Iterator<T> addIt = addCollection.iterator();
  while (addIt.hasNext()) {
   T addObject = addIt.next();
   if (contains(oldCollection, addObject)) {
    updateCollection.add(addObject);
    deleteCollection.remove(addObject);
    addIt.remove();
   }
  }
 }

 public Collection<T> getDeleteCollection() {
  return deleteCollection;
 }

 public Collection<T> getAddCollection() {
  return addCollection;
 }

 public Collection<T> getUpdateCollection() {
  return updateCollection;
 }

 private boolean contains(Collection<T> collection, T object) {
  if (object.getClass().isAssignableFrom(Predicate.class)) {
   return CollectionUtils.exists(collection, (Predicate) object);
  }

  return collection.contains(object);
 }
}

First of all, to build this code you need next libraries: commons-collections and commons-logging. Also, I like generics and use it in all my code, because I like compile errors more than runtime errors :).
Now, about the code. To create collections, I use inner class CollectionFactory, that try to create class of collection same as original collection, if it can not do this, it create standard java.util.List. As you can see, code is simple. All that it done is iterate over collections and put elements in other three collections.

I hope that this class helps you in your projects :)

среда, 18 ноября 2009 г.

Good examples of Java EE patterns.

Good Day.

In the last message from SDN, I received information about "Java EE Patterns and Best Practices Project". That is excellent idea, with excellent realization. I recommend to all developers that use Java EE to see this examples.

To get it, you should have mercurial and checkout repository, you can do it with following command:

hg clone https://hg.kenai.com/hg/javaee-patterns~hg .

To view and run examples, you need Netbeans IDE.

Also you can discuss examples here: http://kenai.com/projects/javaee-patterns?cid=e9803

понедельник, 31 августа 2009 г.

Write compressing rolling file appender for log4j

UPD: One good man leave a good comment :)

I think this is now supported in the standard;

http://logging.apache.org/log4j/companions/extras/apidocs/org/apache/log4j/rolling/TimeBasedRollingPolicy.html


Good day!

On the last week, we gave our software to support specialists. They installed the program to production servers. And over three thousand users began to work with it. At the on of that week, I didn't hear nothing about our product. This is good the good sign! :) But, we have one little problem, log file that wrote program had size little more 1Gb!!! This is not very good result :) Server's administrator sent me this log file. I analyzed it. Unfortunately, all information in it was very useful and didn't find anything that I can remove from log.

For logging, we use log4j. If you never use log4j, I recommend to read official document. Log4j have interface Appender. When you want log something, you call some code, looks like this:

...
import org.apache.log4j.Logger;

public class YourClass {
  private static final Logger log = Logger.getLogger(YourClass.class)
  ...
  public void someMethod() {
    log.info("Call someMethod()");
  }
  ...
}

Log4j pass all logging events through appenders, that you configure. By default, you have many appenders, that can send log over SMTP, JMS and do other interesting things. But, this list has no appenders that can periodically compress log file. To solve this problem, I write my own appender, that can periodically compress current log file. Because, I need compress only files, I inherit my appender from FileAppender and override subAppend() method. I don't speak more and simple show you result code:)

import org.apache.log4j.FileAppender;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.zip.GZIPOutputStream;

/**
 * Appender that periodically compress current log.
 * You need only pass one setting - period after that log should compress.
 * Available periods are MINUTLY, HOURLY, DAILY, WEEKLY, MONTHLY.
 *
 * @author Pokidov.Dmitry
 *         Date: 27.08.2009
 */
public class CompressingRollingFileAppender extends FileAppender {
 enum Period {MINUTLY, HOURLY, DAILY, WEEKLY, MONTHLY}

 private GregorianCalendar rollDate;
 private Period period = Period.HOURLY;
 private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm");

 @Override
 public void activateOptions() {
  super.activateOptions();
  setAppend(false);
 }

 /**
  * Period for rolling. By default - {@code HOURLY}
  * @param period new period. Available values: MINUTLY, HOURLY, DAILY, WEEKLY, MONTHLY.
  */
 public void setPeriod(String period) {
  try {
   this.period = Period.valueOf(period);
  } catch (IllegalArgumentException e) {
   LogLog.warn("No period with name [" + period + "]", e);
  }

  LogLog.debug("Set roll period to [" + this.period + "]");
 }

 @Override
 public void setAppend(boolean flag) {
  LogLog.warn("Compression rolling appender doesn't support append option(always set to false)");
 }

 protected void rollOver() {
  String currentFileName = getFile();
  File currentFile = new File(currentFileName);
  File zipFile = new File(currentFileName + "." + dateFormat.format(new Date()) + ".gz");
  try {
   /*
     actually, I use helper that read all bytes, using NIO.
     see http://jajatips.blogspot.com/2008/11/reading-from-inputstream.html
   */
   FileInputStream stream = new FileInputStream(currentFile);
   byte[] content = new byte[](stream.available());
   stream.read(content);

   GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new FileOutputStream(zipFile));
   gzipOutputStream.write(content, 0, content.length);
   gzipOutputStream.finish();
   gzipOutputStream.flush();
   gzipOutputStream.close();

   setFile(currentFileName, false, getBufferedIO(), getBufferSize());
   rollDate.add(getDateFieldByPeriod(), 1);
  } catch (IOException e) {
   LogLog.error("Error compress current log file [" + currentFile + "] to zip file [" + zipFile + "]", e);
  }
 }

 @Override
 protected void subAppend(LoggingEvent event) {
  if (rollDate == null) {
   rollDate = new GregorianCalendar();
   rollDate.add(getDateFieldByPeriod(), 1);
  }

  GregorianCalendar now = new GregorianCalendar();
  if (rollDate.before(now)) {
   rollOver();
  }

  super.subAppend(event);
 }

 private int getDateFieldByPeriod() {
  switch (period) {
   case MINUTLY:   return GregorianCalendar.MINUTE;
   case HOURLY:    return GregorianCalendar.HOUR;
   case DAILY:     return GregorianCalendar.DAY_OF_MONTH;
   case WEEKLY:    return GregorianCalendar.WEEK_OF_MONTH;
   case MONTHLY:   return GregorianCalendar.MONTH;
   default: return GregorianCalendar.MINUTE;
  }
 }
}

Also, I wrote test, because I don't want situation when error in logging subsystem cause crash in the application :)

import junit.framework.Assert;
import junit.framework.TestCase;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import java.io.File;
import java.io.StringReader;
import java.io.IOException;
import java.util.Properties;

/**
 * @author Pokidov.Dmitry
 *         Date: 28.08.2009
 */
public class CompressingRollingFileAppenderTest extends TestCase {
 private static final String LOG_DIR="log";

 private static class LogRun implements Runnable {
  private Logger log = Logger.getLogger(LogRun.class);

  @Override
  public void run() {
   for (int i = 0; i < 150; i++) {
    log.info("Log " + i);
    try {
     Thread.sleep(1000);
    } catch (InterruptedException e) {
     log.error("Interrupt", e);
    }
   }
  }
 }

 /**
  * Create 2 threads and create some log in it.
  */
 public void testAppender() {
  File dirLog = new File(LOG_DIR);
  try {
   for (File f : dirLog.listFiles()) {
    if (!f.delete()) {
     System.err.println("Cann't delete old log file [" + f.getName() + "]");
     Assert.fail("Cann't delete old log file [" + f.getName() + "]");
    }
   }

   PropertyConfigurator.configure(getLogProperties());

   Thread logThread1 = new Thread(new LogRun());
   Thread logThread2 = new Thread(new LogRun());

   logThread1.start();
   logThread2.start();

   logThread1.join();
   logThread2.join();
  } catch (Exception e) {
   e.printStackTrace();
   Assert.fail("Error while execute test: " + e.getMessage());
  }

  Assert.assertEquals(3, dirLog.list().length);
 }

 private Properties getLogProperties() throws IOException {
  Properties result = new Properties();
  String cfg = "log4j.debug=TRUE\n" +
               "log4j.rootLogger=INFO, main\n" +
               "\n" +
               "log4j.appender.main=com.otr.security.server.utils.CompressingRollingFileAppender\n" +
               "log4j.appender.main.Period=MINUTLY\n" +
               "log4j.appender.main.Append=true\n" +
               "log4j.appender.main.layout=org.apache.log4j.PatternLayout\n" +
               "log4j.appender.main.layout.ConversionPattern=[%d] %5p [%t] (%F:%L) - %m%n\n" +
               "log4j.appender.main.File=" + LOG_DIR + "/test.log";

  result.load(new StringReader(cfg));

  return result;
 }
}
And the production log4j.properties:
log4j.debug=TRUE
log4j.rootLogger=INFO, main

log4j.logger.com.mchange=ERROR
log4j.logger.org.springframework=ERROR

log4j.appender.main=server.utils.CompressingRollingFileAppender
log4j.appender.main.Period=DAILY
log4j.appender.main.File=log/server.log
log4j.appender.main.layout=org.apache.log4j.PatternLayout
log4j.appender.main.layout.ConversionPattern=[%d] %5p [%t] (%F:%L) - %m%n

Most popular

Authors