redirect de spring web flow con zuul

Uno de los problemas que he encontrado últimamente usando Spring cloud oss ha sido el de la integración de Spring web flow (swf) con Zuul.

Si habéis usado swf sabréis que usa el patrón post-redirect para cada una de las llamadas que recibe para manejar así los reloads de página de una manera limpia. El problema viene cuando estás usando una infraestructura como Spring cloud bajo netflix oss. En esta arquitectura tenemos algo parecido a lo siguiente:

zuul problem.001

Como podemos observar tenemos n servicios UI que están sirviendo las peticiones las cuales llegan balanceadas gracias a zuul. Imaginemos que dichos servicios están en los siguientes puntos del cloud:

  • service1: host1:8085
  • service2: host2:8085

Cuando realizamos una llamada a nuestro servicio en http://mydomain.com/myuri dicha petición será escuchada por nginx el cual la redirigirá  a Zuul para que la sirva. Zuul a su vez lo mandará a uno de nuestros servicios gracias al registro de los mismos en Eureka. Pongamos que es service1 quién la sirve. Al hacer post-redirect, la respuesta va a llevar un header 301 y un header “location” indicando la nueva url a la cual redirigirse. Lo esperado sería que dicho location fuera http://mydomain.com/myuri?execution=e1s1, la cual sería perfecta por que la nueva llamada volvería a pasar por toda la infraestructura y la serviría alguno de nuestros servicios sin problemas. Pero no, la url que nos proporciona swf en estos momentos (usando la versión starter 1.3.5  en la versión cloud proporcionada por la Brixton.M4) es http://service1:8085/myuri?execution=e1s1

zuul problem.002

El problema que se crea es el siguiente: Nuestro sistema tiene capada dicha url por seguridad, por lo que acabamos obteniendo una respuesta 500.

Zuul, al menos en esta versión, no maneja bien esta casuística ( al parecer tienen un ticket abierto gracias al cuál esperemos que integren la solución en breve), por lo que la solución pasa por crear un filtro en zuul que solucione este problema.

Si no conocéis los filtros de Zuul, os resumiré que Zuul nos permite facilmente definir filtros para manejar una petición tanto antes, como durante y después de servirla. En su integración con spring boot implementar un filtro zuul es muy sencillo. Solo hemos de seguir los siguientes pasos:

  1. crear una clase que extienda com.netflix.zuul.ZuulFilter
  2. definir en qué punto de la cadena de filtros que se aplicarán va a alojarse dicho filtro sobreescribiendo el método filterOrder
  3. indicar si el filtro se aplica antes, durante o después del enrutado de la petición sobreescribiendo el método filterType especificando uno de los valores para dichos momentos pre, route, post

La implementación que realizaremos será la siguiente: Cambiaremos el header location por la llamada al servicio genérico manteniendo toda la uri y queryparams que contiene el valor aportado por spring web flow. Con esta aproximación el filtro quedaría tal que así:


import java.net.MalformedURLException;

import java.net.URL;

import java.util.Optional;

import org.slf4j.Logger;

import org.springframework.cloud.netflix.zuul.filters.ProxyRouteLocator;

import org.springframework.cloud.netflix.zuul.filters.ProxyRouteLocator.ProxyRouteSpec;

import org.springframework.cloud.netflix.zuul.filters.RouteLocator;

import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import org.springframework.web.util.UrlPathHelper;

import com.netflix.util.Pair;

import com.netflix.zuul.ZuulFilter;

import com.netflix.zuul.context.RequestContext;

public class LocationHeaderRewritingFilter extends ZuulFilter{

protectedstaticfinal String X_FORWARDED_FOR = "X-Forwarded-For";

private Logger log = org.slf4j.LoggerFactory.getLogger(this.getClass());

final UrlPathHelper urlPathHelper = new UrlPathHelper();


final RouteLocator routeLocator;


public LocationHeaderRewritingFilter(RouteLocator routeLocator){

this.routeLocator = routeLocator;

}


@Override

public Object run() {

RequestContext ctx = RequestContext.getCurrentContext();

ProxyRouteSpec route = ((ProxyRouteLocator)routeLocator).getMatchingRoute(urlPathHelper.getPathWithinApplication(ctx.getRequest()));


if (route == null)  return null;


try {

Pair<String, String> lh = locationHeader(ctx);

URL netUrl = new URL(lh.second());


String host = netUrl.getProtocol() +"://" + netUrl.getHost();

String proxyHost = retrieveNewLocationUrl(route);

lh.setSecond(lh.second().replace(

host,

proxyHost));


if(!netUrl.getPath().startsWith(route.getPrefix())){


lh.setSecond(lh.second().replace(

proxyHost,

proxyHost + route.getPrefix()));

}


} catch ( MalformedURLException e) {

log.error("cannot change location from url");

}


return null;

}

private String retrieveNewLocationUrl(ProxyRouteSpec route) {

return ServletUriComponentsBuilder.fromCurrentContextPath().build().toUriString();

}

@Override

publicboolean shouldFilter() {

return locationHeader(RequestContext.getCurrentContext()) != null;

}

@Override

publicint filterOrder() {

return 100;

}

@Override

public String filterType() {

return"post";

}

private Pair<String, String> locationHeader(RequestContext ctx) {

Optional<Pair<String, String>> first = ctx.getZuulResponseHeaders().stream().filter( h -> "Location".equals(h.first())).findFirst();

returnfirst.orElse(null);

}

}

Espero que os sirva de utilidad

Publicado en boot, cloud, netflix oss | Etiquetado , , , | Deja un comentario

dos datasources en spring boot

Hace tiempo que no escribo ya que he estado inmerso en un nuevo proyecto en la empresa en la que trabajo actualmente donde hemos podido aplicar toda la arquitectura de spring cloud netflix oss y no he tenido tiempo para dedicarle al blog. Para no dilatarlo más en el tiempo he decidido crear algunas entradas nuevas en las que explique algunos de los problemas/dudas que hemos encontrado durante el desarrollo del mismo.

Una de las dudas que nos surgió es, ¿cómo declaro el uso de dos datasources en spring boot?

Normalmente la declaración del datasource que use el microservicio se realiza mediante la entrada en el fichero application.yml de este estilo:

spring:
  datasource:
    url: jdbc:oracle:thin:myuri
    username: username
    password: pass
    driver-class-name: driver

