my free code

вторник, 23 сентября 2008 г.

Simple Example for running apectj in standalone application

I'm use IDEA for running app, you can use any other.
First at all we need download aspectJ from their web site.

Application in my example run with on fly or wrapping real-time mode. For this you must run JVM with additional parameter:
-javaagent:aspectjweaver.jar
in IDEA what you need is just put aspectjweaver.jar in root of you application.

Also we need aop.xml aspectj configuration file. It placed in src/META-INF directroy.
Here is my aop.xml file body:

<aspectj>
<aspects>
<aspect name="ru.aspect.AspectJTest"/>
</aspects>
<weaver>
<include within="ru.*"/>
</weaver>
</aspectj>


weaver additional options such as debug and byte code dump you can see here

create aspect class:

package ru;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AspectJTest {
@Around("execution(* *..*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
jp.proceed();
}
}


in this example aspect will be invoked any time, when same method invoked.
i know 3 annotation for aspects method:
@Around
@Before
@After

if you use @Before or @Around in you method declaration you must use JoinPoint class parameter, for example:
@Before("execution(* *..*(..))")
public void before(JoinPoint jp) throws Throwable

aspecj has many different joinpoints, full list you can see in correspondent document page

that is all what we need for run this simple test.

If you want use annotation in you AOP application, first at all you need create correcponding interfaces:

package ru.aspect;

import org.jboss.aop.advice.Scope;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AspectTest {
}

You can setup different type of Target:
ElementType.METHOD - annotation for method
ElementType.CONSTRUCTOR - annotation for constructor
ElementType.PACKAGE - annotation for package
ElementType.PARAMETER - annotation for parameter in method
ElementType.FIELD - annotation for field in class

for policy RetentionPolicy.RUNTIME read corresponding documentation

Remember annotation not work for some static (need more data)

And for end some perfomance testing.
i'm use 3 situation:
1 run method without any aspect
2 run method with annotated aspect
3 run method with aspect (without annotation)

class for test:

package ru;
import ru.aspect.AspectTest;

public class TestAspect {
public void doItWithout(int i) {
double s = Math.acos(i);
}

@AspectTest
public void doItAnnotated(int i) {
double s = Math.acos(i);
}

public void doItAspect(int i) {
double s = Math.acos(i);
}
}

here test invoke

package ru;
import java.util.Date;
public class Test {
public Test() {
}
public static void main(String arg[]) {
//perfomance testing
//invoke method without aspect
long t1 = new Date().getTime();
for (int i = 0; i < 100000; i++) {
new TestAspect().doItWithout(i);
}
System.out.println("Invoke without aspect:"+(new Date().getTime()-t1));
//invoke method with annotated aspect
t1 = new Date().getTime();
for (int i = 0; i < 100000; i++) {
new TestAspect().doItAnnotated(i);
}
System.out.println("Invoke annotated aspect method:"+(new Date().getTime()-t1));
//invoke method with aspect but not annotated
t1 = new Date().getTime();
for (int i = 0; i < 100000; i++) {
new TestAspect().doItAspect(i);
}
System.out.println("Invoke aspect method:"+(new Date().getTime()-t1));
}
}

performance result:
Invoke without aspect:31
Invoke annotated aspect method:94
Invoke aspect method:46


If you has any question just mail me: mailtosmyk@gmail.com

вторник, 29 июля 2008 г.

JBoss richfaces and true pagination (paging) [ENG]

Offcouse richfaces has own implimintation of paging component - rich:datascroller.
But it's manipulate only already fetching data, and every click send request to bean where probably you fetch data from database.
It is NOT true paging mechanism.
Let's me introduce my own (may be not clear, but fully working) paging.
What we must has, lets see: richfaces, jboss seam and offcouce facelets.
Start with facelets custom tag.
we has some facelets tag configuration file, for example: icust.taglib.xml
and must describe it in web.xml

<context-param>
<param-name>facelets.LIBRARIES</param-name>
<param-value>
/WEB-INF/icust.taglib.xml
</param-value>
</context-param>

in this configuration file, simply specify file name wich contains our tag.
For more information about facelets read facelets documentation

