25/05/2012

Book: Good Old "Peopleware"

One more book worth reading - "Peopleware: Productive Projects and Teams" by Tom DeMarco and Timothy Lister. It's essentially a Holy Bible for every manager!

Book: "Continuous Delivery..." by Jez Humble and David Farley

This book may help to make a product releasing and delivery to be more frequent and less painful.

05/10/2011

Builder Pattern Usage: Rule of Thumb

Builder Pattern is a creational design pattern. It is good in:

  1. Creating complex immutable objects
  2. Preventing objects being created to be used before initialization is complete.

Complex Immutable Objects

It's important that object being created should be Complex enough and Immutable. Overwise it may be an overkill to use a Builder for simple cases.

  • If object is not complex enough — you may use more simple ways of creating new object, e.g. use static factory method.
  • If object is not immutable — use setter methods

What kind of object are complex? Much depends on context, of course. But if object has less than than 2-3 constructor parameters is unlikely to be complex enough to use the Builder. I.e, if object can be created with factory method with up to 3 parameters, than object is simple and using Builder pattern is overkill.

One more pattern should be recalled here is Value Object or Data Transfer Object (DTO). Value objects or DTOs may be passed as a constructor parameters to simplify object creation.

Preventing early object access

Builder may be used to create an object which should not be used unless fully initialized. Initializing object using setter methods does not prevent client from calling other object business methods. To handle this incomplete state correctly you normally should perform object state check in the beginning of every business method and throw IllegalStateException... or just prevent object to be created in inconsistent state. One of the way to to this it is to use Builder. But anyway, you should check if all the parameters are initialized inside builder.build() method or inside object constructor:

import org.apache.commons.lang3.Validate;
public class PingResponseBuilder {
    private String serverName;
    private long timestamp;

    public void setServerName(String name) {
        this.serverName = name;
    }

    public void setTimestamp(long millis) {
        this.timestamp = millis;
    }

    private void validate() {
        Validate.notBlank(serverName, "The serverName must not be blank");
        Validate.isTrue(timestamp>0, "The timestamp must be greater than zero: %s", timestamp);
    }

    public PingResponse build() {
        validate();
        return new PingResponse(serverName, timestamp);        
    }
} 

Please keep in mind, that although memory is cheap and processors are fast, creating the new Builder instance for every created object instance is not very efficient. You may re-use single builder object (in a thread-safe manner!!!) for creating multiple object instances by setting differing properties, e.g.:

PingResponseBuilder builder = new PingResponseBuilder();
builder.setServerName("A Test Server");
...
PingResponse firstResponse = builder.setTimestamp(System.currentTimeMillis()).build();
...
PingResponse nextResponse = builder.setTimestamp(System.currentTimeMillis()).build();

25/02/2011

Connecting to Facebook Chat

Some time ago, Facebook has provided a public XMPP service. It makes possible to connect to Facebook chat server via any XMPP client (e.g. Pidgin or Adium) and send messages to your Facebook contacts.


Adium already has a built-in support for Facebook Chat. You may add Facebook Chat to Pidgin as generic XMPP service.

To configure your jabber client use the following recommended settings:

Protocol: XMPP or Jabber
Username:
Domain: chat.facebook.com
Jabber ID: @chat.facebook.com
Password:

Enter the following server info, as requested:

Port: 5222
Server: chat.facebook.com
Use SSL/TLS: no
Allow Plaintext Authentication: no

When you connect to Facebook account you'll see your Facebook friend groups in roster.
Friend groups (friend lists) can be edited only from Facebook site, not from the XMPP client.
Only your confirmed friends can contact you through Facebook Chat.

Currently, Facebook Chat does not support SSL and message encryption, but supports secured authentication using MD5.

For information about integraitng with Facebook Chat see here.

17/11/2010

Installing GWT Development Plugin in FF3.6 on OpenSuse 11.2x64

Compiling the plugin works for me on Arch Linux x86_64 or OpenSuse 11.2 x86_64 & FF36:

"
mkdir gwt-source
cd gwt-source
svn checkout http://google-web-toolkit.googlecode.com/svn/trunk/ trunk
svn checkout http://google-web-toolkit.googlecode.com/svn/plugin-sdks/ plugin-sdks
cd trunk/plugins/xpcom
export BROWSER=ff36
export DEFAULT_FIREFOX_LIBS=/usr/lib/xulrunner-devel-1.9.2/sdk/lib/
make clean
make
"

This shoulds create "gwt-dev-plugin.xpi" in prebuild directory, open it with FF and
may the force be with you.


Update: This reciept also works under Ubuntu 10.10 32bit (you don't need to export DEFAULT_FIREFOX_LIBS)

See original comment:
Issue 4141 - google-web-toolkit - Various compatibility problems with OOPHM Firefox plugin on Linux distros - Project Hosting on Google Code

30/09/2010

How to Set JSON Response Data Locale for Facebook Graph API

To make Facebook server return JSON responses in specified locale, you need to add parameter locale to the query, e.g.: https://graph.facebook.com/me?access_token=xxx&locale=en_US
The result is:
{
...
"gender": "male",
"meeting_for": [
"Friendship"
],
"relationship_status": "Married",
...
}


If you want to get results in russian, then add locale=ru_RU to query:
https://graph.facebook.com/me?access_token=xxx&locale=ru_RU
and you'll get result in russian (UTF-encoded):
{
...
"gender": "\u043c\u0443\u0436\u0441\u043a\u043e\u0439",
"meeting_for": [
"\u0414\u0440\u0443\u0436\u0431\u0430"
],
"relationship_status": "\u0416\u0435\u043d\u0430\u0442/\u0437\u0430\u043c\u0443\u0436\u0435\u043c",
...
}

22/07/2010

GWT is about to add Native JSON function support in version 2.1

GWT 2.1 has reached Milestone 2.

One of the new features in version 2.1 is support for browser's native JSON function in JSONParser class. More...

30/06/2010

Upgrading to Spring 3: "Unable to locate Spring NamespaceHandler" in JAR file built by Maven

After upgrading to Spring 3 your may discover that your application packaged in JAR archive does not start any more throwing an exception like this:

"Unable to locate Spring NamespaceHandler for XML schema namespace[http://www.springframework.org/schema/tx]"

This happens when you include multiple Spring module dependencies in your pom.xml file and use Maven Shade or Assembly plugin to build a single JAR.
Since Spring can does not contain a single distribution jar (org.springframework:spring:jar), Spring namespace handlers, schema mappings and tooling information files are now present in multiple files with names:

  • META-INF/spring.handlers
  • META-INF/spring.schemas
  • META-INF/spring.tooling

In order to make it work in a single shaded JAR you need to merge contents of these files from different jars and place merged files into new JAR. More...

19/11/2009

Mac OS X Applications for Everyday. Keyboard Layout Switchers.


After two years of Mac user experience, I'd like list the applications I use everyday.

I am Russian, so I need a to write the texts in other encoding than ISO. Under windows, there is nice tool called Punto Switcher. For long time there was no such tool for mac, except RuSwitcher by Alexey Proskuryakov. RuSwitcher works only on Tiger and does not work under Leopard.

Hopefully, guys from Yandex has ported PuntoSwitcher to Mac OS X (Leopard & Snow Leopard). It is still a beta but I have no problems using it.

Some of the Punto Switcher's features are:
  • Switching keyboard layout automatically or with a hot key.
  • Fix incorrect layout for selected text.
  • Mac and Windows layout support.

30/01/2009

How to Lookup JBoss MBean Server from Spring (JMX)

If you having problems accessing "mbeanServer" Spring-managed bean under JBoss Application server Read more...

29/01/2009

How to Use Spring from EJB3

This is a short instruction how to inject a spring-managed bean into EJB3 component.

  1. Read SpringFratamework's reference here: http://static.springframework.org/spring/docs/2.5.x/reference/ejb.html#ejb-implementation-ejb3

  2. Place to ejb module's classpath a file beanRefContext.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
    <beans>

    <bean id="myBeanFactory" class="org.springframework.context.support.ClassPathXmlApplicationContext">
    <constructor-arg value="myApplicationContext.xml"/>
    </bean>

    </beans>

  3. Create application context file named myApplicationContext.xml and define You beans there. Place this file to the ejb module's classpath.

  4. Annotate your Stateless Session Bean:
    @Stateless
    @Interceptors(org.springframework.ejb.interceptor.SpringBeanAutowiringInterceptor.class)
    public class MyFacadeBean implements MyFacade {

    @Autowired
    private MySpringComponent component;
    ...
    public void foo() {
    component.foo();//invocation
    }
    }

  5. Deploy and test Your application.

27/01/2009

How To Export Spring Managed Bean To JNDI

Sometimes, it is necessary to export a spring managed bean to JNDI context. Here I want to show how do it.

In spring, there is a bean that provides a similar functionality for exporting to MBean server: MBeanExporter. Unfortunately, there is no standard JNDI bean exporter implementation in spring (current version is 2.5.6) - (Why?).
But it's easy to write it youself:
package com.example.spring.jndi.export;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jndi.JndiTemplate;

public class JndiExporter implements InitializingBean, DisposableBean {

private String jndiName;

private Object bean;

private final JndiTemplate jndiTemplate = new JndiTemplate();

public String getJndiName() {
return jndiName;
}

public void setJndiName(String jndiName) {

this.jndiName = jndiName;
}

public Object getBean() {
return bean;
}

public void setBean(Object bean) {
this.bean = bean;
}


public void afterPropertiesSet() throws Exception {
jndiTemplate.bind(jndiName, bean);
}

public void destroy() throws Exception {
if (bean != null && jndiName != null && bean == jndiTemplate.lookup(jndiName)) {
jndiTemplate.unbind(jndiName);
}
}
}

Add following fragment to spring configuration file:
<bean id="myBean" class="com.example.MyBean"/>

<bean class="com.example.spring.jndi.export.JndiExporter">
<property name="bean" ref="myBean" />
<property name="jndiName" value="MyJNDIName"/>
</bean>
Don't forget to make your bean serializable by implementing java.io.Serializable interface.
Now we can lookup exported bean by adding to the spring config fil:
<jee:jndi-lookup id="myJndiBean" jndi-name="MyJNDIName" proxy-interface="com.example.IMyBean" lookup-on-startup="false"/>

That's all, folks!

02/01/2009

Use BigDecimals for Financial Calculations: One More Example

Recently I was asked about the preferred data type in java application dealing with financial data. I suggest using of BigDecimal and here is the example why. Let's perform following arithmetic operations over double values, double values with strictfp mode enabled and over BigDecimals.

Expression is: "0.999/9 - 0.112"
Expected result is: "-0.001"

Java code:
import java.math.BigDecimal;

public class FloationPointTest {

public strictfp static void testStrictfp() {
System.out.println("strictfp: 0.999/9-0.112 = " + (0.999/9-0.112));
}

public static void main(String ... args) {
System.out.println("double: 0.999/9-0.112 = " + (0.999/9-0.112));
testStrictfp();
System.out.println("BigDecimal: 0.999/9-0.112 = " + (new BigDecimal("0.999").divide(new BigDecimal("9")).subtract(new BigDecimal("0.112"))));
}
}

Program output:
double: 0.999/9-0.112 = -0.0010000000000000009
strictfp: 0.999/9-0.112 = -0.0010000000000000009
BigDecimal: 0.999/9-0.112 = -0.001

We see that only operations with BigDecimal does not lead to the lost of precision. Performing operation over values of type double even in strictfp mode may loose accuracy. It is not acceptable for financial calculations in the days of Global financial crisis.

02/12/2008

Proxy Method Resolving Order

I noticed interesting behavior of method resolving order, which reflects interface order in java.lang.reflect.Proxy.newProxyInstance(...) call. It fact, it is described in Sun's java guide: Dynamic Proxy Classes: Methods Duplicated in Multiple Proxy Interfaces

Supose, there are two interfaces (A and B) with same signature and we have created a proxy that implements this interface in order {A,B}.

When we cast a proxy to class A and invoke it's method, the InvocationHandler says that we invoke a method from interface A (as expected).

But when we cast a proxy to class B and invoke it's method, the InvocationHandler says that we invoke a method from interface A again! Here I'd expect the InvocationHandler will say that we invoking a method of interface B and it's strange for me.

Order of appearing interfaces in proxy instance factory method does matter. Following program demonstrates it:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class ProxyMethod {

interface A {
void doSomething();
}

interface B {
void doSomething();
}

static class EchoHandler implements InvocationHandler {

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method = " + method);
return null;
}
}

public static void main(String[] args) {
Object proxy1 = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{B.class, A.class},// The difference
new EchoHandler());

Object proxy2 = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{A.class, B.class},// The difference
new EchoHandler());