En el caso de ser necesario el uso de dos datasources (algo que debe ser excepcional si es un microservicio real) su declaración pasa a ser de esta forma:


datasource:
  nameddbb1:
    url: jdbc:mysql://uriddbb1
    username: usernameddbb1
    password: passbbdd1
    driver-class-name: com.mysql.jdbc.Driver
  nameddbb2:
    url: jdbc:oracle:thin:uriddbb2
    username: usernameddbb2
    password: pwdddbb2
    driver-class-name: oracle.jdbc.driver.OracleDriver

espero que haya sido útil

Publicado en boot | Etiquetado , | Deja un comentario

spring boot y twitter (sign in framework)

Introducción

Tras haber realizado el ejemplo simple de conexión y logado mediante twitter, el siguiente paso sería el de realizar la versión en la que gestionemos nosotros las credenciales, es decir, que recibamos el callback con los datos de la conexión y los del usuario que nos proporciona twitter y los almacenamos y controlamos el registro. Esto es lo que se conoce como framework “sign in”. Para más información podéis visitar el link sobre su sign in with service provider

Spring Security

En primer lugar vamos a añadir Spring Security a nuestra aplicación. Para lo cual solo hay que añadir a nuestro pom las dependencias:

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.social</groupId>
			<artifactId>spring-social-security</artifactId>
		</dependency>

Observamos que también hemos añadido la parte de seguridad de Spring para la parte social. Si en estos momentos arrancamos la aplicación, veremos que se añade al log de arranque una línea tal como esta

Using default security password: 3c2a7a66-5e57-40a5-9007-7ec195b28292

La cual indica que esa es la password que hay que usar para acceder a cualquier página de la aplicación, usando el usuario “user“. Como esto no es lo que queremos, a continuación definimos la configuración para poder indicar qué páginas van a ser seguras y cuales no. Para ello, tal como se realizan las configuraciones usando Spring boot, creamos nuestra clase de configuración, la cual será WebSecurityConfig, anotándola con @Configuration. Siguiendo las reglas que nos indica Spring Security, lo que hemos de hacer es anotar la clase con @EnableWebSecuirty y proporcionar un bean del tipo WebSecurityConfigurerAdapter donde se va a sobreescribir este mapeo de seguridad sin alterar nada más, y anotada con  @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) para indicar la sobreescritura. Aprovecharemos la propia clase de configuración para extender la clase y anotarla de ese modo. Sobreescribimos el método configure y anulamos de momento toda seguridad ya que la iremos definiendo poco a poco. El resultado es el siguiente:

 
@Configuration
@EnableWebSecurity
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

@Override
	protected void configure(HttpSecurity http) throws Exception {
              http.authorizeRequests().anyRequest().permitAll();
        }

Flujo

Vamos a partir de nuestra página home, a la que vamos a cambiar respecto al ejemplo anterior, para aprovechar el motor de layouts de thymeleaf:

Captura de pantalla 2015-10-18 a las 19.15.42

El contenido pasa a ser:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:social="http://spring.io/springsocial" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layout">
  <body>


<div id="content" class="container" layout:fragment="content">

<div class="jumbotron">

<h1>Welcome to social login project!</h1>

</div>

</div>


  </body>
</html>

donde vemos el uso del layout decorator para decirle la plantilla sobre la cual modificamos el contenido. Dicha plantilla será la página layout.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:social="http://spring.io/springsocial" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
	<head>
		<title>Spring Social Showcase</title>
		 <link href="/webapp/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}" rel="stylesheet"/>
	     <link href="/webapp/css/sticky-footer-navbar.css" th:href="@{/css/sticky-footer-navbar.css}" rel="stylesheet"/>

	</head>
	<body>


<div th:replace="userbars/userbar :: #bar">userbar</div>


		


<div id="content" layout:fragment="content">
			Content goes here
		</div>


		
		


<div th:replace="footers/footer :: #footer">footer</div>


		<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
		<script src="/js/bootstrap.min.js"></script>
	</body>
</html>

Como podemos observar tenemos 2 opciones de menú, logarnos en la aplicación o registrarnos. En primer lugar vamos a ver cómo logamos  mediante twitter.

Spring Social

El primer paso es crear la página de logado:

Captura de pantalla 2015-10-18 a las 19.36.29

para la cual hemos usado una plantilla de bootstrap llamada signin.css. Vemos aquí el contenido de la página signin.html.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:social="http://spring.io/springsocial" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorator="layout">
  <head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <title>Social Login Example Sign in Page</title>

    <!-- Bootstrap -->
    <link href="../../webapp/css/bootstrap.min.css" th:href="@{css/bootstrap.min.css}" rel="stylesheet"/>
    <link href="../../webapp/css/font-awesome.min.css" th:href="@{css/font-awesome.min.css}" rel="stylesheet"/>
    <link href="../../webapp/css/bootstrap-social.css" th:href="@{css/bootstrap-social.css}" rel="stylesheet"/>
    <!--     <link href="css/navbar-fixed-top.css" rel="stylesheet"/> -->
    <link href="../../webapp/css/sticky-footer-navbar.css" th:href="@{css/sticky-footer-navbar.css}" rel="stylesheet"/>
    <link href="../../webapp/css/signin.css" th:href="@{css/signin.css}" rel="stylesheet"/>

  </head>
  <body>


<div id="content" class="container" layout:fragment="content">




<form class="form-signin" id="signin" th:action="@{/signin/authenticate}" method="post">
				<input type="hidden" name="_csrf" th:value="${_csrf.token}"></input>


<div class="formInfo">


<div class="error" th:if="${param.error eq 'bad_credentials'}">
						Your sign in information was incorrect. Please try again or <a th:href="@{/signup}">sign up</a>.
					</div>




<div class="error" th:if="${param.error eq 'multiple_users'}">
						Multiple local accounts are connected to the provider account. Try
						again with a different provider or with your username and
						password.</div>


				</div>




<h2 class="form-signin-heading">Please sign in</h2>


        <label for="username" class="sr-only">User Name</label>
        <input type="username" id="username" class="form-control" placeholder="User Name" name="username" required="" autofocus=""></input>
        <label for="inputPassword" class="sr-only">Password</label>
        <input type="password" id="inputPassword" class="form-control" placeholder="Password" name="password" required=""></input>