<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "facelet-taglib_1_0.dtd">
<facelet-taglib>
<namespace>http://www.icust.ru/jsf</namespace>
<tag>
<tag-name>paging</tag-name>
<source>paging.xhtml</source>
</tag>
</facelet-taglib>

ok! we finish configure our tag =) lets see the tag code

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:rich="http://richfaces.org/rich"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:f="http://java.sun.com/jsf/core">

<c:set var="maxpage" value="9"/>
<c:set var="interval" value="4"/>

<c:if test="${!empty errorlabel}">
<div>
<h:outputText value="#{errorlabel}" escape="false" styleClass="error"/>
</div>
</c:if>
<c:if test="${pagecount ge 2}">
<c:if test="#{! empty page}">
<c:set var="stop" value="${page + interval}" />
<c:if test="${stop le pagecount}">
<c:set var="stop" value="${page + interval}" />
<c:set var="start" value="${page - interval}" />
</c:if>
<c:if test="${stop ge pagecount}">
<c:set var="stop" value="${pagecount}" />
<c:set var="start" value="${pagecount - (2 *interval)}" />
</c:if>
<c:if test="${start le 2}">
<c:set var="start" value="1" />
<c:set var="stop" value="${maxpage}" />
</c:if>
</c:if>
<c:if test="#{page eq 0}">
<c:set var="start" value="1" />
<c:set var="stop" value="${maxpage}" />
<c:set var="page" value="1" />
</c:if>
<c:if test="#{stop ge pagecount}">
<c:set var="stop" value="${pagecount}" />
</c:if>
<div style="width: 100%; height: 27px; margin-top: 9px;">
<div style="float: left;">
<h:outputText value="#{label}"/>
</div>
<div style="float: right;">
<ul class="paging">
<c:if test="#{! (start eq 1)}">
<li>
<h:outputLink value="${url}&pageNo=" styleClass="pagingf">
<h:outputText value="←" escape="false"/>
</h:outputLink>
</li>
</c:if>
<c:forEach var="i" begin="${start}" end="${stop}">
<li>
<c:if test="#{page ne i}">
<h:outputLink value="${url}&pageNo=${i}">
<h:outputText value="${i}"/>
</h:outputLink>
</c:if>
<c:if test="#{page eq i}">
<h:outputLink value="#" styleClass="current">
<h:outputText value="${i}"/>
</h:outputLink>
</c:if>
</li>
</c:forEach>
<c:if test="#{(stop + 1) le pagecount}">
<li>
<h:outputLink value="${url}&pageNo=${pagecount}" styleClass="pagingl">
<h:outputText value="→" escape="false"/>
</h:outputLink>
</li>
</c:if>
</ul>
</div>
</div>
</c:if>
</ui:composition>

us you can see in code we has some additional parameters:
pagecount - total count pages
page - current page number
url - url for paging generate
errorlabel - label for nothing found
and
label - for found (possible found items) message
if page you place tag, for example:
<icust:paging pagecount="#{VideoList.paging.count}" page="#{VideoList.paging.current}" url="#{VideoList.paging.url}" errorlabel="#{VideoList.paging.errorlabel}" label="#{VideoList.paging.label}"/>

you need create simple Paging.class and initialize it in you bean.

