martes, 10 de agosto de 2010

Spring Security Mediante Base de Datos

En el post anterior les mostré como implementar spring security de la manera más básica, ahora continuaremos pero esta vez ya no obtendremos los usuarios desde XML como en:














Ahora vamos a obtener los usuarios desde un archivo de propiedades, para eso el XML anterior se reduce a:










Ojo que el archivo de propiedades lo podemos obtener desde el classpath de la aplicación(como en el ejemplo anterior)
o desde un archivo externo a la aplicación como en:









De esta manera podemos modificar el archivo de propiedades sin desempaquetar el EAR o WAR de la aplicación


El archivo de propiedades que contiene a los usuarios es bien simple, le especificamos el usuario, password, los roles o perfiles y si está habilitado o no:


#usuario=password,rol1[,rolN][,enabled|disabled]
ccacique1=pass1,PERFIL_ADMINISTRADOR,disabled
ccacique2=pass2,PERFIL_SUPERVISOR,enabled
ccacique3=pass3,PERFIL_ADMINISTRADOR,PERFIL_SUPERVISOR,enabled


La gestión de usuarios mediante archivos(sea en XML o en properties) no es apropiado en un entorno de producción donde pueden interactuar cientos o miles de usuarios, por lo que spring security nos proporciona una manera de obtener dichos usuarios desde la base de datos:


Para implementar la gestión de usuarios mediante una base de datos con spring security debemos de hacer lo siguiente:


  1. Implementar un proveedor de usuarios.

  2. El proveedor de usuarios debe de implementar la interface org.springframework.security.core.userdetails.UserDetailsService

  3. La interface anterior obliga a definir el método public UserDetails loadUserByUsername(String username), por lo que debemos de implementarlo

  4. El método anterior retorna un objeto de la clase org.springframework.security.core.userdetails.UserDetails, esta clase define campos de autenticación de usuarios simple como nombre de usuario, password, si el usuario está bloqueado y los perfiles asociados a este usuario.

  5. Colocar la referencia al proveedor de usuarios en donde usuarioService es mi proveedor de usuarios



Veamos la implementación del gestor de usuarios:



package pe.com.slcsccy.pc.springsecurity.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import pe.com.slcsccy.pc.springsecurity.dao.UsuarioDAO;
import pe.com.slcsccy.pc.springsecurity.model.Usuario;

@Service("usuarioService")
public class UsuarioServiceImpl implements UserDetailsService{
@Autowired
private UsuarioDAO usuarioDAO;

@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
Usuario usuario = null;
List usuarios = usuarioDAO.buscarUsuarioPorCodigo(username);
if(usuarios.size()==0)throw new UsernameNotFoundException("Usuario no existe.");
else{
usuario = usuarios.get(0);
usuario.setPerfiles(usuarioDAO.buscarPerfilesDeUsuarioPorCodigo(username));
return usuario;
}
}
}


El proveedor de usuarios hace uso de un objeto de acceso a datos, que lo he implementado con spring jdbc:


package pe.com.slcsccy.pc.springsecurity.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.core.GrantedAuthority;

import pe.com.slcsccy.pc.springsecurity.model.Perfil;
import pe.com.slcsccy.pc.springsecurity.model.Usuario;

public class UsuarioDAOImpl implements UsuarioDAO {

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}

@SuppressWarnings("unchecked")
@Override
public List buscarUsuarioPorCodigo(String codigo) {
return jdbcTemplate.query("select * from usuario where cod_usuario = ? ", new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Usuario usuario = new Usuario();
usuario.setUsername(rs.getString("cod_usuario"));
usuario.setPassword(rs.getString("des_password"));
usuario.setAccountNonExpired(rs.getString("in_cuenta_expirada").equals("N")?true:false);
usuario.setAccountNonLocked(rs.getString("in_cuenta_bloqueada").equals("N")?true:false);
usuario.setCredentialsNonExpired(rs.getString("in_credencial_expirada").equals("N")?true:false);
usuario.setEnabled(rs.getString("in_habilitado").equals("S")?true:false);
return usuario;
}
},codigo);
}

@SuppressWarnings("unchecked")
@Override
public List buscarPerfilesDeUsuarioPorCodigo(String codigo) {
return jdbcTemplate.query("select cod_perfil from usuario_perfil where cod_usuario = ? ", new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Perfil perfil = new Perfil();
perfil.setNombrePerfil(rs.getString("cod_perfil"));
return perfil;
}
},codigo);
}
}


El acceso a los datos se realiza mediante un modelo de dominio de usuarios y de perfiles siguiente:

Usuario.java