<div class="checkbox">
          <label>
            <input type="checkbox" value="remember-me"> Remember me</input>
          </label>
        </div>


        <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
      </form>






<div class="jumbotron">


Some test user/password pairs you may use are:



<ul>


<li>elvis/presley</li>




<li>michael/jackson</li>


				</ul>





					Or you can <a th:href="@{/signup}">signup</a> with a new account.
				

			<!-- TWITTER SIGNIN -->
			


<form id="tw_signin" th:action="@{/signin/twitter}" method="POST">
				<input type="hidden" name="_csrf" th:value="${_csrf.token}"></input>
				<button class="btn btn-social btn-twitter" type="submit">
					<i class="fa fa-twitter"></i> Sign in with Twitter
				</button>
			</form>


		</div>


	</div>


</body>
</html>

Vemos que podemos usar un username y password registrados en la aplicación, o usar nuestra cuenta de twitter. Spring Social funciona de la siguiente manera:

  1. en el caso de que el usuario no esté logado en la aplicación llamará a la url /signup para invitar al usuario a registrarse
  2. en el caso de que esté ya registrado llamará a la url GET /signin/{providerId}?code={verifier}

En nuestra primera iteración, al no tener registrado el usuario en la aplicación, mostraremos nuestra página de registro con los datos de usuario obtenidos de twitter para que se registre sin apenas tener que rellenar datos:
Captura de pantalla 2015-10-18 a las 20.07.44 Para este comportamiento hemos modificado nuestro SignUpController de la siguiente manera:

...
@RequestMapping(value = "/signup", method = RequestMethod.GET)
	public SignupForm showRegistrationForm(WebRequest request, Model model) {
		
		Connection<?> connection = providerSignInUtils.getConnectionFromSession(request);
		if (connection != null) {
			request.setAttribute("message", new Message(MessageType.INFO, "Your " + StringUtils.capitalize(connection.getKey().getProviderId()) + " account is not associated with a Spring Social Showcase account. If you're new, please sign up."), WebRequest.SCOPE_REQUEST);
			SignupForm signupForm = SignupForm.fromProviderUser(connection.fetchUserProfile());
			return signupForm;
		} else {
			return new SignupForm();
		}
		
	}
...

Como vemos vamos a partir de SignupForm, un objeto que va a implementar el futuro formulario de creación de cuenta en la aplicación. Dicho objeto es el siguiente:

@Data
public class SignupForm {

	@NotEmpty
	private String username;

	@Size(min = 6, message = "must be at least 6 characters")
	private String password;

	@NotEmpty
	private String firstName;

	@NotEmpty
	private String lastName;

	@NotEmpty
	private String email;

	public static SignupForm fromProviderUser(UserProfile providerUser) {
		SignupForm form = new SignupForm();
		form.setFirstName(providerUser.getFirstName());
		form.setLastName(providerUser.getLastName());
		form.setUsername(providerUser.getUsername());
		form.setPassword(UUID.randomUUID().toString());
		form.setEmail(providerUser.getEmail());
		form.setEmail("NO EMAIL");
		return form;
	}
}

Una vez rellenados todos los datos se nos creará una cuenta en la aplicación y se nos logará en la misma. Si todo va bien la aplicación nos redirigirá a nuestra home.
Captura de pantalla 2015-10-18 a las 20.10.30
Para conseguir toda esta parte hemos de crear toda la infraestructura de registro y creación de cuentas en nuestro sistema. La parte del controlador que recibe nuestra llamada POST a /signup será la siguiente:

...
@RequestMapping(value="/signup", method=RequestMethod.POST)
	public String signup(@Valid SignupForm form, BindingResult formBinding, WebRequest request) {
		if (formBinding.hasErrors()) {
			return null;
		}
		Account account = createAccount(form, formBinding);
		if (account != null) {
			SignInUtils.signin(account.getUsername());
			providerSignInUtils.doPostSignUp(account.getUsername(), request);
			return "redirect:/";
		}
		return null;
	}

private Account createAccount(SignupForm form, BindingResult formBinding) {
		try {
			Account account = new Account(form.getUsername(), form.getPassword(), form.getFirstName(), form.getLastName(), form.getEmail());
			accountRepository.create(account);
			return account;
		} catch (UsernameAlreadyInUseException e) {
			//formBinding.rejectValue("username", "user.duplicateUsername", "already in use");
			return null;
		}
	}
...

Vamos a poner el método de creación de cuentas en el propio controlador por simplificar la estructura del ejemplo. Vemos que vamos a mapear los datos del formulario a un objeto Account

import lombok.Data;

@Data
public class Account {

	private final String username;

	private final String password;

	private final String firstName;

	private final String lastName;

	private final String email;
}

Este objeto sera pasado como argumento a un objeto que implementa la interfaz AccountRepository, el cual va a realizar la persistencia en base de datos. Dicha interfaz es la siguiente:

public interface AccountsRepository {

	void create(Account account) throws UsernameAlreadyInUseException;

	Account findAccountBy(String username);
}

Para llegar a su implementación primero vamos a definir la base de datos y cómo conectarnos a ella.

conexión a base de datos hsqldb

Vamos a almacenar los usuarios en base de datos, para ello vamos a usar la base de datos embebida hsqldb. Lo que haremos será, en primer lugar, añadir las dependencias maven:

	<dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-data-jpa</artifactId>
	</dependency>
	<dependency>
	    <groupId>org.hsqldb</groupId>
	    <artifactId>hsqldb</artifactId>
	    <scope>runtime</scope>
	</dependency>

A continuación, y tal y como nos indica la referencia de spring boot para base de datos embebidas, definimos un fichero en el raíz del classpath llamado schema.sql para definir las tablas que queremos crear y un fichero data.sql para la carga inicial de las mismas. En nuestro caso el contenido será el siguiente:

create table Account (  id identity,
						username varchar (50) unique,
						password varchar (50) not null,
						firstName varchar (50) not null, 
						lastName varchar (50) not null,
						email varchar (50) not null);

repositorio

A continuación definimos la clase que realizará la persistencia y búsqueda de las cuentas de usuario y que implementará la interfaz AccountRepository:

@Repository
public class JdbcAccountRepository implements AccountsRepository {

	private final JdbcTemplate jdbcTemplate;