public class Paging {
private int count;
private int current;
private String url;
private String label;
private String errorlabel;

public Paging() {
}

public Paging(int count, int current) {
this.count = count;
this.current = current;
}

public int getCurrent() {
return current;
}

public void setCurrent(int current) {
this.current = current;
}

public int getCount() {
return count;
}

public void setCount(int count) {
this.count = count;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getLabel() {
return label;
}

public void setLabel(String label) {
this.label = label;
}

/**
* view error label only one
*
* @return
*/
public String getErrorlabel() {
String error = errorlabel;
return error;
}

public void setErrorlabel(String errorlabel) {
this.errorlabel = errorlabel;
}
}

request parameter for page number is pageNo
in bean you can get it by seam annotation:

@RequestParameter
private Integer pageNo;

in page you must create Paging instance BEFORE get value for table.

if you can simplify my solution you are welcome.

Working example you can see here:
http://dev.maryno.net/video/list.html?data=

some css styles for good look:

.paging {
font-size: 75%;
font-family: Arial, sans-serif;
display: inline;
list-style: none;
text-decoration: none;
}

.paging li {
display: inline;
}

.paging a {
padding: 6px 10px 6px 10px;
background: #fff;
text-align: center;
border: 1px solid #beb1b1;
text-decoration: none;
color: #777 !important;
margin-right: 2px;
margin-left: 2px;
height: 25px;
}

.paging a:HOVER {
text-decoration: none;
background: #e6e6e6;
}

.paging a.current {
border: 1px solid #beb1b1;
font-weight: bold;
background: #969696;
color: #fff !important;
}

.paging a.pagingf {
padding: 6px 8px 6px 8px;
margin-right: 8px;
}

.paging a.pagingl {
padding: 6px 8px 6px 8px;
margin-left: 8px;
}

четверг, 17 июля 2008 г.

Список станций московского метро через запятую [RUS]

Вот возникла необходимость добавить в базу все станции метро, а списка под рукой нет. Что бы было удобнее список представлен через запятую (легко проходится любым парсером) из которого легко можно сделать SQL запросы.

Итак вот он:
Авиамоторная,Автозаводская,Академическая,Александровский Сад,Алексеевская,Алтуфьево,Аннино,Арбатская,Аэропорт,Бабушкинская,Багратионовская,Баррикадная,Бауманская,Беговая,Белорусская,Беляево,Бибирево,Библиотека им.Ленина,Битцевский Парк,Борисово,Боровицкая,Боровское шоссе,Ботанический Сад,Братеево,Братиславская,Бульвар адмирала Ушакова,Бульвар Дмитрия Донского,Бунинская аллея,Варшавская,ВДНХ,Владыкино,Водный Стадион,Войковская,Волгоградский Проспект,Волжская,Волоколамская,Воробьевы Горы,Востриково,ВЦ,Выхино,Дегунино,Деловой центр,Динамо,Дмитровская,Добрынинская,Домодедовская,Достоевская,Дубровка,Жулебино,Зябликово,Измайловская,Калужская,Кантемировская,Каховская,Каширская,Киевская,Китай-Город,Кожуховская,Коломенская,Комсомольская,Коньково,Красногвардейская,Краснопресненская,Красносельская,Красные Ворота,Крестьянская Застава,Кропоткинская,Крылатское,Кузнецкий Мост,Кузьминки,Кунцевская,Курская,Кутузовская,Ленинский Проспект,Лихоборы,Лубянка,Люблино,Маркситская,Марьина роща,Марьино,Маяковская,Медведково,Международная,Менделеевская,Минская,Митино,Молодежная,Нагатинская,Нагорная,Нахимовский Проспект,Никулинская,Новогиреево,Новокосино,Новокузнецкая,Новокурьяново,Новопеределкино,Новослободская,Новые Черемушки,Октябрьская,Октябрьское Поле,Олимпийская деревня,Орехово,Останкино,Отрадное,Охотный Ряд,Павелецкая,Парк Культуры,Парк Победы,Партизанская,Первомайская,Перово,Петровско-Разумовская,Печатники,Пионерская,Планерная,Площадь Ильича,Площадь Революции,Площадь Суворова,Полежаевская,Полянка,Пражская,Преображенская Площадь,Пролетарская,Пронская,Проспект Вернадского,Проспект Мира,Профсоюзная,Пушкинская,Речной Вокзал,Рижская,Римская,Рязанский Проспект,Савеловская,Свиблово,Севастопольская,Селигерская,Семеновская,Серпуховская,Славянский бульвар,Смоленская,Сокол,Сокольники,Солнцево,Спортивная,Сретенский бульвар,Строгино,Студенческая,Сухаревская,Сходненская,Таганская,Тверская,Театральная,Текстильщики,Телецентр,Теплый Стан,Терешково,Тимирязевская,Третьяковская,Трубная,Тульская,Тургеневская,Тушинская,Улица 1905 года,Улица академика Королева,Улица Академика Янгеля,Улица Горчакова,Улица Милашенкова,Улица Остафьевская,Улица Подбельского,Улица Сергея Эйзенштейна,Улица Скобелевская,Улица Старокочаловская,Улица Старопотаповская,Университет,Филевский Парк,Фили,Фрунзенская,Царицыно,Цветной бульвар,Челобитьево,Черкизовская,Чертановская,Чеховская,Чистые Пруды,Чкаловская,Шаболовская,Шипиловская,Шоссе Энтузиастов,Щелковская,Щукинская,Электрозаводская,Юбилейная,Юго-Западная,Южная,Ясенево

четверг, 5 июня 2008 г.

JBoss Seam and logout [ENG]

If you wont do some action AFTER OR BEFORE logout in seam what we must do?
In seam documentation you only see than method Identity.logout
But in not complex solution. Spending some time i found simple and pretty solution - just invoke you own logout method and in this method do Identity.instance().login();
for example:

public String logout() {
// do something before logout
String result = Identity.instance().logout();
// do something after logout
return result;
}

also you need extend logout method in Identity for postAuthenticate invoke


public void logout() {
super.logout();
super.postAuthenticate();
}

it prevent you from resend cookies back

one more thing. This is bug or not but if you use remember-me in seam logout will not work properly (JBoss Seam 2.0)

четверг, 15 мая 2008 г.

deploy red5.war with Tomcat (or any java web server) [ENG]

1. download Red5War_0.7.0.zip from osflash.org/red5/070final.
2. Install Tomcat server
3. Unzip Red5War_0.7.0.zip to webapps in Tomcat
You must get 3 war files (ROOT.war,admin.war,echo.war)
4. Run tomcat (it must be configured for automatically deploy war files)
That's all?
Hmm.. open browser with url http://localhost:8080/demos/ (8080 port for tomcat)
you will see some demo applications. Try any. Not luck?
Strange, in console all see good.
Magic in logging! see file logback.xml and remove all loggers except root and change path for log file.
restart tomcat and ... all working! no magic, just bad documentation and bad testing.

среда, 14 мая 2008 г.

Richfaces Tree Drag & Drop (DND) problem

it's registered bug in richfaces tree.
but we wont it's now.
so simples way it rerender all page =) (rerender highes element not fix problem)