System.out.println("\nproxy1 intfs = " + Arrays.asList(proxy1.getClass().getInterfaces()));
((A) proxy1).doSomething();
((B) proxy1).doSomething();

System.out.println("\nproxy2 intfs = " + Arrays.asList(proxy2.getClass().getInterfaces()));
((A) proxy2).doSomething();
((B) proxy2).doSomething();
}
}

Returns following result:

proxy1 intfs = [interface ProxyMethod$B, interface ProxyMethod$A]
method = public abstract void ProxyMethod$B.doSomething()
method = public abstract void ProxyMethod$B.doSomething()

proxy2 intfs = [interface ProxyMethod$A, interface ProxyMethod$B]
method = public abstract void ProxyMethod$A.doSomething()
method = public abstract void ProxyMethod$A.doSomething()

This behavior allows to build object wrapper as dynamic proxies easily. Interface of the object being wrapped should be passed first to the proxy factory method. So, if methods of the wrapped object's interface is passed to the invocation handler - just invoke them on a wrapped objects. Other methods should be handled differently by invocation handler.

26/03/2008

WebService Authentication under JBoss

In my business project there is a need to check declarative security within EJB3 Session beans exposed as WebServices. The project is deployed under JBoss AS 4.0.5.GA with JBossWS 1.2.0.SP1. After brief investigation, a quick solution was found.