	private final PasswordEncoder passwordEncoder;

	@Inject
	public JdbcAccountRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
		this.jdbcTemplate = jdbcTemplate;
		this.passwordEncoder = passwordEncoder;
	}

	@Transactional
	public void create(Account user) throws UsernameAlreadyInUseException {
		try {
			jdbcTemplate.update(
					"insert into Account (firstName, lastName, username, password, email) values (?, ?, ?, ?, ?)",
					user.getFirstName(), user.getLastName(), user.getUsername(),
					passwordEncoder.encode(user.getPassword()), user.getEmail());
		} catch (DuplicateKeyException e) {
			throw new UsernameAlreadyInUseException(user.getUsername());
		}
	}

	public Account findAccountBy(String username) {
		return jdbcTemplate.queryForObject("select username, firstName, lastName, email from Account where username = ?",
				new RowMapper<Account>() {
					public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
						return new Account(rs.getString("username"), null, rs.getString("firstName"), rs
								.getString("lastName"), rs.getString("email"));
					}
				}, username);
	}

}

Analizando este código, podemos ver que hemos usado el template jdbc de Spring para el acceso simple a la base de datos. El segundo paso es definir el login a la aplicación en el caso de que estés ya registrado.

Spring Security

Veamos de nuevo la página de login:

Captura de pantalla 2015-10-18 a las 19.36.29
En primer lugar definimos a grano más fino las urls securizadas:

definición de estáticos

Sobreescribimos en nuestra clase WebSecurityConfig el método:

@Override
	public void configure(WebSecurity web) throws Exception {
		web
			.ignoring()
				.antMatchers("/**/*.css", "/**/*.png", "/**/*.gif", "/**/*.jpg, /**/*.js");
	}

definición de seguridad a nivel de url,

hay que sobreescribir el método Para las urls de login definimos esta parte:

@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.formLogin()
			.loginPage("/signin")
			.loginProcessingUrl("/signin/authenticate")
			.failureUrl("/signin?param.error=bad_credentials")
...

para permitir la url de logout de la aplicación:

...
.and()
				.logout()
					.logoutUrl("/signout")
					.deleteCookies("JSESSIONID")
					.invalidateHttpSession(true)
					.permitAll()
...

y las urls permitidas de libre acceso

...
		.and()
				.authorizeRequests()
					.antMatchers("/admin/**", "/favicon.ico", "/resources/**", "/auth/**", "/signin/**", "/signup/**").permitAll()
					.antMatchers("/**").authenticated();
}

El siguiente paso es definirle a Spring Security el repositorio al que consultar por las credenciales de los usuarios que intentan logarse. Para ello le inyectamos a la clase WebSecurityConfig el datasource de nuestra base de datos hsql y sobreescribimos el método registerAuthentication en el que le indicamos cómo realizar la consulta:

...
@Inject
private DataSource dataSource;
@Autowired
	public void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
		auth.jdbcAuthentication()
				.dataSource(dataSource)
				.usersByUsernameQuery("select username, password, true from Account where username = ?")
				.authoritiesByUsernameQuery("select username, 'ROLE_USER' from Account where username = ?")
				.passwordEncoder(passwordEncoder());
	}
...

Con esto ya nuestra aplicación realizará el login y permitirá acceder al usuario a las urls seguras.

Login de twitter con usuario registrado

Vamos a ver el caso en el que el usuario ya existe  en la aplicación y uno accede mediante su usuario de twitter Cuando el usuario pulsa el botón de twitter, se registra y vuelve a la aplicación, gracias a la integración de Spring social con Spring security, la aplicación va a comprobar que dicho usuario ya está registrado como usuario de la aplicación, por lo que va a llamar al signin adapter definido en el sistema para realizar dicho logado a la aplicación. Para poder controlar el comportamiento vamos a sobreescribir la clase  SignInAdapter de Spring social tal y como nos dice su referencia.

public class MySignInAdapter implements SignInAdapter{
	
	private final RequestCache requestCache;

	public MySignInAdapter(RequestCache requestCache) {
		this.requestCache = requestCache;
	}
	
	@Override
	public String signIn(String localUserId, Connection<?> connection, NativeWebRequest request) {
		SignInUtils.signin(localUserId);
		return extractOriginalUrl(request);
	}

	private String extractOriginalUrl(NativeWebRequest request) {
		HttpServletRequest nativeReq = request.getNativeRequest(HttpServletRequest.class);
		HttpServletResponse nativeRes = request.getNativeResponse(HttpServletResponse.class);
		SavedRequest saved = requestCache.getRequest(nativeReq, nativeRes);
		if (saved == null) {
			return null;
		}
		requestCache.removeRequest(nativeReq, nativeRes);
		removeAutheticationAttributes(nativeReq.getSession(false));
		return saved.getRedirectUrl();
	}
		 
	private void removeAutheticationAttributes(HttpSession session) {
		if (session == null) {
			return;
		}
		session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
	}

}

Observando este código vemos que lo que hará será el realizar el signin en la aplicación gracias al SigninUtils y a continuación obtener la url original para redirigir a ella después del logado en la aplicación. El código de la clase SigninUtils es el siguiente:


public class SignInUtils {

	/**
	 * Programmatically signs in the user with the given the user ID.
	 */
	public static void signin(String userId) {
		SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(userId, null, null));	
	}
}

Vemos que tan solo se va a encargar de añadir al usuario dentro de Spring Security como usuario autenticado. Por último hay que indicarle a Spring Social que use nuestro SigninAdapter para lo cual definimos una clase de configuración de la parte social:


@Configuration
@EnableSocial
public class SocialConfig {
	
    @Bean
	public SignInAdapter signInAdapter() {
		return new MySignInAdapter(new HttpSessionRequestCache());
	}
}

Con esto hemos terminado la integración y cumplido el flujo de trabajo que queríamos tratar.

twitter

Como vemos ahora nos aparece una bienvenida en la parte superior derecha con nuestro nombre de usuario y el link para deslogarse de la aplicación. También aparece una sección “twitter” donde encontraremos algunas acciones que podemos hacer fácilmente con la api de Spring Social de Twitter:

Captura de pantalla 2015-10-18 a las 20.13.01

 profile

Veamos el controlador

@Controller
public class ProfileController {

	private final Twitter twitter;