<h:form>
<rich:tree switchType="client"
style="width:300px"
value="#{ItemTable.treeNode}"
dropValue="#{treecategory}"
dragValue="#{treecategory}"
dropListener="#{ItemTable.dropListener}"
var="treecategory"
id="categorytree"
dragType="file"
acceptedTypes="file"
ondrop="location.reload(true);">
<rich:treeNode icon="/images/tree/singer.png"
ondblclick="selectCategory('form3','#{treecategory.id}','#{treecategory.name}','true')">
<h:outputLabel value="#{treecategory.name}"/>
</rich:treeNode>
</rich:tree>
</h:form>

четверг, 8 мая 2008 г.

Simple Java File Server (Servlet) [ENG]

features:
1. callback
2. tranfer rate control
3. max user statement
4. max thread statement.
5. Recursive directory scan
6. Multi folder support
7. Error handling
8. Logging
9. support partial download
10. Free source

And all this functionality in 2 classes, 1 interface and 1 property file
Main class is FileServer

package ru.freecode.servlet;

import org.apache.commons.beanutils.ConstructorUtils;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
import java.text.MessageFormat;
import java.util.*;

/**
* Date: 07.05.2008
* Time: 18:47:52
*/
public class FileServer extends HttpServlet {
transient private Logger log = Logger.getLogger(FileServer.class.getName());

private final static int LOG_ERROR = 0;
private final static int LOG_WARN = 1;
private final static int LOG_DEBUG = 2;
private final static int LOG_INFO = 3;


private static ArrayList users = new ArrayList();

/**
* shared context for files
*/
private static HashMap files;

public FileServer() {

}

protected void doHead(HttpServletRequest request, HttpServletResponse response) throws IOException {
doGet(request, response);
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
try {
// reserve for future request.getLocale(); =)
String requestPath = URLDecoder.decode(request.getPathInfo(), "UTF-8");
String remouteAddress = request.getRemoteAddr();
String method = request.getMethod();
String sessionId = request.getSession().getId();

Callback callback = null;
log(LOG_INFO, "{2} request: {0} from : {1} sessionId: {3} params: {4}", requestPath, remouteAddress, method, sessionId, request.getParameterMap());


Properties appProp = loadProperties();
log(LOG_DEBUG, "File Server props: {0}", appProp);

if (appProp.containsKey("callback")) {
try {

callback = (Callback) ConstructorUtils.invokeConstructor(Class.forName(appProp.getProperty("callback")), null);

callback.setProperties(appProp);

} catch (Exception e) {
log(LOG_ERROR, "Callback class not created: {0}", appProp.getProperty("callback"), e);
throw new RuntimeException(e);
}
}

String filename = requestPath.substring(requestPath.lastIndexOf('/') + 1);

FileServerUser user = null;

try {
File file = getFile(appProp, filename);
long start = 0;
long end = 0;
Enumeration enu = request.getHeaderNames();
while (enu.hasMoreElements()) {
String obj = (String) enu.nextElement();
if (obj.equalsIgnoreCase("range")) {
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
String rangeData = request.getHeader(obj);
rangeData = rangeData.substring(rangeData.indexOf('=') + 1).trim();
String positions[] = rangeData.split("-");
try {
start = Long.parseLong(positions[0].trim());
if (positions.length > 1 && positions[1].length() > 0) {
end = Long.parseLong(positions[1].trim());
}
} catch (Exception e) {
log(LOG_ERROR, "Range Error. range: {0} ",request.getHeader(obj), e);
}
log(LOG_DEBUG, "Partial request. start pos: {0}, end pos {1}", start, end);
}
}

user = new FileServerUser();
user.setSessionId(sessionId);
user.setFile(file.getName());
if (callback!=null) {
callback.requestIncoming(user,remouteAddress,sessionId,requestPath);
}
attachUser(user, appProp);

if (sendContent(file, response, start, end, appProp)&&callback!=null) {
callback.downloadDone(user,remouteAddress,sessionId,requestPath);
}
//todo invoke callback
} catch (FileNotFoundException e) {
log(LOG_ERROR, "File not found: {0} ", filename, e);
if (callback!=null ) {
callback.fileNotFound(user,appProp.getProperty("files.dir"),filename);
}
} catch (InterruptedException e) {
log(LOG_ERROR, "Thread Error", e);
if (callback!=null ) {
callback.errorHandle(user,e);
}
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,e.getMessage());
} catch (IOException e) {
log(LOG_ERROR, "IOException", e);
if (callback!=null ) {
callback.errorHandle(user,e);
}
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,e.getMessage());
} catch (MaxThreadException e) {
log(LOG_ERROR, "MaxThreadException", e);
if (callback!=null ) {
callback.errorHandle(user,e);
}
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,"you rich max thread count. Maximum value: "+appProp.getProperty("max.threads"));
} catch (CanDownloadException e) {
log(LOG_ERROR, "CanDownloadException", e);
if (callback!=null ) {
callback.errorHandle(user,e);
}
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,"Download not accept: "+e.getMessage());
} catch (MaxClientException e) {
log(LOG_ERROR, "MaxClientException", e);
if (callback!=null ) {
callback.errorHandle(user,e);
}
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,"max client count riched");
} finally {
deattachUser(user);
}
} catch (Exception e) {
log(LOG_ERROR, "General Exception", e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,"Please contact to site administrator");
}
}