HTTP BASIC Auhtentication with EJB3 Endpoints



Axis client provides HTTP BASIC authentication when invoking web services (see previous article). So, it should be some way to enable it on server side.

For EJB web service endpoints, JBossWS generates and deploys web application, so Session Beans are mapped to Servlets.

Let's look at the example of SLSB exposed as WebService (class HelloIntf, containing missing here @WebService annotations, is generated from WSDL and not listed here):
@javax.ejb.Local(HelloIntf.class)
@javax.ejb.Stateless(name = "HelloPort")
@javax.jws.WebService(endpointInterface = "HelloIntf")
@TransactionManagement
@DeclareRoles({"foo","bar"})
// jboss specific
@org.jboss.ws.annotation.WebContext(
    contextRoot = "/services",
    urlPattern = "/hello",
    authMethod = "BASIC")
public class HelloBean implements HelloIntf {

    @javax.annotation.Resource
    private SessionContext ctx;

    @javax.annotation.security.RolesAllowed({"foo","bar"})
    public String sayHello() {
        return "Hello, " + ctx.getCallerPrincipal();
    }

}


Defined bean will be deployed as http://localhost:8080/services/hello.
Actually, JBossWS generates web.xml by processing annotations. In web.xml following code will be added:
<security-constraint>
<web-resource-collection>
<web-resource-name>HelloPort</web-resource-name>
<url-pattern>/hello</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>EJBServiceEndpointServlet Realm</realm-name>
</login-config>

Unfortunately, the realn name is predefined (hardcoded in JBossWS code) and can not be changed (in JBossWS 1.2.0.SP1), so, we may configure default application-policy called "other" in login-config.xml to meet our needs or to add new application-policy element with name="EJBServiceEndpointServlet Realm" (but the second solution does not works by unknown reason).

22/03/2008

MacOS Applications

As a new Mac user, I was faced to choose and install applications to make my work (and entertainment) more pleasurable.

One of the most important reason when choosing applications to install is to prefer Free and Open Source than Commercial ones. Then comes size and performance. All

Localization


Is my native language is Russian, I need a to write the texts with other encoding than ISO. Here comes RuSwitcher by Alexey Proskuryakov. It is similar that PuntoSwitcher for Windows.