	@Inject
	public ProfileController(Twitter twitter) {
		this.twitter = twitter;
	}
	
	@RequestMapping(value="/twitter/profile", method=RequestMethod.GET)
	public String home(Principal currentUser, Model model) {
		model.addAttribute("profile", twitter.userOperations().getUserProfile());
		return "twitter/profile";
	}
}

y vemos aquí el resultado:

Captura de pantalla 2015-10-22 a las 22.56.06

Seguidores y siguiendos

Veamos el controlador

@Controller
public class FriendsController {

	private final Twitter twitter;
	
	@Inject
	public FriendsController(Twitter twitter) {
		this.twitter = twitter;
	}
	
	@RequestMapping(value="/twitter/friends", method=RequestMethod.GET)
	public String friends(Model model) {
		model.addAttribute("twitterProfile", twitter.userOperations().getUserProfile());
		model.addAttribute("friends", twitter.friendOperations().getFriends());
		return "twitter/friends";
	}

	@RequestMapping(value="/twitter/followers", method=RequestMethod.GET)
	public String followers(Model model) {
		model.addAttribute("twitterProfile", twitter.userOperations().getUserProfile());
		model.addAttribute("friends", twitter.friendOperations().getFollowers());
		return "twitter/friends";
	}
}

y vemos aquí el resultado:

Captura de pantalla 2015-10-22 a las 22.57.08

Trends

Veamos el controlador

@Controller
public class TrendsController {

	// Yahoo Where On Earth ID representing the entire world
		private static final long WORLDWIDE_WOE = 1L;
		
		private final Twitter twitter;

		@Inject
		public TrendsController(Twitter twitter) {
			this.twitter = twitter;
		}

		@RequestMapping(value="/twitter/trends", method=RequestMethod.GET)
		public String showTrends(Model model) {

			model.addAttribute("twitterProfile", twitter.userOperations().getUserProfile());
			model.addAttribute("trends", twitter.searchOperations().getLocalTrends(WORLDWIDE_WOE));
			return "twitter/trends";
		}
}

y vemos aquí el resultado:

Captura de pantalla 2015-10-22 a las 22.58.02 Y esto ha sido todo lo que queríamos abarcar en este post. Todo el código de este ejemplo lo podéis encontrar aquí.

Para más información podéis acceder a :

Publicado en security, social, spring | Etiquetado , , , , | Deja un comentario

spring boot y twitter (connect framework)

Vamos a intentar crear una pequeña aplicación “bootiful” que use twitter como su servicio de autenticación. Para ello vamos a ir enumerando los pasos que vamos siguiendo hasta conseguirlo.

dependencias

De momento, para este ejemplo, vamos a añadir las dependencias de spring boot y spring thymeleaf ya que lo usaremos para renderizar

<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-thymeleaf</artifactId>
	</dependency>

	<!-- 4 testing -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
	

proyecto web

A continuación lo convertimos a proyecto web en nuestro eclipse, dándole características de proyecto con facetas y marcando Dynamic web module, indicando que nuestra carpeta web será webapp en lugar de la webContainer que propone eclipse. Vamos a usar Java 8 y la versión 3 de servlet.

página de inicio

Vamos a crear nuestra página de inicio que tan solo nos dará la bienvenida poniendo un fichero index.html con el texto welcome to the app y convertimos nuestra clase App.class en una aplicación spring boot

@SpringBootApplication
public class App 
{
    public static void main( String[] args )
    {
    	 SpringApplication.run(App.class, args);
    }
}

Arrancamos, accedemos a la dirección por defecto y obtenemos nuestra página de inicio con su mensaje welcome to the app

bootstrap

Ya tenemos una aplicación web con la que trabajar. El siguiente paso para embellecer la aplicación y darle algo de dinamismo va a ser instalar bootstrap. Para ello vamos a su página web y descargamos sus fuentes, descomprimimos el zip y copiamos las carpetas que vienen en su directorio dist en nuestra carpeta webapp. A continuación creamos el controlador de nuestra home

@Controller
public class HomeController {

    @RequestMapping("/")
    public String home() {
       return "home";
    }
}

Al usar thymeleaf, creamos nuestras páginas como html y las alojamos dentro de la carpeta templates de nuestra carpeta de resources. Como vemos en el controlador, se necesitará un home.html, el cuál tendrá el siguiente código:

<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <title>Social Login Example Home Page</title>

    <!-- Bootstrap -->
    <link href="../../webapp/css/bootstrap.min.css" th:href="@{css/bootstrap.min.css}" rel="stylesheet"/>
    <link href="../../webapp/css/sticky-footer-navbar.css" th:href="@{css/sticky-footer-navbar.css}" rel="stylesheet"/>

  </head>
  <body>


<div th:replace="userbars/userbar :: #bar">userbar</div>
<div class="container">
<div class="jumbotron">
<h1>Welcome to Social Login project!</h1>
</div>
</div>


<div th:replace="footers/footer :: #footer">footer</div>

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="js/bootstrap.min.js"></script>
  </body>
</html>

Vemos que necesitamos un userbar y un footer que están alojados en userbars/userbar.html

<html>
  <body>
<div id="content">
      Only this will be rendered!!
    </div>
<nav id="bar" class="navbar navbar-inverse navbar-fixed-top">

<div class="container">
<div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
              <span class="sr-only">Toggle navigation</span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
              <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#" th:href="@{/}">Social Project</a>
          </div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="./register.html" th:href="@{/user/register}">Register</a></li>
<li><a href="./login.html" th:href="@{/signup}">Login</a></li>
            </ul>
          </div>
       </div>
   </nav>
  </body>
</html>

y en footers/footer.html

<html>
  <body>
<footer id="footer" class="footer">
<div class="container">
copyright pretonik 2015.
      </div>
    </footer>
  </body>
</html>

Arrancamos y vemos que ya tenemos nuestra home lista con su cabecera y dos links, uno para logarse y otro para registrarse.

Captura de pantalla 2015-08-24 a las 20.18.24

Comenzaremos por el primer paso lógico, registrarnos.

página de registro

Creamos el que será el controlador de registro de usuario

@Controller
public class RegistrationController {
	@RequestMapping(value = "/user/register", method = RequestMethod.GET)
	public String showRegistrationForm(WebRequest request, Model model) {
		return "signin";
	}
}