private void deattachUser(FileServerUser user) {
if (user == null) {
return;
}
FileServerUser forRemove = null;
for (int i = 0; i < users.size(); i++) {
FileServerUser sUser = (FileServerUser) users.get(i);
//if user has id search by user id
if (user.getId() != null) {
if (sUser.getId().equals(user.getId())) {
sUser.decrementThreads();
if (sUser.getThreadsCount() == 0) {
users.remove(sUser);
}
return;
}
} else {
// else search by sessionid
if (sUser.getSessionId().equals(user.getSessionId())) {
sUser.decrementThreads();
if (sUser.getThreadsCount() == 0) {
users.remove(sUser);
}
return;
}

}
}
}

private void attachUser(FileServerUser user, Properties appProps) throws MaxThreadException, MaxClientException {
int maxThreads = Integer.parseInt(appProps.getProperty("max.threads", "0"));
int maxUsers = Integer.parseInt(appProps.getProperty("max.users", "0"));
for (int i = 0; i < users.size(); i++) {
FileServerUser sUser = (FileServerUser) users.get(i);
//if user has id search by user id
if (user.getId() != null) {
if (sUser.getId().equals(user.getId())) {
if (sUser.getThreadsCount() == maxThreads && maxThreads > 0) {
throw new MaxThreadException();
} else {
sUser.incrementThreads();
}
return;
}
} else {
// else search by sessionid
if (sUser.getSessionId().equals(user.getSessionId())) {
if (sUser.getThreadsCount() == maxThreads && maxThreads > 0) {
throw new MaxThreadException();
} else {
sUser.incrementThreads();
}
}
return;
}
}
if (users.size() == maxUsers && maxUsers > 0) {
throw new MaxClientException();
}
user.setThreadsCount(1);
users.add(user);
}