Web



Web Browser



I desided to stay with Safari as it's really fast web browser.
But there are a number of plugins which make work with Safari more comfortable.

SafariBlock


The SafariBlock plugin is absolutely necessary to block unwanted ads. - does not work in Safari 4 beta

Glimps

GlimpsGlimps - search plugin for Safari. - better then Inquisitor.

Keywurl

Other useful extension is Keywurl - A small, free plugin for Safari that adds keyword search to the browser’s address bar. (just like in FireFox).
search wiki from adress bar with Keywurl

Communications



Mail


Mail.appOn Windows and Ubuntu I prefer Mozilla Thunderbird. But on Mac, I found native Mail.app is useable enough. And it seems to be faster than Thunderbird.

Reading



Reading RSS




Initially, I have installed ... But when I tried Vienna, I stayed on it.

Reading CHM files



There are a number CHM file readers available. For now, I've stayed on Chmox. Significant disadvantage of this program it is not possible to view CHMs with windows-1251 encoding.


Entertainment



GimmeSomeTune - iTunes Plugin



Nice iTunes plugin that fetches lyrics and artwork for currently playing composition. It has last.fm. support. It may start automatically when you start iTunes and shutdowns when you exit it.


Development



SVN Client



SCPlugin - My choice is SVN client with Finder integration.

Games


I like RPG and Battle for Wesnoth is very nice, balanced and playable game. And it's absolutely Free and Open Source!

09/10/2007

«Трусливая» разработка

Я называю трусливой разработкой состояние проекта, когда некоторые (или все) члены команды избегают вносить изменения в исходный код или конфигурационные файлы.
Такое бывает на завершающих стадиях проекта, когда получена некая относительно работоспособная версия сиситемы. В этот момент аналитик или менеджер приходит к разработчикам и просит внести не очень большое изменение в функциональность. В ответ ему говорят, что "лучше мы ничего не будем трогать, чтобы не сломать". Услышать такие слова перед выпуском релиза - это нормально. Действительно, внесение изменений изменит скоуп (scope) релиза и отодвинет его дату, а все уже хотят поскорей его сдать. Но если такое происходит в начае релиза - это повод задуматься: а владеют ли разработчики кодом в полном объеме?

Разве разработчик может не владеть кодом, спросит начинающий менеджер проекта? Ответ: Да. И дело здесь не в профессионализме разработчиков. Причины могут быть различны, например:

  • Отсутствие понимания у разработчика общей картины поведения системы. Невозможность предсказать изменения и оценить "размер бедствия".
  • Опасения разрушить взаимодействия между слабосвязанными компонетами: невозможно сказать, на что повлияет сделанное изменение.
  • Новые люди в проекте. Они боятся лезть в чужой код, особенно, если его автор больше не работает над проектом.
  • Психологическая усталость: нет драйва затевать большую переделку.
  • Противоречия внутри команды: один разработчик отказывается работать с кодом другого разработчика.
Осторожность даже может быть признаком профессионализма и наличия негатовного опыта в прошлом. Вот почему необстреляный солдат так рвется в бой, а ветераны не лезут на рожон. Другими признаками "трусливого" процесса могут быть:
  • Саботирование изменений
  • Неопреледенные оценки времени со стороны разработчиков: разработчики стараются не предоставлять менеджеру информацию, о сроках разработки. На самоа деле, они не могут даже приблизительно определить эти сроки. Возможно, что какие-то сроки все же будут заявлены, но они будут либо взяты с большим запасом, либо будут не честными и не реальными.
Нужно как можно скорее менять ситуацию, переходя от боязни изменений к готовности вносить изменения. В противном случае теряется мотивация (драйв) команды, наступает застой, продукт перестает развиваться.

