четверг, 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

воскресенье, 4 мая 2008 г.

JBoss Seam and Custom Security with privileges[ENG]

coming soon...
for code and example you can see
JBoss Seam and Custom Security with Privilegues[ENG]

JBoss Seam and Custom Security with privileges[RUS]

Для создания привилегий нам потребуются привилегии и собственно класс который их обрабатывает. И так по порядку.

import icust.entity.Privilegue;
import icust.entity.User;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Startup;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.security.Identity;

import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;

@Name("org.jboss.seam.security.identity")
@Scope(value = ScopeType.SESSION)
@Install(precedence = Install.DEPLOYMENT)
@BypassInterceptors
@Startup
public class ICustIdentity extends Identity implements Serializable {
private User currentUser;
private Map permissions;


public void setPermissions(Map permissions) {
this.permissions = permissions;
}

public boolean hasPermission(String name, String action, Object...arg) {
if (permissions==null||permissions.size()==0) {
return false;
}

Iterator iter = permissions.keySet().iterator();
while (iter.hasNext()) {
Object key = iter.next();
Privilegue priv = (Privilegue) permissions.get(key);
if (priv.getName().equalsIgnoreCase(name)&&priv.getAction().equalsIgnoreCase(action)) {
return true;
}
}
return false;
}
public User getCurrentUser() {
return currentUser;
}
public void setCurrentUser(User currentUser) {
this.currentUser = currentUser;
}
}

как видите ничего сложного, на что неоюходимо обратить внимание:
1. Класс должен наследоваться от Identity
2. Аннотация. Разберемся подробнее:
@Name("org.jboss.seam.security.identity")
имя бина в конткетсе seam, оно должно быть таким и никаким другим.
@Scope(value = ScopeType.SESSION)
тут я думаю объяснять не стоит. Безопастность хранится в сессии ну и может еще в куках )
@Install(precedence = Install.DEPLOYMENT)
важный параметр, в свое время потратил много времени что бы понять почему не работает, хотя должно. он отвечает насколько данный компонент стоит выше в иерархии одноименных копнонетов. Т.е. компоненты могут иметь одинаковое имя, но вот который из них будет загружен указывает именно этот параметр. Более подробно ищите в документации к seam.
@BypassInterceptors
Точно не скажу, но могу предположить - игнорируем все перехватчики
@Startup
ну и напоследок, данный компонет должен загружатся в самом начале.
3 Метод
основной метод который делает всю работу это
public boolean hasPermission(String name, String action, Object...arg)
соответственно стандартный класс поставляемый в seam в данном методе реализует просто заглушку. Мы же делаем там некую работу по проверке привилегий.
Далее нам понадобится прописать в components.xml

Выражение "#{Login.login}" означает что проверку авторизации будет выболнять компонент Login в методе login. Параметр authenticate-every-request важен, если вы не хотите что и в всплывающих окнах проводилась проверка авторизации.

<event type="org.jboss.seam.notLoggedIn">
<action expression="#{redirect.captureCurrentView}">
</event>

<event type="org.jboss.seam.postAuthenticate">
<action expression="#{redirect.returnToCapturedView}">
</event>

Эти два обработчика событий описывают поведение seam в случае успешной и не успешной авторизации. Если авторизация прошла успешно, пользователь будет автоматически переадресован на страницу к которой он пытался получить доступ.

Далее pages.xml

<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.0.xsd"
login-view-id="/login.xhtml">
<page id="/user/*" required="true">
</page>
</pages>

тут и объяснять то особо нечего. Хочу отметить только что это не единственно возможный варинат закрытия доступа к страницам. Так же можно воспользоваться тэгом restrict или же попытатся реализовать свой PhaseListener =)

Теперь рассмотрим метод авторизации:

public boolean login() {
try {
Criteria crit = hibernateSession.createCriteria(User.class);
crit.add(Expression.eq("login",Identity.instance().getUsername()).ignoreCase());
crit.add(Expression.eq("password",Identity.instance().getPassword()));
crit.add(Expression.eq("status",Boolean.TRUE));
User user = (User) crit.uniqueResult();
if (user!=null) {
Identity.instance().setUsername(user.getName());
((ICustIdentity)Identity.instance()).setCurrentUser(user);
user.setLastaccess(new Date());
hibernateSession.update(user);
if (user.getRole() != null)
{
Identity.instance().addRole(user.getRole().getName());
((ICustIdentity)Identity.instance()).setPermissions(user.getRole().getPrivs());
}
if (redirect.getViewId()==null) {
redirect.setViewId("/user/index.xhtml");
}

return true;
} else {
FacesMessages.instance().add("Invalid username/password");
return false;
}
} catch (Exception e) {
log.error(e.getMessage(), e);
FacesMessages.instance().add(new FacesMessage("application.error"));
}
return false;
}

Как видите он возвращает true или false в зависимости от того авторизовался пользователь или нет. Я не буду подробно останавливатся на данном методе, расскажу только о двух моментах:
1. Установка роли и привилегий
Identity.instance().addRole(user.getRole().getName());
((ICustIdentity)Identity.instance()).setPermissions(user.getRole().getPrivs());
2. Переадресация
if (redirect.getViewId()==null) {
redirect.setViewId("/user/index.xhtml");
}
Seam имеет встроенный компонет redirect(см документацию). Так в чем тут особенность? Дело в том что если вы попытались обратится к странице, доступ к которой закрыт неавторизованному пользователю то после авторизации вас автоматически перебросит на эту страницу(redirect.getViewId() будет содержать адресс той страницы.). Но что делать если вы сразу обратились к странице авторизации, то Seam 
не будет знать куда вас перебросить.
Ну и наконец то ради чего вы все это читали :-)
rendered="#{s:hasPermission('role','add','')||s:hasPermission('role','edit','')}"
или же прямо на методе:
@Restrict("#{s:hasPermission('account','modify',selectedAccount)}")
public void modify() {}

более подробно можно посмотреть перейдя по ссылке:
http://docs.jboss.com/seam/1.1.5.GA/reference/en/html/security.html

this is my first blog post[RUS]

Это мое первое сообщение в блоге! Ура товарищи!

В своем блоге я постараюсь писать посты на двух языках - Русском и Английском.
Так как Английския я знаю хуже русского, то за перевод особо не ручаюсь.

My first blog post[ENG]

this is my first blog post. Vivat!

In my blog's posts i try write any messages it two language - Russian and English