private boolean sendContent(File file, HttpServletResponse response, long start, long end, Properties appProps) throws IOException, InterruptedException {
int buffersize = 1024 * 16;
int sleep = 0;
boolean result = false;
if (appProps.containsKey("max.speed")) {
int speed = Integer.parseInt(appProps.getProperty("max.speed"));
sleep = (int) (((float) buffersize / speed) * 1000);
}

response.setContentType("application/octet-stream");
response.addHeader("Accept-Ranges", "bytes");

response.addHeader("Content-Disposition", "attachment; filename=" + file.getName());
if (end > 0) {
response.addHeader("Content-Range", "bytes " + start + "-" + end + "/" + (end + 1));
}

long size = file.length();
if (start > 0 && end > 0) {
size = end - start;
}
if (start > 0 && end == 0) {
size = file.length() - start;
}

response.addHeader("Content-Length", "" + size);

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));

if (start > 0) {
bis.skip(start);
}

BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());

byte buffer[] = new byte[buffersize];

int read = bis.read(buffer);
long dataSend = 0;
while (read > 0) {

if (end > 0 && start + dataSend + read > end) {
bos.write(buffer, 0, (int) (end - start - dataSend));
bos.flush();
break;
} else {
bos.write(buffer, 0, read);
bos.flush();
}
dataSend = dataSend + read;
read = bis.read(buffer);
if (read<=0) {
result = true;
} else {
// setup speed
if (sleep > 0) {
Thread.sleep(sleep);
}
}
}
bos.close();
bis.close();
return result;
}


private Properties loadProperties() throws IOException {
Properties props = new Properties();
props.load(this.getClass().getResourceAsStream("/fileserver.properties"));
return props;
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) {

}

protected void doPut(HttpServletRequest request, HttpServletResponse response) {

}

protected void doDelete(HttpServletRequest request, HttpServletResponse response) {

}

private void log(int logtype, String message, Object... obj) {
StringBuffer sb = new StringBuffer();
MessageFormat format = new MessageFormat(message);
sb.append(format.format(message, obj));
switch (logtype) {
case LOG_DEBUG:
log.debug(sb);
break;
case LOG_ERROR:
log.debug(sb, (Throwable) obj[obj.length-1]);
break;
case LOG_INFO:
log.info(sb);
break;
case LOG_WARN:
log.warn(sb);
break;
}
}

public File getFile(Properties appProp, String filename) throws FileNotFoundException {
if (files == null || !files.containsKey(filename)) {
if (files == null) {
files = new HashMap();
} else {
files.clear();
}
String paths = appProp.getProperty("files.dir");
String[] path = paths.split(";");
for (int i = 0; i < path.length; i++) {
String ph = path[i].split(",")[0];
Collection coll = FileUtils.listFiles(new File(ph), null, true);
Iterator fileIterator = coll.iterator();
while (fileIterator.hasNext()) {
File o = (File) fileIterator.next();
files.put(o.getName(), o);
}
}
}
if (!files.containsKey(filename)) {
throw new FileNotFoundException("FILE NOT FOUND: " + filename);
}
return (File) files.get(filename);
}
}