Итак, как я предлагаю исправлять ситуацию следующим образом:

  1. Автоматизировать процесс сборки, автоматического тестирования и разворачивания системы. Да, бывает, что система "собирается на коленке". Нужно как можно скорей добиться воспроизводимости получения работающей версии системы по исходному коду (см. книжку).
  2. Уменьшить скоуп релизов. Перейти на короткие релизы (не более 2-3 недель). Это уменьшит нервозность среди заказчиков, менеджмента да и среди самих разработчиков. Начнет что-то наконец получаться в запланированный срок.
  3. Начать вносить небольшие изменения, используя техники рефакторинга (см.Мартин Фаулер "Рефакторинг")
  4. Агрессивно использовать TDD - покрывать тестами функциональность, которая подвергнется рефакторингу, чтобы гарантировать её работоспособность после завершения рефакторинга.
  5. Если возможно, разделить большое приложение на более мелкие модули с более понятной и простой функциональностью.
При этом нужно быть готовым к потерям времени на рефакторинг.

А что бы вы ещё посоветовали?

Development Process Guidelines

"You're pirates. Hang the code, and hang the rules.
They're more like guidelines anyway."


I'll try to figure out some configuration and development process guidelines. Don't take it too seriously, it's just a guidelines :-)

Required infrastructure

Following tools and environments must be available:
  1. Issue Tracking system - JIRA or BugZilla
  2. Version Control System [VCS] - Subversion, CVS, etc.
  3. Continuous Integration Tool [CI tool] - Hudson, Continuum etc.
  4. System Test environment exists (dev-box) - for system test by development team (QA team - by request). Available for developers to deploy.
  5. Integration Test environment exists (tst-box) - for QA team. Not available for developers
  6. Pre-Production (user-acceptance) environment exists (uat-box) - visible to end-uses, used by another projects within organization (clients). Maintained by Integrators
  7. Production environment (prod-box) - maintained by Integrators.

Project requirements

Project structure requires following:
  • Each system component should be buildable (build script exists)
  • Each system component should be buildable on the remote machine using CI tool without human intervention (no manual source modifying) by schedule or forced build.

Roles

  • Developer - works with code
  • Builder - prepares the distribution
  • Tester - tests the application
  • Integrator - manages UAT and PROD environments
Single person may share multiple roles

The Process


  1. Builder gets the binaries from the CI Tool and deploys to dev-box environment.
  2. When internal system test passed:
    1. Packages components, deployed to dev-box, into distribution package with build number assigned.
    2. Builder submits the Package to SCM or special folder.
    3. Builder assembles the change list (or marks issues in issue tracking system).
    4. Builder writes deployment instructions (if required)
    5. Builder sends changelist, deployment instructions and reference to the distribution package to the Tester.
  3. Tester gets the package from the Builder and deploys it to Dev-box.
  4. Tester tests changes described in CHANGELIST or from Bug Tracking system as well as the regression tests
  5. If QA test passed:
    1. Tester marks Distribution package as Release Candidate
    2. Developers implement features and fix bugs. All changes are submitted to the VCS[Version Control System]
    3. Tester writes Release Notes for RC.
    4. Tester sends deploys tested package together with release notes to the Integrator
    5. Integrator installs RC version to uat-box. (Integrator learns how to install the new release over existing running system.)
  6. If UAT phase passed
    1. RC version is marked as release version
    2. Integrator installs RC version to the customer's server.
I must have left something important in process.
Comments are welcome ;-)

03/09/2007

Coding java persistence: JPA vs. Hibernate

In my current projects, I deal with database persistence layer. I use Hibernate implementation of the JPA 1.0 API. But restrictions of JPA API limits my ability to use some cool features of Hibernate, including


  1. Full control over cascade behavior;

  2. Indexed collections;

  3. Collections of primitive values.


"Use JPA when possible, and Hibernate extensions where you can't do without" is a reasonable solution, but... It smells.... Anyway, the code becomes not portable enough when using Hibernate extensions. May be, it would be better to skip JPA and relay on Plain Old Hibernate (POH)?



XML Config vs. Annotations: Prons and Cons


JPA and Hibernate both supports XML mapping config and annotation config.


XML does not require to import annotations in entity classes. It makes entities more portable. They stay a simple POJOs, without any extra dependencies. It makes possible to package them in separate jar and include as a dependency in the client tier.


Annotations make mapping config to live together with property declarations in java file. It is more readable to the developer. It make sense when you don't plan to expose your entities outside the persistence module.

redirect