De momento solo redirige a la página de registro signin.html:

<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <title>Social Login Example Sign in Page</title>

    <!-- Bootstrap -->
    <link href="../../webapp/css/bootstrap.min.css" th:href="@{css/bootstrap.min.css}" rel="stylesheet"/>
    <link href="../../webapp/css/font-awesome.min.css" th:href="@{css/font-awesome.min.css}" rel="stylesheet"/>
    <link href="../../webapp/css/bootstrap-social.css" th:href="@{css/bootstrap-social.css}" rel="stylesheet"/>
    <!--     <link href="css/navbar-fixed-top.css" rel="stylesheet"/> -->
    <link href="../../webapp/css/sticky-footer-navbar.css" th:href="@{css/sticky-footer-navbar.css}" rel="stylesheet"/>

  </head>
  <body>
<div th:replace="userbars/userbar :: #bar">userbar</div>
<div class="container">
<div class="jumbotron">
<h1>Welcome to sign up page !</h1>
    <!-- TWITTER SIGNIN -->
<form id="tw_signin" th:action="@{/connect/twitter}" method="POST">
				<button class="btn btn-social btn-twitter" type="submit"><i class="fa fa-twitter"></i> Connect with Twitter</button>
				
			</form>
    </div>
</div>
<div th:replace="footers/footer :: #footer">footer</div>

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="js/bootstrap.min.js"></script>
  </body>
</html>

Como podéis ver, hemos añadido bootstrap-social a nuestra página de sign in, para poder dejar la cosa un poco más bonita:

Captura de pantalla 2015-09-22 a las 8.53.39

El primer paso que haremos será el de permitir el registro mediante la cuenta de twitter, sin tener que crear un usuario de manera explícita. Para ello seguimos los pasos marcados por Spring Social

registro de aplicación en twitter

Todo usuario de twitter puede ser un desarrollador de aplicaciones que usen twitter. Para ello solo hace falta añadir la aplicación que vamos a crear aquí os dejo el link para realizarlo

configuración

Una vez registrada nuestra aplicación en twitter, el primer paso es incluir la dependencia de spring social en nuestro proyecto

 
	<dependency> 
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-social-twitter</artifactId>
	</dependency>

A continuación editar nuestro archivo application.properties para incluir las claves que nos han dado en twitter para autorizar el acceso. Como nosotros aún no lo tenemos, pues lo creamos en /src/main/resources e incluimos las líneas

spring.social.twitter.appId=M471sdfsq44Whmnvpcc9I
spring.social.twitter.appSecret=g6UD0aLv5tsdgsdfdsOYOymGo9TEuOkx3NQOHiEcpC

Y ahora arrancamos nuestra aplicación, vamos a la página de login y probamos a pulsar el link que habíamos dejado preparado en nuestra jsp. Al pulsar nos redireccionará a la página para logramos y autorizar de este modo, a la aplicación que hemos registrado en el paso previo, a acceder a nuestra cuenta

Captura de pantalla 2015-09-16 a las 9.14.34

Si la autorizamos el siguiente paso será el de redirigirnos a la url /connect/twitter en modo GET e intentar renderizar la template connect/twitterConnected. Si no implementamos dicha template nos dará error. Implementamos dicho html:

<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <title>Social Login Example Sign in Page</title>

    <!-- Bootstrap -->
    <link href="../../webapp/css/bootstrap.min.css" th:href="@{css/bootstrap.min.css}" rel="stylesheet"/>
    <link href="../../webapp/css/font-awesome.min.css" th:href="@{css/font-awesome.min.css}" rel="stylesheet"/>
    <!--     <link href="css/navbar-fixed-top.css" rel="stylesheet"/> -->
    <link href="../../webapp/css/sticky-footer-navbar.css" th:href="@{css/sticky-footer-navbar.css}" rel="stylesheet"/>
  </head>
  <body>

<div th:replace="userbars/userbar :: #bar">userbar</div>
<div class="container">
<div class="jumbotron">
<h1>Connected to Twitter !</h1>
			You are now connected to your Twitter account.
			Click <a href="/">here</a> to see your Twitter friends.
    </div>
</div>
<div th:replace="footers/footer :: #footer">footer</div>


    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="js/bootstrap.min.js"></script>
  </body>
</html>

que nos renderizará lo siguiente:

Captura de pantalla 2015-09-24 a las 23.34.17

Cómo veis hemos puesto un link para ver información de la persona logada. Esto llamará de nuevo a la home. Esto lo hemos hecho con la intención de demostrar que realmente estamos conectados a twitter. Para ello vamos a cambiar el código de nuestro HomeController por lo siguiente:

@Controller
public class HomeController {

	 private Twitter twitter;

	 private ConnectionRepository connectionRepository;
	
	@Inject
	public HomeController(Twitter twitter,
			ConnectionRepository connectionRepository) {
		this.twitter = twitter;
		this.connectionRepository = connectionRepository;
	}

	@RequestMapping("/")
	public String home(Model model) {

       if (connectionRepository.findPrimaryConnection(Twitter.class) == null) {
           return "home";
       }

       model.addAttribute(twitter.userOperations().getUserProfile());
       CursoredList<TwitterProfile> friends = retrieveFriends();
       model.addAttribute("friends", friends);
       return "hello";
	}

	/**
	 * @return
	 */
	public CursoredList<TwitterProfile> retrieveFriends() {
		CursoredList<TwitterProfile> friends = twitter.friendOperations().getFriends();
		return friends;
	}
}

Tal y como podemos observar, le hemos inyectado en su construcción el repositorio de conexión de twitter y la clase que registrará la información de la conexión con twitter. Cuando el controlador recibe una petición va a comprobar si el usuario está conectado o no. Si lo está, muestra sus amigos redirigiendo a un template llamado hello.html. En caso contrario lo que hará es mostrar la home tal y como la conocemos.

Ponemos aquí cómo es el resultado al estar logado y redirigido a la página hello:

Captura de pantalla 2015-09-24 a las 9.02.23

 

Para más información podéis visitar el link sobre su connect framework

nota: como idea, si se quiere poder dirigir a una landing distinta en función de alguna condición, siempre se puede implementar el controller para la versión GET de /connect/twitter y realizar ahí la algoritmia necesaria para este cometido.