Callback API defined in interface:

package ru.freecode.servlet;

import java.util.Properties;

/**
* Date: 07.05.2008
* Time: 18:59:43
*/
public interface Callback {
/**
* set properties for callback, it can be db props, url and etc
* @param props
*/
public void setProperties(Properties props);
/**
* this function invoked after request incoming
* @param user - user
* @param remouteAddress - user remoute address
* @param sessionId - http sessionId
* @param requestPath - full path in request except server name
* @throws CanDownloadException thrown if user can't download file
*/
public void requestIncoming(FileServerUser user, String remouteAddress, String sessionId, String requestPath) throws CanDownloadException;

/**
* function invoked after download complete
* @param user
* @param remouteAddress
* @param sessionId
* @param requestPath
*/
public void downloadDone(FileServerUser user, String remouteAddress, String sessionId, String requestPath);

/**
* function invoked if file not found
* @param user
* @param property
* @param filename
*/
public void fileNotFound(FileServerUser user, String property, String filename);

/**
* handle error
* @param user
* @param e
*/
public void errorHandle(FileServerUser user, Exception e);
}


also we need User for restrict:

package ru.freecode.servlet;

import java.util.Date;

public class FileServerUser {
private String id;
private int threadsCount;
private String sessionId;
private String file;
private Date access = new Date();

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public int getThreadsCount() {
return threadsCount;
}

public void setThreadsCount(int threadsCount) {
this.threadsCount = threadsCount;
}

public String getSessionId() {
return sessionId;
}

public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}

public String getFile() {
return file;
}

public void setFile(String file) {
this.file = file;
}

public String toString() {
return id + " " + sessionId + " count: " + threadsCount + " " + file;
}

public void incrementThreads() {
threadsCount++;
}

public void decrementThreads() {
threadsCount--;
}

public Date getAccess() {
return access;
}
}


and some useful Exceptions:

package ru.freecode.servlet;

/**
* Date: 08.05.2008
* Time: 2:16:25
*/
public class CanDownloadException extends Exception{
public CanDownloadException(String s) {
super(s);
}
}

package ru.freecode.servlet;

/**
* Date: 08.05.2008
* Time: 1:23:27
*/
public class MaxClientException extends Exception {
}

package ru.freecode.servlet;

/**
* Date: 08.05.2008
* Time: 1:23:48
*/
public class MaxThreadException extends Exception{
}



thats it.
Only what you needed for start its web configure and file server configure.
in web xml:

<servlet>>
<servlet-name>File Servler Servlet</servlet-name>
<servlet-class>ru.freecode.servlet.FileServer</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>File Servler Servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>


file server properties

files.dir=/movies/m1;/movies/m2;/home/m3;/home/m4;
callback.server=http://dev.maryno.net/video/command
callback.user=someuser
callback.password=somepassword
callback=ru.freecode.video.VideoServerCallback
#threads count on client
max.threads=5
#speed in byte/s on 1 thread
max.speed=448576
#max.speed=2048576
# users not threads
max.users=20

parameter "files.dir" setup files directories. You can point on many directories separate they wich ";" character
parameters where name started callback it's properties for implementation callback class. This parameters depends on you logic.
"callback" write here you class what implement callback interface. If this parameter blank then server will be work as simple http file server.

"max.threads" parameter point on how many connections can do one user. If user id can't be set in callback or any where else users will be checking by session id.
parameter "max.speed" setup maximum download spead for one threads in byte per second.

and last "max.users" say file server how many users can be accepted in one time. So if you setup 5 threads and 5 users, you server will serve 25 threads maximum.

dependeces:
commons-beanutils-1.7.0.jar
commons-codec-1.3.jar
commons-collections-3.1.jar
commons-digester.jar
commons-io-1.3.2.jar
commons-lang-2.2.jar
commons-logging-1.1.jar
log4j-1.2.13.jar