package pe.com.slcsccy.pc.springsecurity.model;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class Usuario implements UserDetails{

private static final long serialVersionUID = -4993799715730712001L;

private List perfiles;
private String username;
private String password;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private boolean enabled;

public Collection getPerfiles() {
return perfiles;
}

@SuppressWarnings("unchecked")
public void setPerfiles(List perfiles) {
this.perfiles = (List) perfiles;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isAccountNonExpired() {
return accountNonExpired;
}
public void setAccountNonExpired(boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}
public boolean isAccountNonLocked() {
return accountNonLocked;
}
public void setAccountNonLocked(boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
public void setCredentialsNonExpired(boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Collection getAuthorities() {
return perfiles;
}

}



Perfil.java


package pe.com.slcsccy.pc.springsecurity.model;
import org.springframework.security.core.GrantedAuthority;
public class Perfil implements GrantedAuthority{

private static final long serialVersionUID = 1L;
private String nombrePerfil;

public Perfil(){
}

public String getNombrePerfil() {
return nombrePerfil;
}

public void setNombrePerfil(String nombrePerfil) {
this.nombrePerfil = nombrePerfil;
}

@Override
public String getAuthority() {
return this.nombrePerfil;
}

}


El modelo de dominio y el objeto de acceso a datos colaboran entre sí para conectarse y dar soporte a los datos de una base de datos Oracle que tiene la siguiente estructura e información:


create user springsecurity identified by springsecurity account unlock;
grant resource,connect to springsecurity;

create table springsecurity.usuario(
cod_usuario varchar2(40) not null,
des_password varchar2(40) not null,
in_cuenta_expirada char(1) default 'N' not null ,
in_cuenta_bloqueada char(1) default 'N' not null ,
in_credencial_expirada char(1) default 'N' not null ,
in_habilitado char(1) default 'S' not null ,
primary key(cod_usuario)
);

create table springsecurity.perfil(
cod_perfil varchar2(40) not null,
des_perfil varchar2(40) not null,
primary key(cod_perfil)
);

create table springsecurity.usuario_perfil(
cod_usuario varchar2(40),
cod_perfil varchar2(40),
primary key(cod_usuario,cod_perfil),
foreign key(cod_usuario) references springsecurity.usuario(cod_usuario),
foreign key(cod_perfil) references springsecurity.perfil(cod_perfil)
);

insert into springsecurity.perfil values('PERFIL_ADMINISTRADOR','PERFIL DE ADMINISTRADOR');
insert into springsecurity.perfil values('PERFIL_SUPERVISOR','PERFIL DE SUPERVISOR');
insert into springsecurity.usuario(cod_usuario,des_password) values('ccacique1','pass1');
insert into springsecurity.usuario(cod_usuario,des_password) values('ccacique2','pass2');
insert into springsecurity.usuario(cod_usuario,des_password) values('ccacique3','pass3');
insert into springsecurity.usuario(cod_usuario,des_password) values('ccacique4','pass4');
insert into springsecurity.usuario_perfil(cod_usuario,cod_perfil) values('ccacique1','PERFIL_ADMINISTRADOR');
insert into springsecurity.usuario_perfil(cod_usuario,cod_perfil) values('ccacique2','PERFIL_SUPERVISOR');



Con la estructura anterior ya podemos gestionar nuestros usuarios con soporte de spring security.


El contenedor de beans de spring security quedaría de la siguiente manera:



xmlns:b="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">


access-decision-manager-ref="accessDecisionManager">







login-page="/login.jsp"
default-target-url="/public/mostrarFormulario.htm"
authentication-failure-url="/loginerror.jsp"/>





invalidate-session="true"
logout-url="/public/terminarSesion.htm"
logout-success-url="/logout.jsp" />













































El proyecto completo te lo puedes bajar desde AQUI

sábado, 7 de agosto de 2010

Spring Security Mediante XML

Hola que tal, ahora les voy a presentar una pequeña implementación de spring security, el caso de uso a implementar es el siguiente:

Permitir que la aplicación calculadora(formulario simple de ingreso de operandos y selección de operación) sea accedida mediante usuarios que previamente se han autenticado o que tambien sea accedida en forma anónima al seleccionar una lista de opciones.

1. Lo primero que tenemos que hacer es configurar un filtro en el descriptor de despliegue de nuestra aplicación web de la siguiente manera:



xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">



calculadora-controller
org.springframework.web.servlet.DispatcherServlet
1




calculadora-controller
*.htm





springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy




springSecurityFilterChain
/*




org.springframework.web.context.ContextLoaderListener




contextConfigLocation

/WEB-INF/calculadora-security.xml





index.jsp




403
/sinacceso.jsp






2. Ahora vamos a configurar los beans que conforman el controller de la aplicación, mediante anotaciones. El flujo de control de la aplicación es simple: define un archivo de inicio index.jsp que redirecciona hacia /public/iniciar.htm está ruta será interceptada por el filtro de spring security y como la URL es pública no necesitará de una autenticación, después llegará hacia el controller: @Controller public class CalculadoraController {...} que ha mapeado la URL solicitada y la redirigirá hacia la página jsp opciones.jsp. En la página de opciones es donde se prueba la seguridad implementanda mediante spring, en esta página listo 5 opciones de acceso(todas me conducen al formulario de la calculadora), la primera opción accesa al formulario en forma anónima, las 4 siguientes requieren una autenticación, si el usuario aún no se a autenticado es redirigido hacia login.jsp para que se autentique, si el usuario ingresa sus credenciales(usuario y passwords) y genera un error entonces es redirigido hacia loginerror.jsp, si el usuario se autentica satisfactoriamente pero no tiene permiso entonces generará un error 403(acceso denegado, el cual es definido en el descriptor de despliegue)



xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">





p:basename="mensajes"/>

p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/>






3. Ahora lo más importante, la definición del contexto de seguridad mediante spring:



xmlns:b="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">


access-decision-manager-ref="accessDecisionManager">







login-page="/login.jsp"
default-target-url="/public/mostrarFormulario.htm"
authentication-failure-url="/loginerror.jsp"/>





invalidate-session="true"
logout-url="/public/terminarSesion.htm"
logout-success-url="/logout.jsp" />










































El proyecto completo te lo puedes bajar desde AQUI

martes, 27 de julio de 2010

Spring Web Services y MTOM Parte 2

Continuando con el post anterior ahora les voy a presentar el cliente del servicio web implementando mediante saaj y axiom integrado a spring. Veamos:

El cliente se despliega de la siguiente manera:

1. Maven ejecuta la clase principal CalculadoraClientWebServiceZip mediante un plugin:



org.codehaus.mojo
exec-maven-plugin
1.1.1



java




pe.com.slcsccy.pc.springws.client.CalculadoraClientWebServiceZip




2. La clase CalculadoraClientWebServiceZip crea los beans que se conectaran con el web service, este contenedor de beans se llama applicationContextZip.xml:



xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">









C:\\logs\spring.config



































Una vez creados los beans anteriores la aplicación cliente llamará al bean 'clienteWebServiceSaaj' y ejecutará el método enviarZip(nombreArchivo), este buscará en el sistema de archivos el archivo zip y lo enviará al web service, para poder hacer esto es necesario que previamente se hayan creado los proxies del web service mediante una llamada a la meta maven 'jaxb2:xjc', está meta buscará el schema XSD y generará las fuentes java, es decir realizará el binding entre el web service y su cliente. El plugin que implementa la meta maven se ejecuta como parte del proceso generate sources de maven, pero para evitar que eclipse me muestre errores de compilación la ejecuto antes:



org.codehaus.mojo
jaxb2-maven-plugin
1.3



xjc




src/main/resources
src/main/java
pe.com.slcsccy.pc.springws.client.schema
false




La clase que crea el mensaje de envío al web service y que recepciona su respuesta mediante Saaj es la siguiente:CalculadoraClientSaajMTOM.java


package pe.com.slcsccy.pc.springws.client;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;

import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
import org.springframework.ws.soap.saaj.SaajSoapMessageFactory;

import pe.com.slcsccy.pc.springws.client.schema.MensajeRequest;
import pe.com.slcsccy.pc.springws.client.schema.MensajeResponse;
import pe.com.slcsccy.pc.springws.client.schema.ObjectFactory;
import pe.com.slcsccy.pc.springws.client.schema.OperacionesZipType;

public class CalculadoraClientSaajMTOM extends WebServiceGatewaySupport {

private ObjectFactory objectFactory = new ObjectFactory();

public CalculadoraClientSaajMTOM(SaajSoapMessageFactory messageFactory) {
super(messageFactory);
}

public MensajeResponse enviarZip(String nombreArchivo){
MensajeRequest mensajeRequest = objectFactory.createMensajeRequest();
OperacionesZipType zipType = new OperacionesZipType();
zipType.setNombreArchivoZip(nombreArchivo);
DataHandler handler = new DataHandler(new FileDataSource(nombreArchivo));
zipType.setArchivoZip(handler);
mensajeRequest.setOperacionesZip(zipType);
Object respuesta = getWebServiceTemplate().marshalSendAndReceive(mensajeRequest);
return (MensajeResponse)respuesta;
}
}


La clase que realiza lo mismo que la anterior pero mediante Axiom es la siguiente:CalculadoraClientAxiomMTOM.java


package pe.com.slcsccy.pc.springws.client;

import java.io.IOException;
import java.util.Iterator;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.xml.namespace.QName;
import javax.xml.transform.TransformerException;

import org.apache.axiom.om.OMAttribute;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.OMOutputFormat;
import org.apache.axiom.om.OMText;
import org.apache.axiom.soap.SOAPBody;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axiom.soap.SOAPMessage;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.client.core.WebServiceMessageCallback;
import org.springframework.ws.client.core.WebServiceMessageExtractor;
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
import org.springframework.ws.soap.axiom.AxiomSoapMessage;
import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory;

public class CalculadoraClientAxiomMTOM extends WebServiceGatewaySupport {

public CalculadoraClientAxiomMTOM(AxiomSoapMessageFactory messageFactory) {
super(messageFactory);
}

public void enviarZip(final String nombreArchivo){

WebServiceMessageCallback requestCallback = new WebServiceMessageCallback() {
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
AxiomSoapMessage soapMessage = (AxiomSoapMessage) message;
SOAPMessage axiomMessage = soapMessage.getAxiomMessage();
SOAPFactory factory = (SOAPFactory) axiomMessage.getOMFactory();

//Armamos el contenido del mensaje:
SOAPBody cuerpoMensaje = axiomMessage.getSOAPEnvelope().getBody();
OMNamespace ns = factory.createOMNamespace("http://www.slcsccy.com.pe/pc/springws/calculadora", "calc");
OMElement mensajeRequestElement = factory.createOMElement("mensajeRequest",ns);
cuerpoMensaje.addChild(mensajeRequestElement);
OMElement operacionesZipElement = factory.createOMElement("operacionesZip", ns);

//Creo el archivo zip
OMAttribute nombreArchivoZipAttribute = factory.createOMAttribute("nombreArchivoZip",ns,nombreArchivo);
operacionesZipElement.addAttribute(nombreArchivoZipAttribute);
OMElement archivoZipElement = factory.createOMElement("archivoZip", ns);
DataSource dataSource = new FileDataSource(nombreArchivo);
DataHandler dataHandler = new DataHandler(dataSource);
OMText archivoZipText = factory.createOMText(dataHandler, true);
archivoZipElement.addChild(archivoZipText);
operacionesZipElement.addChild(archivoZipElement);
mensajeRequestElement.addChild(operacionesZipElement);

OMOutputFormat outputFormat = new OMOutputFormat();
outputFormat.setSOAP11(true);
outputFormat.setDoOptimize(true);
soapMessage.setOutputFormat(outputFormat);
}
};

WebServiceMessageExtractor responseExtractor = new WebServiceMessageExtractor() {
@SuppressWarnings("unchecked")
public Object extractData(WebServiceMessage message) throws IOException, TransformerException {
AxiomSoapMessage mensajeAxiom = (AxiomSoapMessage)message;
SOAPMessage mensaje = mensajeAxiom.getAxiomMessage();
SOAPEnvelope envelope = mensaje.getSOAPEnvelope();
SOAPBody body = envelope.getBody();

OMElement mensajeResponse = body.getFirstElement();
OMElement resultados = mensajeResponse.getFirstElement();

StringBuilder salida = new StringBuilder();
Iterator resultadosElement = resultados.getChildren();
QName atributoId = new QName("http://www.slcsccy.com.pe/pc/springws/calculadora","id");
while(resultadosElement.hasNext()){
OMElement elemento = resultadosElement.next();
salida.append("id:"+elemento.getAttributeValue(atributoId)+",valor:"+elemento.getText()+"\n");
}

logger.info("salida retorno:"+salida.toString());
return salida.toString();
}
};

@SuppressWarnings("unused")
String response = (String)getWebServiceTemplate().sendAndReceive(requestCallback,responseExtractor);
}
}



Una vez creado el proxy del web service ahora podemos conectarnos al web service y obtener los resultados esperados, veamos el log del cliente:


2010-07-27 23:35:27 (AbstractBeanFactory.java:241) - Returning cached instance of singleton bean 'clienteWebServiceSaaj'
2010-07-27 23:35:27 (WebServiceAccessor.java:110) - Opening [org.springframework.ws.transport.http.HttpUrlConnection@372a54] to [http://localhost:7001/springws/serviciosWeb]
2010-07-27 23:35:28 (WebServiceTemplate.java:581) - Sent request [SaajSoapMessage {http://www.slcsccy.com.pe/pc/springws/calculadora}mensajeRequest]
2010-07-27 23:35:35 (WebServiceTemplate.java:639) - Received response [SaajSoapMessage {http://www.slcsccy.com.pe/pc/springws/calculadora}mensajeResponse] for request [SaajSoapMessage {http://www.slcsccy.com.pe/pc/springws/calculadora}mensajeRequest]


Ahora veamos el log del web service:


2010-07-27 23:35:34 (WebServiceMessageReceiverObjectSupport.java:114) - Accepting incoming [org.springframework.ws.transport.http.HttpServletConnection@ea0dfc] to [http://localhost:7001/springws/serviciosWeb]
2010-07-27 23:35:34 (MessageDispatcher.java:167) - Received request [SaajSoapMessage {http://www.slcsccy.com.pe/pc/springws/calculadora}mensajeRequest]
2010-07-27 23:35:34 (AbstractMethodEndpointMapping.java:62) - Looking up endpoint for [{http://www.slcsccy.com.pe/pc/springws/calculadora}mensajeRequest]
2010-07-27 23:35:34 (MessageDispatcher.java:256) - Endpoint mapping [org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping@3311c0] maps request to endpoint [public pe.com.slcsccy.pc.springws.schema.MensajeResponse pe.com.slcsccy.pc.springws.ws.CalculadoraEndpointWithAnnotation.calcular(pe.com.slcsccy.pc.springws.schema.MensajeRequest) throws javax.xml.datatype.DatatypeConfigurationException,pe.com.slcsccy.pc.springws.exception.OperandoInvalidoException,pe.com.slcsccy.pc.springws.exception.UnzipException]
2010-07-27 23:35:34 (AbstractValidatingInterceptor.java:179) - Request message validated
2010-07-27 23:35:34 (AbstractLoggingInterceptor.java:160) - Request: UEsDBBQAAAAIAE26+TxJUMN0JgAAACgAAAAPAAAAb3BlcmFjaW9uZXMudHh0PcixCQAgEATB/OEa0RV8T9D+K/Mjs2EaOVlsRf+qS2ysGJxbr3hQSwECFAAUAAAACABNuvk8SVDDdCYAAAAoAAAADwAAAAAAAAAAACAAAAAAAAAAb3BlcmFjaW9uZXMudHh0UEsFBgAAAAABAAEAPQAAAFMAAAAAAA==
2010-07-27 23:35:34 (MessageDispatcher.java:277) - Testing endpoint adapter [org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter@1d33c09]
2010-07-27 23:35:35 (MarshallingMethodEndpointAdapter.java:146) - Unmarshalled payload request to [pe.com.slcsccy.pc.springws.schema.MensajeRequest@1ea9700]
2010-07-27 23:35:35 (AbstractBeanFactory.java:241) - Returning cached instance of singleton bean 'calculadoraEndpoint'
2010-07-27 23:35:35 (MarshallingMethodEndpointAdapter.java:153) - Marshalling [pe.com.slcsccy.pc.springws.schema.MensajeResponse@1fcbeda] to response payload
2010-07-27 23:35:35 (AbstractLoggingInterceptor.java:160) - Response: 8016108968
2010-07-27 23:35:35 (AbstractValidatingInterceptor.java:221) - Response message validated
2010-07-27 23:35:35 (MessageDispatcher.java:178) - Sent response [SaajSoapMessage {http://www.slcsccy.com.pe/pc/springws/calculadora}mensajeResponse] for request [SaajSoapMessage {http://www.slcsccy.com.pe/pc/springws/calculadora}mensajeRequest]
2010-07-27 23:35:35 (FrameworkServlet.java:677) - Successfully completed request


Con Axion el log en el cliente es el siguiente:


2010-07-27 23:43:35 (AbstractBeanFactory.java:241) - Returning cached instance of singleton bean 'lifecycleProcessor'
2010-07-27 23:43:35 (AbstractBeanFactory.java:241) - Returning cached instance of singleton bean 'clienteWebServiceAxiom'
2010-07-27 23:43:35 (WebServiceAccessor.java:110) - Opening [org.springframework.ws.transport.http.HttpUrlConnection@1f4d2b] to [http://localhost:7001/springws/serviciosWeb]
2010-07-27 23:43:35 (WebServiceTemplate.java:581) - Sent request [AxiomSoapMessage {http://www.slcsccy.com.pe/pc/springws/calculadora}mensajeRequest]
2010-07-27 23:43:35 (OMOutputFormat.java:140) - Start getContentType: OMOutputFormat [ mimeBoundary =null rootContentId=null doOptimize=true doingSWA=false isSOAP11=true charSetEncoding=null xmlVersion=null contentType=null ignoreXmlDeclaration=false autoCloseWriter=false actionProperty=null optimizedThreshold=0]
2010-07-27 23:43:35 (OMOutputFormat.java:166) - getContentType= {multipart/related; boundary="MIMEBoundary_be673ed239be738d78bcea194265e824bd3e5e2b92766546"; type="application/xop+xml"; start="<0.ae673ed239be738d78bcea194265e824bd3e5e2b92766546@apache.org>"; start-info="text/xml"} OMOutputFormat [ mimeBoundary =MIMEBoundary_be673ed239be738d78bcea194265e824bd3e5e2b92766546 rootContentId=0.ae673ed239be738d78bcea194265e824bd3e5e2b92766546@apache.org doOptimize=true doingSWA=false isSOAP11=true charSetEncoding=null xmlVersion=null contentType=text/xml ignoreXmlDeclaration=false autoCloseWriter=false actionProperty=null optimizedThreshold=0]
2010-07-27 23:43:35 (MTOMXMLStreamWriter.java:91) - OutputStream =class org.springframework.ws.transport.AbstractSenderConnection$RequestTransportOutputStream
2010-07-27 23:43:35 (MTOMXMLStreamWriter.java:92) - OMFormat = OMOutputFormat [ mimeBoundary =MIMEBoundary_be673ed239be738d78bcea194265e824bd3e5e2b92766546 rootContentId=0.ae673ed239be738d78bcea194265e824bd3e5e2b92766546@apache.org doOptimize=true doingSWA=false isSOAP11=true charSetEncoding=null xmlVersion=null contentType=text/xml ignoreXmlDeclaration=false autoCloseWriter=false actionProperty=null optimizedThreshold=0]
2010-07-27 23:43:35 (StAXUtils.java:615) - About to create XMLOutputFactory implementation with classloader=java.net.URLClassLoader@32d985
2010-07-27 23:43:35 (StAXUtils.java:617) - The classloader for javax.xml.stream.XMLOutputFactory is: null
2010-07-27 23:43:35 (StAXDialectDetector.java:219) - StAX implementation at jar:file:/I:/repo-maven/org/codehaus/woodstox/wstx-asl/3.2.9/wstx-asl-3.2.9.jar!/ is:
Title: WoodSToX XML-processor
Symbolic name: null
Vendor: woodstox.codehaus.org
Version: 3.2.9
2010-07-27 23:43:35 (StAXDialectDetector.java:181) - Detected StAX dialect: Woodstox
2010-07-27 23:43:35 (StAXUtils.java:635) - Created XMLOutputFactory = class org.apache.axiom.util.stax.wrapper.ImmutableXMLOutputFactory for classloader=java.net.URLClassLoader@32d985
2010-07-27 23:43:35 (StAXUtils.java:637) - Size of XMLOutputFactory map =1
2010-07-27 23:43:35 (StAXUtils.java:352) - XMLStreamWriter is org.apache.axiom.util.stax.dialect.WoodstoxStreamWriterWrapper
2010-07-27 23:43:35 (OptimizationPolicyImpl.java:60) - Start MTOMXMLStreamWriter.isOptimizedThreshold()
2010-07-27 23:43:35 (OptimizationPolicyImpl.java:65) - DataHandler fetched, starting optimized Threshold processing
2010-07-27 23:43:35 (OptimizationPolicyImpl.java:71) - node should be added to binart NodeList for optimization
2010-07-27 23:43:35 (MTOMXMLStreamWriter.java:182) - Calling MTOMXMLStreamWriter.flush
2010-07-27 23:43:35 (MTOMXMLStreamWriter.java:190) - The XML writing is completed. Now the attachments are written
2010-07-27 23:43:35 (MTOMXMLStreamWriter.java:172) - close
2010-07-27 23:43:36 (StAXSOAPModelBuilder.java:278) - Build the OMElement Envelope by the StaxSOAPModelBuilder
2010-07-27 23:43:36 (StAXSOAPModelBuilder.java:278) - Build the OMElement Header by the StaxSOAPModelBuilder
2010-07-27 23:43:36 (StAXSOAPModelBuilder.java:278) - Build the OMElement Body by the StaxSOAPModelBuilder
2010-07-27 23:43:36 (StAXSOAPModelBuilder.java:278) - Build the OMElement mensajeResponse by the StaxSOAPModelBuilder
2010-07-27 23:43:36 (WebServiceTemplate.java:639) - Received response [AxiomSoapMessage {http://www.slcsccy.com.pe/pc/springws/calculadora}mensajeResponse] for request [AxiomSoapMessage {http://www.slcsccy.com.pe/pc/springws/calculadora}mensajeRequest]
2010-07-27 23:43:36 (WebServiceTemplate.java:639) - Received response [AxiomSoapMessage {http://www.slcsccy.com.pe/pc/springws/calculadora}mensajeResponse] for request [AxiomSoapMessage {http://www.slcsccy.com.pe/pc/springws/calculadora}mensajeRequest]
2010-07-27 23:43:36 (StAXSOAPModelBuilder.java:278) - Build the OMElement resultados by the StaxSOAPModelBuilder
2010-07-27 23:43:36 (StAXSOAPModelBuilder.java:278) - Build the OMElement resultado by the StaxSOAPModelBuilder
2010-07-27 23:43:36 (StAXSOAPModelBuilder.java:278) - Build the OMElement resultado by the StaxSOAPModelBuilder
2010-07-27 23:43:36 (StAXSOAPModelBuilder.java:278) - Build the OMElement resultado by the StaxSOAPModelBuilder
2010-07-27 23:43:36 (StAXSOAPModelBuilder.java:278) - Build the OMElement resultado by the StaxSOAPModelBuilder


El proyecto completo en eclipse te lo puedes bajar desde AQUI

lunes, 26 de julio de 2010

Spring Web Services y MTOM Parte 1

Hola que tal hace tiempo que no posteaba, así que decidí hacerlo con un nuevo tema, el de los web services, el tema tiene que ver con la arquitectura orientada a servicios que estoy analizando desde hace un par de meses así que para recordar algo de los web services decidí implementar uno con spring web services; allí les va la definición simple del caso de uso que implementé:

CUS: Calcular operaciones matemáticas.

Descripción breve del CUS: El CUS permitirá al usuario del web service calcular operaciones matemáticas de suma, resta, multiplicación y división, la información enviada al servicio web podrá estar en formato XML o en un archivo de texto comprimido en formato zip que el web service procesará y generará la información en formato XML.

Un ejemplo de mensaje XML enviado al servidor será:



xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">



12
13




22
5







Cuando implementamos web services lo podemos hacer mediante un servlet o un enterprise java bean(EJB) en este caso como estoy usando spring la implementación será con un servlet, por lo que nuestra aplicación será un empaquetado WAR(Web ARchive), ahora una vez definido como será deployado el web service, debemos definir como será diseñado, el diseño de los web services puede realizarse de dos maneras:

(1)Primero escribimos código java que implemente el web service y una vez terminado de codificado, lo deployamos en nuestro servidor de aplicaciones favorito y es éste ultimo quien creará la descripción de nuestro servicio web inspeccionando las clases anotadas con @WebService. Este tipo de diseño se conoce como 'JAVA to WSDL' ó primero java.

(2)Primero escribimos el contrato del web service, el contrato en toda especificación de componente de software es la descripción detallada de mensajes intercambiados entre este componente y su entorno, en el caso de los web services el contrato o especificación es el descriptor del servicio web(WSDL). Este tipo de diseño se conoce como 'WSDL to JAVA' ó primero el contrato.


Como voy a usar spring para implementar mi web service lo primero que tengo que hacer es definir el formato de los mensajes aceptados por mi web service así como el formato de los mensajes de respuesta de mi web service. Esta definición la realizo en un schema XSD:




elementFormDefault="qualified" attributeFormDefault="qualified"
targetNamespace="http://www.slcsccy.com.pe/pc/springws/calculadora"
xmlns:calc="http://www.slcsccy.com.pe/pc/springws/calculadora"
xmlns:xmime="http://www.w3.org/2005/05/xmlmime">












maxOccurs="unbounded" />






































type="xs:decimal" />


























El schema anterior me define los mensajes de entrada al web service y el formato de mensaje de respuesta.


Ahora veamos el descriptor de despliegue de la aplicación web:



xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

Servicio web simple que implementa una calculadora


calculadora-annotation
org.springframework.ws.transport.http.MessageDispatcherServlet

transformWsdlLocations
true




calculadora-annotation
/*




El descriptor anterior define el servlet calculadora-annotation-servlet.xml en forma implicita, dicho contenedor de beans es el siguiente:




xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xmlns:sws="http://www.springframework.org/schema/web-services"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-1.5.xsd
http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-1.5.xsd">







C:\\logs\spring.config





class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">















class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">






































A continuación veamos la clase principal que implementa el web service mediante anotaciones de spring:



package pe.com.slcsccy.pc.springws.ws;

import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipInputStream;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.xml.datatype.DatatypeConfigurationException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;

import pe.com.slcsccy.pc.springws.domain.Operacion;
import pe.com.slcsccy.pc.springws.exception.OperandoInvalidoException;
import pe.com.slcsccy.pc.springws.exception.UnzipException;
import pe.com.slcsccy.pc.springws.schema.MensajeRequest;
import pe.com.slcsccy.pc.springws.schema.MensajeResponse;
import pe.com.slcsccy.pc.springws.schema.ObjectFactory;
import pe.com.slcsccy.pc.springws.schema.OperacionType;
import pe.com.slcsccy.pc.springws.schema.OperacionesZipType;
import pe.com.slcsccy.pc.springws.schema.ResultadoType;
import pe.com.slcsccy.pc.springws.schema.MensajeResponse.Resultados;
import pe.com.slcsccy.pc.springws.service.CalculadoraService;

@Endpoint
public class CalculadoraEndpointWithAnnotation {

private final Log logger = LogFactory.getLog(CalculadoraEndpointWithAnnotation.class);
public static final String NAMESPACE_URI = "http://www.slcsccy.com.pe/pc/springws/calculadora";
public static final String REQUEST_LOCAL_NAME = "mensajeRequest";
public static final String RESPONSE_LOCAL_NAME = "mensajeResponse";
private ObjectFactory objectFactory = new ObjectFactory();

private CalculadoraService calculadoraService;


/**
* Antes de la ejecución de este método la declaración:
* <sws:marshalling-endpoints marshaller="formador" unmarshaller="formador"/>
* se a encargado de crear el arbol de objetos desde un archivo xml, hay
* que recordar que el servicio web recibe XML y que es spring integrado con
* JAXB quién realiza el proceso de unmarshalling y marshalling para crear el
* arbol de objetos(MensajeRequest) y el formato de respuesta respectivamente.
* */
@PayloadRoot(localPart = REQUEST_LOCAL_NAME, namespace = NAMESPACE_URI)
public MensajeResponse calcular(MensajeRequest request)
throws DatatypeConfigurationException,OperandoInvalidoException,UnzipException {
logger.info("Mensaje recibido.");

//Empiezo a crear el objeto de respuesta
MensajeResponse response = objectFactory.createMensajeResponse();
Resultados resultados = objectFactory.createMensajeResponseResultados();
response.setResultados(resultados);

//Si el cliente del servicio web envió las operaciones en formato XML
if(request.getOperaciones()!=null){
List<OperacionType> operaciones = request.getOperaciones().getOperacion();
for(OperacionType operacion:operaciones){
if( operacion.getOperador().equals("/") &&
operacion.getOperandos().getOperando().get(1).equals(BigDecimal.ZERO))
throw new OperandoInvalidoException("No se puede dividir por cero.");

BigDecimal calculo = calculadoraService.calcular(operacion.getOperandos().getOperando(), operacion.getOperador());
ResultadoType resultado = objectFactory.createResultadoType();
resultado.setId(operacion.getId());//Copio los identificadores.
resultado.setValue(calculo);
resultados.getResultado().add(resultado);
logger.info("Respuesta calculada en el server:"+calculo.toPlainString());
}
}
else{
//Si no me enviaron las operaciones en formato XML, entonces me las
//enviaron en un archivo comprimido en formato zip:
OperacionesZipType operacionesZip = request.getOperacionesZip();
String archivoAsString = unzipOperaciones(operacionesZip.getArchivoZip());
List<Operacion> operacionesDomain = crearOperaciones(archivoAsString);
int idSecuencial = 1;
for(Operacion operacion:operacionesDomain){
BigDecimal calculo = calculadoraService.calcular(operacion.getOperandos(), operacion.getOperador());
ResultadoType resultado = objectFactory.createResultadoType();
resultado.setId(String.valueOf(idSecuencial++));
resultado.setValue(calculo);
resultados.getResultado().add(resultado);
logger.info("Respuesta calculada en el server:"+calculo.toPlainString());
}
}
return response;
}


/**
* Descomprime el archivo zip en una cadena
* */
private String unzipOperaciones(DataHandler handlerZip)throws UnzipException{
DataSource dataSource = null;
ZipInputStream zis = null;
String archivoAsString = null;
try{
dataSource = handlerZip.getDataSource();
zis = new ZipInputStream(dataSource.getInputStream());
if(zis.getNextEntry() != null){
int bytesLeidos = 0;
int BUFFER_SIZE = 1024*10;
byte buffer[] = new byte[BUFFER_SIZE];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while( (bytesLeidos = zis.read(buffer,0,BUFFER_SIZE)) != -1 ){
bos.write( buffer, 0, bytesLeidos);
}
archivoAsString = new String(bos.toByteArray());
}
zis.close();
}catch(Exception e){
logger.info("Error al leer el zip.",e);
throw new UnzipException("Error al leer el zip.");
}finally{
}
return archivoAsString;
}


/**
* Crea una lista de operaciones a partir de un cadena de texto.
* */
private List<Operacion> crearOperaciones(String archivoAsString){
Operacion operacion = null;
List<Operacion> operaciones = new ArrayList<Operacion>();
String[] lineas = archivoAsString.split("\n");
for(int i=0;i<lineas.length;i++){
logger.info("Linea a analizar:"+lineas[i]);
String[] operacionToken = lineas[i].split(",");
List<BigDecimal> operandos = new ArrayList<BigDecimal>();
for(int j=1;j<operacionToken.length;j++){
logger.info("Operador a procesar:"+operacionToken[j]);
operandos.add(new BigDecimal(operacionToken[j].trim()));
}
operacion = new Operacion(operacionToken[0],operandos);
operaciones.add(operacion);
}
return operaciones;
}


public CalculadoraService getCalculadoraService() {
return calculadoraService;
}


public void setCalculadoraService(CalculadoraService calculadoraService) {
this.calculadoraService = calculadoraService;
}
}


Como siempre para que el desarrollo sea más eficiente, generar fuentes con JAXB, compilar y deployar a weblogic uso MAVEN, el descriptor del proyecto es el siguiente:


xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4.0.0
pe.com.slcsccy.pc
springws
war
0.0.1-SNAPSHOT
Aplicación de ejemplo básica de spring web services
http://maven.apache.org


2.9.1
t3
false
true
true
false
false
true
localhost
7001
ccacique
xxx
AdminServer
true




jdk14-jdk15

!1.6



javax.xml.stream
stax-api


javax.xml.soap
saaj-api







junit
junit
3.8.1
test


org.springframework.ws
spring-ws-core
2.0.0-M1


javax.xml.bind
jaxb-api
2.0



com.sun.xml.bind
jaxb-impl
2.0.3



log4j
log4j
1.2.16



springws


org.codehaus.mojo
jaxb2-maven-plugin
1.3



xjc




src/main/webapp/WEB-INF
src/main/java
pe.com.slcsccy.pc.springws.schema
false



org.apache.maven.plugins
maven-compiler-plugin

1.6
1.6



org.codehaus.mojo
weblogic-maven-plugin
${weblogic-maven-plugin.version}

${weblogic-maven-plugin.upload}
${weblogic-maven-plugin.verbose}
${weblogic-maven-plugin.debug}
${weblogic-maven-plugin.exploded}
${weblogic-maven-plugin.noExit}
${weblogic-maven-plugin.continueOnFailure}
${weblogic-maven-plugin.adminServerHostName}
${weblogic-maven-plugin.adminServerPort}
${weblogic-maven-plugin.adminServerProtocol}
${weblogic-maven-plugin.userId}
${weblogic-maven-plugin.password}
${weblogic-maven-plugin.remote}
${weblogic-maven-plugin.targetNames}



weblogic
wlfullclient
10.3


com.bea
com.bea.core.descriptor.wl
1.1.0.0








En el siguiente post expondré los clientes del web service que envía y reciben las respuestas del web service.


El proyecto completo en eclipse te lo puedes bajar desde AQUI

lunes, 24 de mayo de 2010

PLSQL Básico2

Más ejemplos sobre PLSQL, en este post veamos como podemos crear procedimientos que utilicen arrays,collections y estructuras de datos derivadas de estos

Ejemplo 1: Muestra el uso de arrays asociativos
Definimos un tipo de la forma: TYPE miHashType IS TABLE OF NUMBER INDEX BY VARCHAR2(6);
La clausula TYPE especifica que vamos a definir un nuevo tipo.
El código miHashType es el nombre del nuevo tipo, es como una nueva clase
La clausula IS TABLE OF NUMBER indica que el arreglo asociativo va a tener valores de tipo NUMBER
La clausula BY VARCHAR2(5) indica que los valores de los indices van a ser del tipo VARCHAR2(6)


CREATE OR REPLACE PROCEDURE collection01 IS

TYPE miHashType IS TABLE OF NUMBER INDEX BY VARCHAR2(6);
arreglo1 miHashType;
arreglo2 miHashType;

TYPE miEmployeeType IS TABLE OF employees%rowtype INDEX BY PLS_INTEGER;
empleadosArray miEmployeeType;

-- Los tipos anidados pueden almacenar un número ilimitado de elementos
TYPE miTableType IS TABLE OF VARCHAR2(20);
arregloV1 miTableType;
arregloV2 miTableType;

-- Los tipos varray tienen un número máximo de elementos
TYPE miArregloType IS VARRAY(4) OF VARCHAR2(10);
varreglo1 miArregloType;
varreglo2 miArregloType;

BEGIN
-- Agregamos 3 entradas al primer arreglo:
arreglo1('clave1') := 666;
arreglo1('clave2') := 667;
arreglo1('clave3') := 668;

-- Agregamos 3 entradas al primer arreglo:
arreglo2('clave1') := 100;
arreglo2('clave2') := 101;
arreglo2('clave3') := 102;
arreglo2('clave4') := 103;
arreglo2('clave5') := 104;
arreglo2('clave6') := 105;

-- Ahora mostramos los valores deacuerdo a su clave:
dbms_output.put_line('El valor del arreglo asociativo arreglo2(''clave2''):'||arreglo2('clave2'));

-- Obtenemos los datos de un empleado:
SELECT * INTO empleadosArray(100) FROM employees WHERE employee_id = 100;
dbms_output.put_line('Nombre del empleado: '||empleadosArray(100).first_name);
dbms_output.put_line('Apellidos del empleado: '||empleadosArray(100).last_name);
dbms_output.put_line('Email del empleado: '||empleadosArray(100).email);

-- Ahora asignamos los valores a los arreglos:
arregloV1 := miTableType('carlos','alberto','cacique','yupanqui','certificado','en','java','y','oracle');
arregloV2 := miTableType('zaida','angelica');
varreglo1 := miArregloType('uno','dos');
-- Con el metodo EXTEND extendemos el arreglo dinamico, el primer argument
-- especifica cuantos elementos más creamos y el segundo el valor que se va a
-- copiar de los elementos existentes a los elementos nuevos
varreglo1.EXTEND(2,1);
--varreglo1(3) := 'tres';
--varreglo1(4) := 'cuatro';
varreglo2 := miArregloType('primero','segundo','tercero','cuarto');

-- Ahora mostramos los valores de los tipos anidados;
FOR i IN arregloV1.FIRST..arregloV1.LAST LOOP
dbms_output.put_line('arregloV1('||i||'):'||arregloV1(i));
END LOOP;
FOR i IN arregloV2.FIRST..arregloV2.LAST LOOP
dbms_output.put_line('arregloV2('||i||'):'||arregloV2(i));
END LOOP;


-- Ahora mostramos los valores de los tipos varrays
FOR i IN varreglo1.FIRST..varreglo1.LAST LOOP
dbms_output.put_line('varreglo1('||i||'):'||varreglo1(i));
END LOOP;
FOR i IN varreglo2.FIRST..varreglo2.LAST LOOP
dbms_output.put_line('varreglo2('||i||'):'||varreglo2(i));
END LOOP;

END collection01;


Ejemplo 2:
Ahora veremos como declarar un tipo complejo con TYPE... IS RECORD, despues creamos un tipo anidado con el tipo creado anteriormente, una vez creado el tipo anidado, creamos una variable del tipo anidado, todo esto para poder mantener los datos que más tarde nos retornará un cursor.
Después declaramos un tipo cursor y creamos una variable, más adelante en el cuerpo del procedimiento abrimos el cursor para un SELECT específico y cargamos la variable coleccion con los datos del cursor por medio de la clausula FETCH <cursor> BULK COLLECTION INTO <coleccion>, al final del procedimiento solo mostramos los datos pero desde la colección.


CREATE OR REPLACE PROCEDURE collection02 IS

-- Declaramos un tipo de dato complejo
TYPE emp_name_rec IS RECORD (
firstname employees.first_name%TYPE,
lastname employees.last_name%TYPE,
hiredate employees.hire_date%TYPE
);

-- Tipo Tabla que puede mantener información acerca de los empleados
TYPE EmpList_tab IS TABLE OF emp_name_rec;
SeniorSalespeople EmpList_tab;

-- Declaramos una referencia a un cursor para seleccionar un subconjunto de columnas.
EndCounter NUMBER := 10;
TYPE EmpCurTyp IS REF CURSOR;
emp_cv EmpCurTyp;

BEGIN
-- Abrimos el cursor con la sentencia SELECT indicada
OPEN emp_cv FOR SELECT first_name, last_name, hire_date FROM employees
WHERE job_id = 'SA_REP' ORDER BY hire_date;

-- Sacamos los datos del cursor y los colocamos dentro de la coleccion
FETCH emp_cv BULK COLLECT INTO SeniorSalespeople;

-- Cerramos el cursor
CLOSE emp_cv;

-- Para este ejemplo mostramos un máximo de 10 empleados
IF SeniorSalespeople.LAST > 0 THEN
IF SeniorSalespeople.LAST < 10 THEN EndCounter := SeniorSalespeople.LAST;
END IF;
FOR i in 1..EndCounter LOOP
DBMS_OUTPUT.PUT_LINE(SeniorSalespeople(i).lastname || ', '
|| SeniorSalespeople(i).firstname || ', ' || SeniorSalespeople(i).hiredate);
END LOOP;
END IF;
END collection02;


Ejemplo 3: En este ejemplo se muestra la eficiencia de la clausula FORALL a diferencia de la clausula FOR LOOP con tipos anidados:


CREATE OR REPLACE PROCEDURE COLLECTION03 IS
-- Definimos des tipos de datos anidados
TYPE NumTab IS TABLE OF parts1.pnum%TYPE INDEX BY PLS_INTEGER;
TYPE NameTab IS TABLE OF parts1.pname%TYPE INDEX BY PLS_INTEGER;
-- Declaramos variables de los tipos definidos anteriormente
pnums NumTab;
pnames NameTab;
-- Cuantas veces vamos a iterar
iterations CONSTANT PLS_INTEGER := 10000;
-- Variables que nos van a ayudar a medir el tiempo
t1 INTEGER;
t2 INTEGER;
t3 INTEGER;
BEGIN
FOR j IN 1..iterations LOOP -- load index-by tables
pnums(j) := j;
pnames(j) := 'Part No. ' || TO_CHAR(j);
END LOOP;
t1 := DBMS_UTILITY.get_time;
FOR i IN 1..iterations LOOP -- use FOR loop
INSERT INTO parts1 VALUES (pnums(i), pnames(i));
END LOOP;
t2 := DBMS_UTILITY.get_time;
FORALL i IN 1..iterations -- use FORALL statement
INSERT INTO parts2 VALUES (pnums(i), pnames(i));
t3 := DBMS_UTILITY.get_time;
DBMS_OUTPUT.PUT_LINE('Tiempo de ejecución en segundos');
DBMS_OUTPUT.PUT_LINE('---------------------');
DBMS_OUTPUT.PUT_LINE('FOR LOOP: ' || TO_CHAR((t2 - t1)));
DBMS_OUTPUT.PUT_LINE('FORALL: ' || TO_CHAR((t3 - t2)));
COMMIT;
END COLLECTION03;


Ejemplo 4: Ejemplo de uso de operaciones de comparacion con operadores SET:


CREATE OR REPLACE PROCEDURE COLLECTION04 IS

TYPE nested_typ IS TABLE OF NUMBER;
nt1 nested_typ := nested_typ(1,2,3);
nt2 nested_typ := nested_typ(3,2,1);
nt3 nested_typ := nested_typ(2,3,1,3);
nt4 nested_typ := nested_typ(1,2,4);
respuesta BOOLEAN;
cantidad NUMBER;

-- Definimos un procedimiento que mostrará una cadena de caracteres y una cantidad
PROCEDURE imprimir(truth BOOLEAN DEFAULT NULL, quantity NUMBER DEFAULT NULL) IS
BEGIN
IF truth IS NOT NULL THEN
DBMS_OUTPUT.PUT_LINE(CASE truth WHEN TRUE THEN 'True' WHEN FALSE THEN 'False' END);
END IF;
IF quantity IS NOT NULL THEN
DBMS_OUTPUT.PUT_LINE(quantity);
END IF;
END;
BEGIN
-- La variable anidada nt1 se encuentra en el subconjunto especificado por IN?
-- Sí, nt1 coincide con nt2 que se encuentra en la clausula IN
respuesta := nt1 IN (nt2,nt3,nt4);
imprimir(truth => respuesta);

-- La variable anidada nt1 es un subconjunto de la variable nt3
-- Sí, todos los elementos coinciden
respuesta := nt1 SUBMULTISET OF nt3;
imprimir(truth => respuesta);

-- La variable anidada no es un subconjunto de la variable anidada nt4?
-- Sí, la variable NO lo es.
respuesta := nt1 NOT SUBMULTISET OF nt4;
imprimir(truth => respuesta);

-- CARDINALITY retorna el numero de elementos de la variable anidada nt3
cantidad := CARDINALITY(nt3);
imprimir(quantity => cantidad);

-- CARDINALITY(SET(?)) muestra el número de elementos distintos en la variable
cantidad := CARDINALITY(SET(nt3));
imprimir(quantity => cantidad);

-- El elemento con valor 4 es un miembro de la variable nt1?
-- No, ese elemento no existe en la variable anidada.
respuesta := 4 MEMBER OF nt1;
imprimir(truth => respuesta);

-- La variable nt3 es un SET, es decir no tiene duplicados?
-- NO, la variable tiene duplicados, por lo tanto no es un SET
respuesta := nt3 IS A SET;
imprimir(truth => respuesta);

-- La variable nt3 no es un SET?
-- Sí dicha variable no es un SET porque contiene elementos duplicados.
respuesta := nt3 IS NOT A SET;
imprimir(truth => respuesta);

-- La variable nt1 no tiene elementos?
-- falso, dicha variable si tiene elementos.
respuesta := nt1 IS EMPTY;
imprimir(truth => respuesta);

END COLLECTION05;


Ejemplo 5: Operaciones con los diferentes métodos implícitos de la colecciones.

  • lista.EXIST(n), lista, es una variable de tipo anidada o varray y n es el indice de un elemento en la lista
  • lista.COUNT, retorna el número de elementos que la lista actualmente contiene.
  • lista.LIMIT, retorna el tamaño máximo de la tabla anidada.
  • lista.TRIM, remueve un elemento del final de la lista, si le especificasmo un argumentoentonces removerá la cantidad especificada.



CREATE OR REPLACE PROCEDURE COLLECTION05 IS

-- Definimos un tipo anidado de enteros
TYPE listaNumerosType IS TABLE OF INTEGER;
lista listaNumerosType := listaNumerosType(1,3,5,7);

BEGIN
dbms_output.put_line('Cantidad de elementos de la lista 1:'||lista.COUNT);
-- Borramos el segundo elemento:
lista.DELETE(2);
dbms_output.put_line('Cantidad de elementos de la lista 2:'||lista.COUNT);

-- Si en la lista existe el elemento con indice 1 entonces hacer:
IF lista.EXISTS(1) THEN
DBMS_OUTPUT.PUT_LINE('El element #1 existe.');
END IF;
-- Si en la lista no existe el elemento con indice 2 entonces hacer
IF lista.EXISTS(2) = FALSE THEN
DBMS_OUTPUT.PUT_LINE('El elemento #2 ha sido borrado.');
END IF;

-- Agregamos un par de elementos más a la lista y volvemos a hacer COUNT
lista.EXTEND(5);
lista(5) := 9;
dbms_output.put_line('Cantidad de elementos de la lista 3:'||lista.COUNT);
dbms_output.put_line('Limite de elementos de la lista :'||lista.LIMIT);
-- Removera 2 elementos del final de la coleccion, ojo que TRIM solo removerá uno
lista.TRIM(2);
dbms_output.put_line('Cantidad de elementos de la lista despues de TRIM:'||lista.COUNT);
dbms_output.put_line('Limite de elementos de la lista despues de TRIM:'||lista.LIMIT);

-- Usando los metodos first, prior, next y last
dbms_output.put_line('lista.FIRST:'||lista.FIRST);
dbms_output.put_line('lista.LAST:'||lista.LAST);
dbms_output.put_line('lista.PRIOR(2):'||lista.PRIOR(2));
dbms_output.put_line('lista.NEXT(2):'||lista.NEXT(2));

END COLLECTION05;

PLSQL Básico1

Encontré varios ejemplos de hace varios años cuando aprendía plsql, allí les dejo algunos ejemplitos, ojalá les sirvan:

Ejemplo 1: Lo unico que hace es mostrar una linea en consola.


CREATE OR REPLACE PROCEDURE proc01 IS
BEGIN
dbms_output.put_line('Hola mundo con los procedimientos de Oracle');
END;


Ejemplo 2: Igual que el anterior pero ahora la ultima línea finaliza con el nombre del procedimiento... buena practica!


CREATE OR REPLACE PROCEDURE proc02 IS
BEGIN
dbms_output.put_line('Otro procedimiento pero ahora con otro texto');
END proc02;


Ejemplo 3: Uso de la clausula %TYPE y %ROWTYPE, la clausula %TYPE sirve para asignar los tipos de datos heredados de otras variables en tiempo de ejecución lo mismo para %ROWTYPE que actua a nivel de fila


CREATE OR REPLACE PROCEDURE proc03 IS
v_fecha_sistema DATE;
v_fecha_contratacion v_fecha_sistema%TYPE;
v_salario employees.salary%TYPE;
reg_empleados employees%ROWTYPE;
BEGIN
SELECT sysdate INTO v_fecha_sistema FROM dual;
SELECT * INTO reg_empleados FROM employees WHERE employee_id=200;
v_salario := reg_empleados.salary;
v_fecha_contratacion := reg_empleados.hire_date;
dbms_output.put_line('El nombre del empleado es:'||reg_empleados.first_name);
dbms_output.put_line('El salario del empleado es:'||v_salario);
dbms_output.put_line('La fecha de contratación del empleado es:'||v_fecha_contratacion);
dbms_output.put_line('La fecha del sistema es:'||v_fecha_sistema);
END proc03;


Ejemplo 4: Muestra el uso de etiquetas para resolucion de nombres de variables, analiza el ámbito de la variable v_cantidad


CREATE OR REPLACE PROCEDURE proc04 IS
BEGIN
<<ambito_externo>>
DECLARE
v_cantidad NUMBER := 100;
BEGIN
DECLARE
v_cantidad NUMBER := 150;
BEGIN
-- Inicio de un bloque de codigo interno
dbms_output.put_line('Valor de la variable interna:'||v_cantidad);
dbms_output.put_line('Valor de la variable externa:'||ambito_externo.v_cantidad);
END;
-- Aqui podemos continuar con los datos de la variable etiquetada externamente
dbms_output.put_line('Valor de la variable:'||v_cantidad);
END;
END proc04;


Ejemplo 5: Podemos crear funciones dentro del procedimiento y usarlas en el mismo procedimiento.


CREATE OR REPLACE PROCEDURE proc05 IS

-- Funcion sumar:
FUNCTION sumar(ope1 IN NUMBER,ope2 IN NUMBER) RETURN NUMBER IS
BEGIN
RETURN ope1+ope2;
END sumar;

-- Funcion restar:
FUNCTION restar(ope1 IN NUMBER,ope2 IN NUMBER) RETURN NUMBER IS
BEGIN
RETURN ope1-ope2;
END restar;

-- Funcion multiplicar:
FUNCTION multiplicar(ope1 IN NUMBER,ope2 IN NUMBER) RETURN NUMBER IS
BEGIN
RETURN ope1*ope2;
END multiplicar;

-- Funcion dividir:
FUNCTION dividir(ope1 IN NUMBER,ope2 IN NUMBER) RETURN NUMBER IS
BEGIN
RETURN ope1/ope2;
END dividir;

BEGIN
dbms_output.put_line('Suma de 15 y 45: '||sumar(15,45));
dbms_output.put_line('Resta de 15 y 45: '||restar(15,45));
dbms_output.put_line('Multiplicacion de 15 y 45: '||multiplicar(15,45));
dbms_output.put_line('Division de 15 y 45: '||dividir(15,45));
END proc05;


Ejemplo 6: Muestra el uso de las estructuras de control condicional IF-THEN e IF-THEN-ELSE


CREATE OR REPLACE
PROCEDURE proc06(vi_cantidad_minima IN INTEGER) IS
v_sueldo_base NUMBER := 4000;
v_cantidad_empleados INTEGER := 0;
BEGIN
--Obtenemos la cantidad de empleados que tienen un sueldo mayor al sueldo base
SELECT COUNT(employee_id) INTO v_cantidad_empleados FROM employees
WHERE salary > v_sueldo_base;

-- Testeamos si la cantidad de empleados es mayor a una constante
dbms_output.put_line('La cantidad de empleados es:'||v_cantidad_empleados);
dbms_output.put_line('-----------------------------------------------------');
dbms_output.put_line('Uso de las estructura if-then');
IF v_cantidad_empleados > vi_cantidad_minima THEN
dbms_output.put_line('La cantidad de empleados:'||v_cantidad_empleados||', ES MAYOR a la minima');
END IF;

dbms_output.put_line('Uso de las estructura if-then-else');
IF v_cantidad_empleados > vi_cantidad_minima THEN
dbms_output.put_line('La cantidad de empleados:'||v_cantidad_empleados||', ES MAYOR a la minima');
ELSE
dbms_output.put_line('La cantidad de empleados:'||v_cantidad_empleados||', NO ES MAYOR a la minima');
END IF;

END proc06;


Ejemplo 7: Muestra el uso de las estructuras de control iterativas LOOP y EXIT


CREATE OR REPLACE
PROCEDURE proc07(vi_cantidad_iteraciones IN INTEGER) IS
v_contador1 INTEGER := 0;
v_contador2 INTEGER := 0;
BEGIN
-- Primero con una declaración EXIT para salir de un bucle.
dbms_output.put_line('Iteracion con clausula EXIT');
LOOP
IF v_contador1 = vi_cantidad_iteraciones THEN
EXIT;
ELSE
dbms_output.put_line('Iterando con EXIT');
END IF;
v_contador1 := v_contador1 + 1;
END LOOP;

--Ahora iteramos pero con la clausula EXIT WHEN
v_contador1 := 0;
dbms_output.put_line('Iteracion con la clausula EXIT WHEN');
LOOP
v_contador1 := v_contador1 + 1;
dbms_output.put_line('Iterando con EXIT WHEN');
EXIT WHEN v_contador1 >= 20;
END LOOP;

--Ahora iteramos con la clausula EXIT WHEN con etiquetas
v_contador1 := 0;
v_contador2 := 0;
dbms_output.put_line('Iteracion con la clausula EXIT WHEN CON ETIQUETAS');
<<externo>>
LOOP
v_contador1 := v_contador1 + 1;
dbms_output.put_line('Iterando con EXIT WHEN');
v_contador2 := 0;
<<interno>>
LOOP
v_contador2 := v_contador2 + 1;
dbms_output.put_line('Iterando con EXIT WHEN ANIDADO');
EXIT externo WHEN (v_contador1 = 2 AND v_contador2 = 3);
EXIT interno WHEN v_contador2 >= 3;
END LOOP;
EXIT externo WHEN v_contador1 >= 3;
END LOOP;

END proc07;


Ejemplo 8: Muestra el uso de estructuras de iteracion WHILE-LOOP-END LOOP con EXIT y etiquetas


CREATE OR REPLACE
PROCEDURE proc08(vi_cantidad_iteraciones IN INTEGER) IS
v_contador1 INTEGER := 0;
v_contador2 INTEGER := 0;
v_contador3 INTEGER := 0;
BEGIN
--Ahora iteramos con la clausula EXIT WHEN con etiquetas
v_contador1 := 0;
v_contador2 := 0;
dbms_output.put_line('Iteracion con la clausula EXIT WHEN CON ETIQUETAS');
<<externo>>
LOOP
v_contador1 := v_contador1 + 1;
dbms_output.put_line('Iterando con EXIT WHEN');
v_contador2 := 0;
<<interno>>
LOOP
v_contador2 := v_contador2 + 1;
dbms_output.put_line('Iterando con EXIT WHEN ANIDADO');
v_contador3 := 0;
WHILE (v_contador3 < 5 ) LOOP
v_contador3 := v_contador3 + 1;
dbms_output.put_line('Iterando con EXIT WHEN ANIDADO CON WHILE-LOOP-END LOOP');
END LOOP;
EXIT externo WHEN (v_contador1 = 2 AND v_contador2 = 3);
EXIT interno WHEN v_contador2 >= 3;
END LOOP;
EXIT externo WHEN v_contador1 >= 3;
END LOOP;
END proc08;