El contenido de este ejemplo podéis encontrarlo aquí.

El siguiente paso sería el de realizar la versión en la que gestionemos nosotros las credenciales, es decir, que recibimos el callback con los datos de la conexión y los del usuario que nos proporciona twitter y los almacenamos y controlamos el registro.

Publicado en social, spring | Etiquetado , , | Deja un comentario

una de patrones … visitor pattern

El patrón Visitor está incluido dentro de los llamados patrones de comportamiento o behavioral patterns.

visitor pattern

La definición de GoF es:

Represent an operation to be performed on elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

Como vemos, es una manera ideal de modificar comportamientos sin modificar las clases sobre las que opera.

También es muy útil para operaciones iterativas. El ejemplo típico es el de la iteración sobre una estructura de directorios. Con este patrón se pueden realizar operaciones sobre los ficheros como búsquedas, backups, borrado etc, implementando un Visitor por cada operación.

la idea:

La idea se mueve sobre la existencia de 2 objetos distintos, uno llamado “element” y el otro  “Visitor“. El objeto element tiene un método “accept“, el cual recibe un objeto visitor como argumento y va a invocar el método “visit” del mismo para ejecutar la operación que dicho objeto representa.

De esta manera se pueden implementar nuevos algoritmos que afecten a un conjunto de objetos simplemente proporcionando nuevos visitors y pasándolos como argumento a los mismos.

Vamos a verlo mejor con un ejemplo:

problema a resolver:

El ejemplo va a mostrar cómo un árbol de nodos, que en este caso representan objetos de una tienda online, van a imprimir el título del elemento. En lugar de crear un método print en cada uno de ellos se van a crear 2 clases Visitor, una para mostrar el título completo y otra para mostrar el título alargado, que serán las que impriman dicho título.

implementación:

En primer lugar vamos a crear una clase abstracta que implementa los métodos de las clases Visitors

public abstract class TitleVisitor {
   String title;
   public void setTitle(String in) {
       this.title = in;
   }
   public String getTitle() {
       return this.title;
   }
    
   public abstract void visit(BookInfo bookInfo);
   public abstract void visit(DvdInfo dvdInfo);   
   public abstract void visit(GameInfo gameInfo);
}

El siguiente paso será implementar los dos algoritmos que mostrarán los títulos en dos visitors distintos. El primero será en que muestra los títulos completos

public class TitleLongVisitor extends TitleVisitor {
   public void visit(BookInfo bookInfo) {
       this.setTitle("LB-Book: " + 
                           bookInfo.getTitleName() + 
                           ", Author: " + 
                           bookInfo.getAuthor());
   }   
   
   public void visit(DvdInfo dvdInfo) {
       this.setTitle("LB-DVD: " + 
                           dvdInfo.getTitleName() + 
                           ", starring " + 
                           dvdInfo.getStar() + 
                           ", encoding region: " + 
                           dvdInfo.getEncodingRegion());
   }   
   
   public void visit(GameInfo gameInfo) {
       this.setTitle("LB-Game: " + 
                           gameInfo.getTitleName());
   }   
}

y a continuación el que implementa los títulos cortos

public class TitleShortVisitor extends TitleVisitor {
   public void visit(BookInfo bookInfo) {
       this.setTitle("SB-Book: " + bookInfo.getTitleName());
   }   
   
   public void visit(DvdInfo dvdInfo) {
       this.setTitle("SB-DVD: " + dvdInfo.getTitleName());
   }
   
   public void visit(GameInfo gameInfo) {
       this.setTitle("SB-Game: " + gameInfo.getTitleName());
   }
}

El siguiente paso va a ser el crear una clase abstracta que extenderán todas nuestras clases element

public abstract class AbstractTitleInfo {  
   private String titleName;    
   public final void setTitleName(String titleNameIn) {
       this.titleName = titleNameIn;
   }
   public final String getTitleName() {
       return this.titleName;
   }
    
   public abstract void accept(TitleVisitor titleVisitor);
}

y vamos a crear tres clases elements, la primera será una clase book

public class BookInfo extends AbstractTitleInfo {  
   private String author;
    
   public BookInfo(String titleName, String author) {
       this.setTitleName(titleName);
       this.setAuthor(author);
   }    
   
   public void setAuthor(String authorIn) {
       this.author = authorIn;
   }
   public String getAuthor() {
       return this.author;
   }
   
   public void accept(TitleVisitor titleVisitor) {
       titleVisitor.visit(this);
   }
}

la siguiente una clase dvd

public class DvdInfo extends AbstractTitleInfo {  
   private String star;
   private char encodingRegion;
    
   public DvdInfo(String titleName, 
                  String star, 
                  char encodingRegion) {
       this.setTitleName(titleName);
       this.setStar(star);
       this.setEncodingRegion(encodingRegion);
   }    
   
   public void setStar(String starIn) {
       this.star = starIn;
   }
   public String getStar() {
       return this.star;
   }
   public void setEncodingRegion(char encodingRegionIn) {
       this.encodingRegion = encodingRegionIn;
   }
   public char getEncodingRegion() {
       return this.encodingRegion;
   }
   
   public void accept(TitleVisitor titleVisitor) {
       titleVisitor.visit(this);
   } 
}

y finalmente una clase game

public class GameInfo extends AbstractTitleInfo {  
   public GameInfo(String titleName) {
       this.setTitleName(titleName);
   }    
   
   public void accept(TitleVisitor titleVisitor) {
       titleVisitor.visit(this);
   }
}

Por último creamos la clase que ejecutará la prueba

class TestTitleVisitor {
   public static void main(String[] args) {
       AbstractTitleInfo bladeRunner = 
         new DvdInfo("Blade Runner", "Harrison Ford", '1');
       AbstractTitleInfo electricSheep = 
         new BookInfo("Do Androids Dream of Electric Sheep?", 
                      "Phillip K. Dick");
       AbstractTitleInfo sheepRaider = 
         new GameInfo("Sheep Raider");
       
       TitleVisitor titleLongVisitor = 
         new TitleLongVisitor();
       
       System.out.println("Long Titles:");     
       bladeRunner.accept(titleLongVisitor);
       System.out.println("Testing bladeRunner   " + 
                           titleLongVisitor.getTitle());
       electricSheep.accept(titleLongVisitor);
       System.out.println("Testing electricSheep " + 
                           titleLongVisitor.getTitle());
       sheepRaider.accept(titleLongVisitor);
       System.out.println("Testing sheepRaider   " + 
                           titleLongVisitor.getTitle());
       
       TitleVisitor titleShortVisitor = 
         new TitleShortVisitor();
       
       System.out.println("Short Titles:");     
       bladeRunner.accept(titleShortVisitor);
       System.out.println("Testing bladeRunner   " + 
         titleShortVisitor.getTitle());
       electricSheep.accept(titleShortVisitor);
       System.out.println("Testing electricSheep " + 
         titleShortVisitor.getTitle());
       sheepRaider.accept(titleShortVisitor);
       System.out.println("Testing sheepRaider   " + 
         titleShortVisitor.getTitle());
   }
}      

