http://static.springsource.org/downloads/nightly/snapshot-download.php?project=SPR
http://static.springsource.org/downloads/nightly/release-download.php?project=SPR
http://static.springsource.org/downloads/nightly/snapshot-download.php?project=SPR
http://static.springsource.org/downloads/nightly/release-download.php?project=SPR
xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:local="*"
creationComplete="init()">
import mx.core.UIComponent;
import mx.events.ResizeEvent;
import org.alivepdf.display.Display;
import org.alivepdf.fonts.CoreFont;
import org.alivepdf.fonts.FontFamily;
import org.alivepdf.layout.Layout;
import org.alivepdf.layout.Orientation;
import org.alivepdf.layout.Size;
import org.alivepdf.layout.Unit;
import org.alivepdf.pages.Page;
import org.alivepdf.pdf.PDF;
import org.alivepdf.saving.Method;
private var pdfViewer: HTMLLoader;
private function init(): void {
// Creamos un PDF con AlivePDF desde el mismo Flex
var pdf: PDF = new PDF(Orientation.PORTRAIT, Unit.MM, Size.A4);
pdf.setDisplayMode(Display.FULL_PAGE, Layout.SINGLE_PAGE);
// Añadimos una página
var page: Page = new Page(Orientation.PORTRAIT, Unit.MM, Size.A4);
pdf.addPage(page);
// Añadimos el contenido de la página
pdf.setFont(new CoreFont(FontFamily.ARIAL), 25);
pdf.writeText(23, "Hello World!");
pdf.drawRect(new Rectangle(100, 100, 100, 100));
// Almacenamos el PDF generado en la carpeta de almacenamiento de la aplicación
var folder: File = File.applicationStorageDirectory;
var file: File = new File(folder.url + "test.pdf");
var fs: FileStream = new FileStream();
fs.open(file, FileMode.WRITE);
fs.writeBytes(pdf.save(Method.LOCAL));
fs.close();
// Si el ordenador tiene capacidad para ver el PDF
if (HTMLLoader.pdfCapability == HTMLPDFCapability.STATUS_OK) {
// Cargamos el PDF generado en el visor de HTML desde local
var request: URLRequest = new URLRequest(folder.url + "test.pdf");
pdfViewer = new HTMLLoader();
pdfViewer.load(request);
//pdfViewer.loadString("
h“); // Añadimos el visor de PDF a la aplicación
var container: UIComponent = new UIComponent();
container.addChild(pdfViewer);
container.addEventListener(ResizeEvent.RESIZE, resizeHandler);
container.percentHeight = 100;
container.percentWidth = 100;
addElement(container);
}
}
private function resizeHandler(event: ResizeEvent): void {
// Ajustamos el tamaño del visor al tamaño disponible de la aplicación
pdfViewer.width = DisplayObject(event.target).width
pdfViewer.height = DisplayObject(event.target).height;
}
]]>
Supongamos tres ordenadores:
(Ojo con las mayúsculas y minúsculas, son importantes).
1. El controlador de dominio del dominio “LAB.COM” con Active Directory (DC-LAB.LAB.COM). Esta máquina tiene Windows 2003 Server.
2. El servidor donde reside nuestra aplicación (RS-LAB.LAB.COM). Este servidor también está en el dominio “LAB.COM”.
3. Una máquina que también está en el dominio “LAB.COM” desde donde se conectará un usuario con Internet Explorer a nuestra aplicación utilizando autenticación basada en Kerberos v5.
Pasos:
1. Configurar “C:\WINDOWS\krb5.ini” en la máquina donde reside nuestra aplicación:
[domain_realm]
.lab.com = LAB.COM
lab.com = LAB.COM
[libdefaults]
default_realm = LAB.COM
[realms]
LAB.COM = {
kdc = DC-LAB.LAB.COM
admin_server = DC-LAB.LAB.COM
default_domain = LAB.COM
}
2. Generamos un usuario en el controlador de dominio para nuestra aplicación. Se trata de un usuario normal. Se pone cualquier contraseña (no importa) y se marca para que no expire. Por ejemplo, creamos el usuario “http-rs-lab.lab.com”.
3. A continuación en el controlador de dominio, utilizando el comando ktpass, creamos el archivo cifrado con la clave privada que permitirá identificar a nuestra aplicación ante el servidor de Kerberos:
> ktpass -princ HTTP/rs-lab.lab.com@LAB.COM -mapuser http-rs-lab.lab.com@lab.com -pass * -out http-rs-lab.keytab
Targeting domain controller: DC-LAB.LAB.COM
Using legacy password setting method
Successfully mapped HTTP/rs-lab.lab.com to http-rs-lab.lab.com.
Type the password for HTTP/rs-lab.lab.com:
Type the password again to confirm:
WARNING: pType and account type do not match. This might cause problems.
Key created.
Output keytab to http-rs-lab.keytab:
Keytab version: 0×502
keysize 62 HTTP/rs-lab.lab.com@LAB.COM ptype 0 (KRB5_NT_UNKNOWN) vno 4 etype 0×1
7 (RC4-HMAC) keylength 16 (0x399f9bb3158d6b454313f297192705dd)
Aunque da un warning, no importa, funciona perfectamente.
Pienso que la clave está en hacer coincidir la parte de la url que hay después de “HTTP/” con la url de la aplicación (con minúsculas), ya que en mayúsculas no funciona.
4. A continuación copiamos el archivo “http-rs-lab.keytab” a la carpeta WEB-INF/classes de nuestra aplicación. Por lo visto es mejor tenerla fuera del classpath cuando esté en producción. Se puede referenciar también con el protocolo “file:” posteriormente en el XML de configuración de spring.
5. Configuramos spring:
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xmlns:sec=”http://www.springframework.org/schema/security”
xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd”>
<sec:http entry-point-ref=”spnegoEntryPoint”>
<sec:intercept-url pattern=”/secure/**” access=”IS_AUTHENTICATED_FULLY” />
<sec:custom-filter ref=”spnegoAuthenticationProcessingFilter”
position=”BASIC_PROCESSING_FILTER” />
</sec:http>
<bean id=”spnegoEntryPoint”
class=”org.springframework.security.extensions.kerberos.web.SpnegoEntryPoint” />
<bean id=”spnegoAuthenticationProcessingFilter”
class=”org.springframework.security.extensions.kerberos.web.SpnegoAuthenticationProcessingFilter”>
<property name=”authenticationManager” ref=”authenticationManager” />
</bean>
<sec:authentication-manager alias=”authenticationManager”>
<sec:authentication-provider ref=”kerberosServiceAuthenticationProvider” />
</sec:authentication-manager>
<bean id=”kerberosServiceAuthenticationProvider”
class=”org.springframework.security.extensions.kerberos.KerberosServiceAuthenticationProvider”>
<property name=”ticketValidator”>
<bean
class=”org.springframework.security.extensions.kerberos.SunJaasKerberosTicketValidator”>
<property name=”servicePrincipal” value=”HTTP/rs-lab.lab.com@LAB.COM” />
<property name=”keyTabLocation” value=”classpath:http-rs-lab.keytab” />
<property name=”debug” value=”true”/>
</bean>
</property>
<property name=”userDetailsService” ref=”dummyUserDetailsService” />
</bean>
<!– Just returns the User authenticated by Kerberos and gives him the ROLE_USER –>>
<bean id=”dummyUserDetailsService” class=”org.springframework.security.extensions.kerberos.sample.DummyUserDetailsService”/>
</beans>
Hasta aquí todo como lo explica estupendamente Mike en su blog (http://blog.springsource.com/2009/09/28/spring-security-kerberos/).
Cuando en el navegador se pidan los credenciales, el usuario se puede introducir de dos maneras:
Como “lab\paco” o como “paco@lab.com”. Ambas formas son compatibles.
Ahora vamos a ver cómo configurar Flex para conseguir que aquellos usuarios que estén en el dominio se puedan autenticar utilizando Kerberos y aquellos que no estén en el dominio puedan utilizar una autenticación basada en formularios de Flex.
…. DRAFT
Example cache configuration:
proxy_cache_path /usr/local/nginx/cache/altaller levels=1:2 keys_zone=altaller:8m max_size=1000m inactive=600m;
proxy_temp_path /usr/local/nginx/cache/tmp;
server {
listen 80;
server_name admin.altaller.es;
index index.html;
auth_basic “altaller.es”;
auth_basic_user_file htpasswd;
location / {
proxy_pass http://localhost:8080;
proxy_cache altaller;
proxy_cache_key $uri$is_args$args;
proxy_cache_valid 200 302 60m;
proxy_cache_valid 404 1m;
}
}
This configuration specifies that there is a cache “altaller” stored in “/usr/local/nginx/cache/altaller”.
Each time that a new page is requested using http, a new file with a strange name is created inside previous folder. In this case, we request a “/admin/hello.html” page and a file “e74e6f246520c52df1b3e41fee0a5cdb” is created inside the cache structure that contains the following code:
z?1N????j?1N??0?NG
KEY: /admin/hello.html
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=ISO-8859-1
Content-Language: en-US
Content-Length: 399
Date: Thu, 28 Jul 2011 21:53:46 GMT
Connection: close
Thu Jul 28 21:53:46 UTC 2011
5
23
hello
This is a message
hola
……………
As you can see, the key “/admin/hello.html” and the HTML content is contained inside this file.
If we delete this file, the page is invalidated from the nginx cache.
Looking inside the code of nginx_ngx_cache_purge plugin, it uses a nginx function “ngx_http_file_cache_create_key” that creates the name of the file using the key of the cached page.
And finally, the strange name is a MD5 digest of the key in hexadecimal.
To test this, I have developed in Java the equivalent code:
MessageDigest digest = MessageDigest.getInstance(“MD5″);
byte[] bytes = digest.digest(“/admin/hello.html”.getBytes(“utf-8″));
StringBuffer sb = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
sb.append(Integer.toString((bytes[i] & 0xff) + 0×100, 16).substring(1));
}
System.out.println(sb.toString());
And TACHÁN!!! The result is:
e74e6f246520c52df1b3e41fee0a5cdb
Finally, the only thing we must do to explicitly invalidate a page from the cache using Java is deleting the file whose name is a MD5 of the key in hexadecimal.
The file is created in two levels folder structure (“/cache/altaller/b/cd/e74e6f246520c52df1b3e41fee0a5cdb”).
First level is taken from the last letter in the file name “b”.
Second level is taken from the two letters before the last “cd”.
That’s all folks!
We needed a reverse proxy cache to manage the traffic to a Tomcat server. After evaluating several alternatives (G-WAN, Traffic Server, Varnish, Lighttpd, …), we decided to use nginx for its low memory usage.
This is the basic configuration to manage the traffic to the Tomcat server:
http { proxy_cache_path /Users/paco/cache/altaller levels=1:2 keys_zone=altaller:8m max_size=1000m inactive=600m; proxy_temp_path /Users/paco/cache/tmp; server { location / { proxy_pass http://localhost:8080; proxy_cache altaller; proxy_cache_valid 200 302 60m; proxy_cache_valid 404 1m; } } }
That's all folks!
“MyWebService.java”
package com.hg; import javax.jws.WebService; @WebService public interface MyWebService { String test(byte[] text); }
"MyWebServiceImpl.java"
package com.hg; import javax.jws.WebService; @WebService(endpointInterface="com.hg.MyWebService", serviceName="MyWebService") public class MyWebServiceImpl implements MyWebService { public String test(byte[] text) { return new String(text) + "\nOK"; } }
"mule-config.xml"
<?xml version="1.0" encoding="UTF-8"?> <mule xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:spring="http://www.springframework.org/schema/beans" xmlns:stdio="http://www.mulesoft.org/schema/mule/stdio" xmlns:cxf="http://www.mulesoft.org/schema/mule/cxf" xmlns:http="http://www.mulesoft.org/schema/mule/http" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/3.1/mule.xsd http://www.mulesoft.org/schema/mule/stdio http://www.mulesoft.org/schema/mule/stdio/3.1/mule-stdio.xsd http://www.mulesoft.org/schema/mule/cxf http://www.mulesoft.org/schema/mule/cxf/3.1/mule-cxf.xsd http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/3.1/mule-http.xsd"> <!-- Documentation URL: http://www.mulesoft.org/documentation/display/MULE3USER/Building+Web+Services+with+CXF --> <!-- REST: http://localhost:63081/rs/test/arg0/hola --> <flow name="MyWebServiceProducer"> <http:inbound-endpoint address="http://localhost:63081/rs" exchange-pattern="request-response"> <cxf:jaxws-service serviceClass="com.hg.MyWebService"/> </http:inbound-endpoint> <component class="com.hg.MyWebServiceImpl"/> </flow> <!-- Documentation URL: http://www.mulesoft.org/documentation/display/MULE3USER/Consuming+Web+Services+with+CXF --> <flow name="MyWebServiceConsumer"> <inbound-endpoint address="file:///C:/in"> <object-to-byte-array-transformer/> </inbound-endpoint> <cxf:jaxws-client serviceClass="com.hg.MyWebService" operation="test"/> <outbound-endpoint address="http://localhost:63081/rs" exchange-pattern="request-response"/> <outbound-endpoint address="file:///C:/out"/> </flow> </mule>
The other day I needed to merge two XML documents using ActionScript (Flex). I show here a simple example of a recursive function to do so.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
applicationComplete="example()">
<fx:Script>
<![CDATA[
import mx.controls.Alert;
private function example(): void {
var xml1: XML =
<fx:XMLList xmlns:fx="http://ns.adobe.com/mxml/2009">
<fx:menuitem label="menu-1">
<fx:menuitem label="menu-1-1">
<fx:menuitem label="option-1"/>
<fx:menuitem label="option-2"/>
</fx:menuitem>
<fx:menuitem label="menu-1-2">
<fx:menuitem label="option-3"/>
</fx:menuitem>
</fx:menuitem>
<fx:menuitem label="menu-2">
<fx:menuitem label="option-4"/>
</fx:menuitem>
</fx:XMLList>
var xml2: XML =
<fx:XMLList xmlns:fx="http://ns.adobe.com/mxml/2009">
<fx:menuitem label="menu-1">
<fx:menuitem label="menu-1-1">
<fx:menuitem label="option-2"/>
<fx:menuitem label="option-6"/>
</fx:menuitem>
<fx:menuitem label="menu-1-2">
<fx:menuitem label="menu-1-2-1">
<fx:menuitem label="option-7"/>
<fx:menuitem label="option-8"/>
</fx:menuitem>
</fx:menuitem>
</fx:menuitem>
<fx:menuitem label="menu-3">
<fx:menuitem label="option-5"/>
</fx:menuitem>
</fx:XMLList>
merge(xml1, xml2);
Alert.show(xml1.toXMLString());
}
private function merge(destination: XML, source: XML): void {
var t1: XML;
var t2: XML;
for each (t2 in source.*) {
var exists: Boolean = false;
for each (t1 in destination.*) {
if (t1.@label == t2.@label) {
exists = true;
break;
}
}
if (exists) {
merge(t1, t2);
}
else {
destination.appendChild(t2);
}
}
}
]]>
</fx:Script>
</s:Application>
See you soon!
Software development can be simplified by using MDD (Model Driven Development). I use MWE, Xpand, Xtend, (formerly known as oAW [openArchitectureWare]), …, to generate code from higher-level models.
Thus, it increases flexibility and agility in software development, shifting from an model focused on the implementation to a model focused on the design.
Sometimes I need to run the MWE workflows outside of Eclipse.
These are the necessary steps that have worked for me:
// Change the context class loader ClassLoader before = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader( GenerationServiceImpl.class.getClassLoader()); // Add binary folders to classpath to allow the workflow // to find the necessary custom classes (postprocessors, functions) URLClassLoader loader = (URLClassLoader) Thread.currentThread() .getContextClassLoader(); Method method = loader.getClass().getDeclaredMethod( "addURL", new Class[] {URL.class}); method.setAccessible(true); method.invoke(loader, new Object[] { new File(CARTRIDGE_BASE_FOLDER, "bin").toURI().toURL()}); // Instantiate Workflow runner final WorkflowRunner runner = new WorkflowRunner(); // Change the resource loader to find the resources // needed by the workflow ResourceLoader resourceLoader = new ResourceLoader( new File(CARTRIDGE_BASE_FOLDER, "bin")); ResourceLoaderFactory.setCurrentThreadResourceLoader( resourceLoader); // Overwrite the parameters of the workflow Mapparams = new HashMap (); File modelFolder = new File(workingFolder, "model"); params.put("applicationModel", new File(modelFolder, "model.mm")); params.put("projectName", "result"); params.put("projectFolder", workingFolder.getPath()); // Specify the workflow file String wfFile = "D:/.../generator.mwe"; runner.run(wfFile, new NullProgressMonitor(), params, null); // Restore the previous context class loader ResourceLoaderFactory.setCurrentThreadResourceLoader(null); Thread.currentThread().setContextClassLoader(before);
And this is an example of custom resource loader that works for me:
import java.io.File; import java.net.URL; import org.eclipse.emf.mwe.core.resources.ResourceLoaderDefaultImpl; public class ResourceLoader extends ResourceLoaderDefaultImpl { private File baseFolder; public ResourceLoader(File baseFolder) { this.baseFolder = baseFolder; } @Override protected URL loadFromContextClassLoader(String path) { URL url = null; try { url = new URL(path); } catch (Exception e) { try { File file = new File(path); if (!file.isAbsolute()) file = new File(baseFolder, path); url = file.toURI().toURL(); } catch (Exception e2) { e2.printStackTrace(); } } return url; } }
Finally, this is a piece of the workflow file:
<?xml version="1.0" encoding="utf-8"?> <workflow> <property file="workflow/generator.properties"/> <!-- Load internal Model - Begin --> <component class="org.eclipse.emf.mwe.utils.Reader"> <uri value="${applicationModel}"/> <modelSlot value="model"/> </component> <!-- Load internal Model - End --> <!-- Generate static resources - Begin --> <component class="com.hernandezgomez.CopyResources"> <projectFolder value="${projectFolder}"/> <projectName value="${projectName}"/> <resourcesFolder value="${cartridgeResources}"/> </component> <!-- Generate static resources - End --> <!-- Generate code - Begin --> <!-- Root folder - Start --> <component class="org.eclipse.xpand2.Generator"> <metaModel id="ecore" class="org.eclipse.xtend.typesystem.emf.EmfMetaModel" metaModelPackage="org.eclipse.emf.ecore.EcorePackage"/> <metaModel id="mm" class="org.eclipse.xtend.typesystem.emf.EmfMetaModel" metaModelPackage="mm.MmPackage"> <metaModelFile value="metamodel/mm.ecore"/> </metaModel> <fileEncoding value="utf-8"/> <globalVarDef name="projectName" value="'${projectName}'"/> <outlet path="${projectFolder}/${projectName}"> <postprocessor class="com.hernandezgomez.SkipWrittingSameFile"/> </outlet> <expand value="templates::project::main FOR model"/> </component> <component class="org.eclipse.xpand2.Generator"> <metaModel idRef="mm"/> <fileEncoding value="utf-8"/> <outlet path="${projectFolder}/${projectName}"> <postprocessor class="com.hernandezgomez.SkipWrittingSameFile"/> </outlet> <expand value="templates::build::main FOR model"/> </component> ... </workflow>
That’s all!
C:\>xming :0 -clipboard -multiwindow
In putty.exe -> SSH -> X11 -> Check “Enable X11 forwarding” and introduce localhost:0 in “X display location”.
From Putty console:
# firefox (or whatever you want)
This is a simple example of configuring Apache Ivy to manage/retrieve the Spring dependencies.
First, to install Ivy in Ant, ivy-x.x.x.jar must be copied to ANT_HOME/lib.
These are the necessary files:
“build.xml”
<project name="test" default="run" xmlns:ivy="antlib:org.apache.ivy.ant"> <property name="lib.dir" value="lib"/> <property name="build.dir" value="build"/> <property name="src.dir" value="src"/> <property name="res.dir" value="resources"/> <path id="lib.path.id"> <fileset dir="${lib.dir}" includes="*.jar"/> </path> <path id="run.path.id"> <path refid="lib.path.id"/> <path location="${build.dir}"/> <path location="${res.dir}" /> </path> <ivy:settings file="ivysettings.xml"/> <target name="resolve"> <ivy:retrieve/> </target> <target name="report" depends="resolve"> <ivy:report todir="${build.dir}"/> </target> <target name="run" depends="resolve"> <mkdir dir="${build.dir}" /> <javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="lib.path.id" /> <java classpathref="run.path.id" classname="test.Main"/> </target> <target name="clean-cache"> <ivy:cleancache /> </target> </project>
"ivy.xml"
<ivy-module version="2.0"> <info organisation="com.hernandezgomez" module="server"/> <dependencies> <dependency org="org.springframework" name="org.springframework.core" rev="3.0.5.RELEASE"/> <dependency org="org.springframework" name="org.springframework.beans" rev="3.0.5.RELEASE"/> <dependency org="org.springframework" name="org.springframework.context" rev="3.0.5.RELEASE"/> </dependencies> </ivy-module>
"ivy.settings"
<ivysettings> <settings defaultResolver="spring.chain"/> <resolvers> <chain name="spring.chain"> <url name="com.springsource.repository.bundles.release"> <ivy pattern="http://repository.springsource.com/ ivy/bundles/release/[organisation]/ [module]/[revision]/[artifact]-[revision].[ext]" /> <artifact pattern="http://repository.springsource.com/ ivy/bundles/release/[organisation]/ [module]/[revision]/[artifact]-[revision].[ext]" /> </url> <url name="com.springsource.repository.bundles.external"> <ivy pattern="http://repository.springsource.com/ ivy/bundles/external/[organisation]/ [module]/[revision]/[artifact]-[revision].[ext]" /> <artifact pattern="http://repository.springsource.com/ ivy/bundles/external/[organisation]/ [module]/[revision]/[artifact]-[revision].[ext]" /> </url> </chain> </resolvers> </ivysettings>
"Main.java"
package test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"beans/*.xml"}); context.getBean("server"); } }
"Server.java"
package test; public class Server { public Server() { System.out.println("Server instantiated"); } }
"beans/server.xml"
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="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"> <bean id="server" class="test.Server"/> </beans>
"log4j.properties"
log4j.rootCategory=INFO, console log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.apache.log4j.SimpleLayout
Finally, execute ant:
# ant
See you soon!