y vemos su traza

Long Titles:


Testing bladeRunner   LB-DVD: 
  Blade Runner, starring Harrison Ford, encoding region: 1
Testing electricSheep LB-Book: 
  Do Androids Dream of Electric Sheep?, Author: Phillip K. Dick
Testing sheepRaider   LB-Game: 
  Sheep Raider


Short Titles:
Testing bladeRunner   SB-DVD: 
  Blade Runner
Testing electricSheep SB-Book: 
  Do Androids Dream of Electric Sheep?
Testing sheepRaider   SB-Game: 
  Sheep Raider 

Es un buen patrón para implementar cuando vas a tener ciertos objetos a los que se les van a aplicar varias operaciones que pueden cambiar con el tiempo por motivos de negocio.

Publicado en patterns, refactoring | Etiquetado , , , | 1 Comentario

una de patrones … chain of responsibility pattern

El patrón chain of responsibility o “cadena de responsabilidades” es uno de los patrones de comportamiento más usados.

chain of responsabilityComo su nombre indica, es un patrón cuyo enfoque consiste en definir una serie de clases las cuales van a recibir una petición y cada una de ellas dirá si es su responsabilidad atender la petición o no. Esto es muy útil sobre todo a la hora de desacoplar quién hace la petición de quién la sirve.

Se usa mucho para hacer filtros, gestores de servicios… y en general siempre que se recibe una petición que va a ser servida por una entidad concreta entre un conjunto de posibilidades.

Normalmente su estructura suele contar de un interfaz que indica la acción a realizar, una clase abstracta que controla la cadena, una clase que monta la cadena y los n eslabones de la cadena, cada uno de los cuales controla si son los responsables de servir la petición o no y cómo realizar la acción..

un ejemplo típico es el del logger:

problema a resolver

poder logar en distintas salidas, dependiendo del nivel de importancia de la traza, implementando un patrón “chain of responsability”

implementación

En primer lugar creamos la interfaz que define la acción a realizar

public interface MyLogger {

    void logMessage(int level, String message);
}

En segundo lugar creamos la clase abstracta que dará soporte a todas las implementaciones concretas de la funcionalidad, la cual extiende la interfaz creada anteriormente.

public abstract class AbstractLogger implements MyLogger{
   public static int INFO = 1;
   public static int DEBUG = 2;
   public static int ERROR = 3;

   protected int level;

   //next element in chain or responsibility
   protected AbstractLogger nextLogger;

   public void setNextLogger(AbstractLogger nextLogger){
      this.nextLogger = nextLogger;
   }

   public void logMessage(int level, String message){
      if(this.level <= level){
         write(message);
      }
      if(nextLogger !=null){
         nextLogger.logMessage(level, message);
      }
   }

   abstract protected void write(String message);
	
}

A continuación definimos las tres funcionalidades de Loggin (nuestros eslabones de la cadena):

salida por consola

public class ConsoleLogger extends AbstractLogger {

   public ConsoleLogger(int level){
      this.level = level;
   }

   @Override
   protected void write(String message) {		
      System.out.println("Standard Console::Logger: " + message);
   }
}

Salida estándar de error

ublic class ErrorLogger extends AbstractLogger {

   public ErrorLogger(int level){
      this.level = level;
   }

   @Override
   protected void write(String message) {		
      System.out.println("Error Console::Logger: " + message);
   }
}

Y salida por fichero

public class FileLogger extends AbstractLogger {

   public FileLogger(int level){
      this.level = level;
   }

   @Override
   protected void write(String message) {		
      System.out.println("File::Logger: " + message);
   }
}

Definimos el montaje de la cadena a través de una clase builder

public class ChainPatternBuilder {
	
   public MyLogger getChainOfLoggers(){

      AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
      AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
      AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);

      errorLogger.setNextLogger(fileLogger);
      fileLogger.setNextLogger(consoleLogger);

      return errorLogger;	
   }
}

Vemos cómo usar la cadena de responsabilidad

public class ChainPatternDemo {
	
   public static void main(String[] args) {
      MyLogger loggerChain = (new ChainPatternBuilder()).getChainOfLoggers();

      loggerChain.logMessage(AbstractLogger.INFO, 
         "This is an information.");

      loggerChain.logMessage(AbstractLogger.DEBUG, 
         "This is an debug level information.");

      loggerChain.logMessage(AbstractLogger.ERROR, 
         "This is an error information.");
   }
}

Y por último verificamos la salida

Standard Console::Logger: This is an information.
File::Logger: This is an debug level information.
Standard Console::Logger: This is an debug level information.
Error Console::Logger: This is an error information.
File::Logger: This is an error information.
Standard Console::Logger: This is an error information.

La cadena de responsabilidades, a nivel estricto, es exclusiva, es decir, solo hay una entidad responsable de atender la petición, pero puede extenderse sin problema a un modelo no exclusivo donde siempre extiendas la llamada, pese a procesarla, para que otros puedan también procesarla si les toca, es decir, un modelo estilo “bus”.

Publicado en java, patterns, refactoring | Etiquetado , , , | Deja un comentario

una de patrones … Behavioral patterns

Los patrones de comportamiento o behavioral patterns son aquellos enfocados a cambiar el comportamiento de la ejecución de una clase en función de los parámetros proporcionados.

Aquí vemos un resumen de todos los patrones definidos por GOF:

designpatterns1-2

No entraremos a definir todos ellos sino un subconjunto de los más usados en programación web basada en Java.

Publicado en patterns, refactoring | Etiquetado , , , | 1 Comentario