pondělí 3. března 2014

Ovlivnění generovaného WADL

Při buildování REST aplikací, při využití implementace Jersey, se automaticky generuje i WADL. Tento je možné částečně doplňovat. Nyní ukáži postup přidávání dokumentačních elementů do generovaného WADL.

1) Vytvořím soubor application-doc.xml, tento soubor obsahuje stručný popis aplikace. Příklad:

 <applicationDocs targetNamespace="http://wadl.dev.java.net/2009/02">  
     <doc title="Post Code" xml:lang="en">Title and high level description</doc>  
 </applicationDocs>

2) Vytvořím soubor application-grammars.xml obsahující umístění vlastních XML schemat. Příklad:
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
 <grammars xmlns="http://wadl.dev.java.net/2009/02"   xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xi="http://www.w3.org/1999/XML/xinclude">  
   <include href="../xsd/schema.xsd" />  
 </grammars>  

3) Vytvořím potomka třídy WadlGeneratorConfig, v kterém budou uvedeny oba XML soubory vytvořené v předchozích krocích. Navíc je přidán soubor resourcedoc.xml, který bude vygenerován.

 package biz.prodejna.rest  
 import com.sun.jersey.api.wadl.config.WadlGeneratorConfig;  
 import com.sun.jersey.api.wadl.config.WadlGeneratorDescription;  
 import com.sun.jersey.server.wadl.generators.WadlGeneratorApplicationDoc;  
 import com.sun.jersey.server.wadl.generators.WadlGeneratorGrammarsSupport;  
 import com.sun.jersey.server.wadl.generators.resourcedoc.WadlGeneratorResourceDocSupport;  
 import java.util.List;  
 public class MyWadlGeneratorConfig extends WadlGeneratorConfig {  
   @Override  
   public List<WadlGeneratorDescription> configure() {  
     return generator( WadlGeneratorApplicationDoc.class )  
      .prop( "applicationDocsStream", "application-doc.xml" )  
      .generator( WadlGeneratorGrammarsSupport.class )  
      .prop( "grammarsStream", "application-grammars.xml" )  
      .generator( WadlGeneratorResourceDocSupport.class )  
      .prop( "resourceDocStream", "resourcedoc.xml" )  
      .descriptions();  
   }  
 }  

4) Třídu vytvořenou v předchozím kroku uvedu jako inicializační parametr JerseyServletu ve web.xml

 <servlet>  
     <servlet-name>JerseyServlet</servlet-name>  
     <servlet-class>  
       com.sun.jersey.spi.container.servlet.ServletContainer  
     </servlet-class>  
     <init-param>  
       <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>  
       <param-value>true</param-value>  
     </init-param>  
     <init-param>  
       <param-name>com.sun.jersey.config.property.WadlGeneratorConfig</param-name>  
       <param-value>biz.prodejna.rest.MyWadlGeneratorConfig</param-value>  
     </init-param>  
     <load-on-startup>1</load-on-startup>  
 </servlet>  

5) Aby byl vygenerován soubor resourcedoc.xml je třeba do pom.xml přidat maven javadoc plugin s docletem com.sun.jersey.wadl.resourcedoc.ResourceDoclet.

 <plugin>  
    <groupId>org.apache.maven.plugins</groupId>  
    <artifactId>maven-javadoc-plugin</artifactId>  
    <version>2.9.1</version>  
    <executions>  
        <execution>  
            <goals>  
                <goal>javadoc</goal>  
            </goals>  
            <phase>compile</phase>  
        </execution>  
    </executions>  
    <configuration>  
        <encoding>UTF-8</encoding>  
        <verbose>false</verbose>  
        <show>public</show>  
        <subpackages>biz.prodejna.rest</subpackages>  
        <doclet>com.sun.jersey.wadl.resourcedoc.ResourceDoclet</doclet>  
        <docletPath>${path.separator}${project.build.outputDirectory}</docletPath>  
        <docletArtifacts>  
            <docletArtifact>  
            <groupId>com.sun.jersey.contribs</groupId>  
            <artifactId>wadl-resourcedoc-doclet</artifactId>  
            <version>${jersey-release-version}</version>  
        </docletArtifact>  
        <docletArtifact>  
            <groupId>com.sun.jersey</groupId>  
            <artifactId>jersey-server</artifactId>  
            <version>${jersey-release-version}</version>  
        </docletArtifact>  
        <docletArtifact>  
            <groupId>xerces</groupId>  
            <artifactId>xercesImpl</artifactId>  
            <version>2.6.1</version>  
        </docletArtifact>  
        </docletArtifacts>  
        <!-- the following option is required as a work around for  
        version 2.5 of the javadoc plugin which will be used  
        by a maven version > 2.0.9 -->  
        <useStandardDocletOptions>false</useStandardDocletOptions>  
        <additionalparam>-output ${project.build.outputDirectory}/resourcedoc.xml</additionalparam>  
     </configuration>  
 </plugin>  

6) Aby se generovaly doc elementy i k jednotlivým parametrům je do pom.xml třeba přidat závislost

 <dependency>  
   <groupId>javax.ws.rs</groupId>  
   <artifactId>javax.ws.rs-api</artifactId>  
   <version>2.0</version>  
 </dependency>  

Nyní by celé řešení mělo být funkční. Pokud přijdete na to jak v elementech parametrů generovat atribut required můžete mi o tom dát vědět do diskuze níže. Nebo máte jiný způsob jak co nepřesněji definovat REST API? I o tom se rád dočtu.

úterý 4. února 2014

IptabLes a IptabLex

Možná se Vám v procesech objevili dva (třeba i v několika kopiích), které nadměrně zatěžují procesor a jmenují se jak je uvedeno v nadpisu. Možná máte i velký upload (TCP Retransmission) na převážně Čínské IP adresy. Pravděpodobně také máte spuštěný nějaký servletový kontejner.
Problém může nastat pokud na svém počítači hostujete aplikaci využívající Struts 2 verze 2.3.15 nebo starší.
Následující kroky se mi osvědčili při odstranění uvedeného malware:
  1.  Undeploy aplikací využívající uvedenou verzi Struts 2.
  2.  v /boot odstranit všechny soubory .IptabLe* a IpabLe*
  3. v /etc/rc* odstrnit všechny soubory S55IptabLe*
  4. Zkontrolujte jestli nemáte nevítaného hosta: cat /etc/passwd|grep '/bin/bash'
  5. Rebuild a deploy aplikací s nejnovější verzí Struts 2
A takovéto requesty (u mě z adresy 60.190.218.252) onen problém způsobují:

POST /videos.action HTTP/1.1
User-Agent: Mozilla/5.0
Accept: */*
Content-Type: application/x-www-form-urlencoded
Expect: 100-continue
Connection: Keep-Alive
redirect:${%23res%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23res.setCharacterEncoding(%22UTF-8%22),%23a%3d(new%20java.lang.ProcessBuilder(new%20java.lang.String[]{%22killall%22%2C%22%2Fboot%2F.IptabLes%22})).start(),%23b%3d%23a.getInputStream(),%23c%3dnew%20java.io.InputStreamReader(%23b),%23d%3dnew%20java.io.BufferedReader(%23c),%23e%3dnew%20char[20000],%23d.read(%23e),%23res.getWriter().println(%23e),%23res.getWriter().flush(),%23res.getWriter().close()}

středa 5. června 2013

Glassfish 3.1.2.2 bug GRIZZLY-1375 a GRIZZLY-1385

V glassfishi novějším než verze 3.0.1 se objevuje chyba v grizzlym. Ta se projeví, při volání webové služby se zabezpečením, následujícím výpisem do serverového logu:
 WSITPVD0035: Error in Verifying Security in Inbound Message.  
 com.sun.xml.wss.impl.XWSSecurityRuntimeException: WSS1601: Security Requirements not met - Transport binding configured in policy but incoming message was not SSL enabled  
      at com.sun.xml.wss.impl.policy.verifier.MessagePolicyVerifier.verifyPolicy(MessagePolicyVerifier.java:125)  

Chyba je již od grizzlyho verze 1.9.55 vyřešena. Ovšem tato verze zatím není z update centra glassfishe dostupná.
Osvědčil se mi však následující postup s použitím toho času nejnovější verze Glassfish 3.1.2.2:
  1. Stáhnout si grizzly-utils-1.9.57.jar
  2. Stáhnout si grizzly-lzma-1.9.57.jar 
  3. Stažené soubory umístit do adresáře glassfish/modules
  4. Přihlásit se telnetem na localhost 6666. Pokud tuta volba není v GF ještě povolena, lze postupovat podle kapitoly 10.4.1 v tomto dokumentu
  5. Zadat příkaz lb grizzly. Ve výpisu budou řádky podobné těmto:
      224|Resolved   |    1|grizzly-lzma (1.9.50)
     265|Resolved   |    1|grizzly-utils (1.9.50)
  6. postupně zadat příkaz uninstall s parametrem id (první sloupec předchozího výpisu). V tomto případě
    uninstall 224
    uninstall 265
  7. Instalace novějších verzí:
    install file:../../../modules/grizzly-lzma-1.9.57.jar
    install file:../../../modules/grizzly-utils-1.9.57.jar
  8. Nyní je možné odstranit původní soubory bundlů
    rm glassfish/modules/grizzly-lzma.jar
    rm glassfish/modules/grizzly-utils.jar
Díky této opravě je možné narazit na další chybu v GF. Problémem je volání metody javax.xml.ws.WebServiceContext.getUserPrincipal(). Stejná data lze však získat i voláním javax.xml.ws.WebServiceContext.getMessageContext().get("CLIENT_SUBJECT"). Například uživatelské jméno lze získat následovně:
 javax.security.auth.Subject subject = (javax.security.auth.Subject)wsContext.getMessageContext().get("CLIENT_SUBJECT");  
       Set privateCredentials = subject.getPrivateCredentials();  
       String userName;  
       for(Object credential : privateCredentials){  
         if(credential instanceof com.sun.enterprise.security.auth.login.common.PasswordCredential) userName = ((com.sun.enterprise.security.auth.login.common.PasswordCredential)credential).getUser();  
       }  

Drobný tip k update centru nakonec: Možná se snažíte spustit update center a ten píše hlášku:
 You are running on a 64 bit Linux distribution and the 32 bit Linux  
 compatibility libraries do not appear to be installed. In order to use  
 the Update Center tools you must install the 32 bit compatibility libraries.  
 On Ubuntu (and possibly other Debian based systems) please install the  
 ia32-libs package. On RedHat 4 (and other RPM based systems), you may  
 need to add multiple 'compat' runtime library packages. Please see the  
 Update Center Release Notes for more information  
Pokud jste již ia32-libs nainstalovali a nepřišla žádná změna, pak vězte, že je třeba ještě nainstalovat libjpeg62:i386. Např. příkazem sudo apt-get install libjpeg62:i386

středa 3. dubna 2013

Glassfish JMS broker perzistující zprávy do Sybase ASE

Co budeme potřebovat:
  1. Oracle Glassfish 3.1.2.2 Dále je předpokládáno umístění v ~/glassfish3122
  2. Sybase Adaptive Server Enterprise 15.5
  3. JMS broker (integrovaný v Glassfishi)
  4. JDBC konektor pro Sybase ASE - jConnect 7.0.7
Postup:
1) Ve webové administraci serveru Glassfish otevřeme Configurations->server-config->Java Message Service. JMS Service Type nastavíme na hodnotu LOCAL. Poté v Additional Properties postupně nastavíme následující:
 <property name="imq.persist.store" value="jdbc"></property>  
     <property name="imq.persist.jdbc.sybase.driver" value="com.sybase.jdbc4.jdbc.SybDriver"></property>  
     <property name="imq.persist.jdbc.sybase.user" value="db_user"></property>  
     <property name="imq.persist.jdbc.sybase.password" value="db_user_passwd"></property>  
     <property name="imq.persist.jdbc.dbVendor" value="sybase"></property>  
     <property name="imq.brokerid" value="testBroker"></property>  
     <property name="imq.autocreate.reaptime" value="1"></property>  
     <property name="imq.instanceconfig.version" value="300"></property>  
     <property name="imq.jmsra.managed" value="true"></property>  
     <property name="imq.persist.jdbc.sybase.closedburl" value="jdbc:sybase:Tds:IPAddress:port/database"></property>  
     <property name="imq.persist.jdbc.sybase.opendburl" value="jdbc:sybase:Tds:IPAddress:port/database"></property>  
 Toto je samozřejmě možné zadat i v souboru config/domain.xml příslušné domény.
Pokud se rozhodnete namísto com.sybase.jdbc4.jdbc.SybDriver použít com.sybase.jdbc4.jdbc.SybDataSource nejspíš vás v logu bude čekat tato chyba:
 ERROR [B3000]: Could not open persistent message store:  
 com.sun.messaging.jmq.jmsserver.util.BrokerException: [B3026]: Failed to get connection to jdbc:sybase:Tds:IPAddress:2030/database  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.comm.CommDBManager.newConnection(CommDBManager.java:796)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.comm.CommDBManager.getNewConnection(CommDBManager.java:757)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.comm.DBConnectionPool.createConnection(DBConnectionPool.java:483)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.comm.DBConnectionPool.<init>(DBConnectionPool.java:352)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.comm.DBConnectionPool.<init>(DBConnectionPool.java:233)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.DBManager.getDBManager(DBManager.java:184)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.JDBCStore.<init>(JDBCStore.java:112)  
      at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)  
      at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)  
      at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)  
      at java.lang.reflect.Constructor.newInstance(Constructor.java:525)  
      at java.lang.Class.newInstance0(Class.java:372)  
      at java.lang.Class.newInstance(Class.java:325)  
      at com.sun.messaging.jmq.jmsserver.persist.StoreManager.getStore(StoreManager.java:169)  
      at com.sun.messaging.jmq.jmsserver.Globals.getStore(Globals.java:995)  
      at com.sun.messaging.jmq.jmsserver.Broker._start(Broker.java:955)  
      at com.sun.messaging.jmq.jmsserver.Broker.start(Broker.java:456)  
      at com.sun.messaging.jmq.jmsserver.Broker.main(Broker.java:2151)  
 Caused by: java.sql.SQLException: JZ0PN: Specified port number of -1 was out of range. Port numbers must meet the following conditions: 0 <= portNumber <= 65535  
      at com.sybase.jdbc4.jdbc.SybConnection.getAllExceptions(SybConnection.java:2780)  
      at com.sybase.jdbc4.jdbc.SybConnection.handleSQLE(SybConnection.java:2648)  
      at com.sybase.jdbc4.jdbc.SybConnection.tryLogin(SybConnection.java:479)  
      at com.sybase.jdbc4.jdbc.SybConnection.handleHAFailover(SybConnection.java:3109)  
      at com.sybase.jdbc4.jdbc.SybConnection.<init>(SybConnection.java:328)  
      at com.sybase.jdbc4.jdbc.SybDriver.createConnection(SybDriver.java:681)  
      at com.sybase.jdbc4.jdbc.SybDriver.connect(SybDriver.java:620)  
      at com.sybase.jdbc4.jdbc.SybDriver.connect(SybDriver.java:653)  
      at com.sybase.jdbc4.jdbc.SybDataSource.getConnection(SybDataSource.java:320)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.comm.CommDBManager.newConnection(CommDBManager.java:782)  
      ... 17 more|#]  
Pokud se budete snažit použít některý ze starších ovladačů od Sybase, například verze 6.0, bude v logu chyba následující:
 Local broker:ERROR [B3000]: Could not open persistent message store:  
 com.sun.messaging.jmq.jmsserver.util.BrokerException: Failed to open persistent store.  
      at com.sun.messaging.jmq.jmsserver.persist.StoreManager.getStore(StoreManager.java:219)  
      at com.sun.messaging.jmq.jmsserver.Globals.getStore(Globals.java:995)  
      at com.sun.messaging.jmq.jmsserver.Broker._start(Broker.java:955)  
      at com.sun.messaging.jmq.jmsserver.Broker.start(Broker.java:456)  
      at com.sun.messaging.jmq.jmsserver.Broker.main(Broker.java:2151)  
 Caused by: com.sybase.jdbc3.utils.UnimplementedOperationException: The method com.sybase.jdbc3.jdbc.SybDatabaseMetaData.getSQLStateType() has not been completed and should not be called.  
      at com.sybase.jdbc3.jdbc.ErrorMessage.raiseRuntimeException(ErrorMessage.java:950)  
      at com.sybase.jdbc3.utils.Debug.notImplemented(Debug.java:387)  
      at com.sybase.jdbc3.jdbc.SybDatabaseMetaData.getSQLStateType(SybDatabaseMetaData.java:2261)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.comm.CommDBManager.initDBMetaData(CommDBManager.java:177)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.DBManager.getDBManager(DBManager.java:185)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.JDBCStore.<init>(JDBCStore.java:112)  
      at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)  
      at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)  
      at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)  
      at java.lang.reflect.Constructor.newInstance(Constructor.java:525)  
      at java.lang.Class.newInstance0(Class.java:372)  
      at java.lang.Class.newInstance(Class.java:325)  
      at com.sun.messaging.jmq.jmsserver.persist.StoreManager.getStore(StoreManager.java:169)  
      ... 4 more|#]  

2) Abychom mohli používat JDBC ovladač pro Sybase ASE uvedený v kroku 1, musíme o něm dát JMS brokerovi vědět. To uděláme v souboru ~/glassfish3122/mq/etc/imqenv.conf přidáním řádku s cestou k ovladači: IMQ_DEFAULT_EXT_JARS=~/glassfish3122/glassfish/lib/jconn4-7.0.7.jar V uvedeném umístění samozřejmě musí tento soubor existovat.
Pokud cesta k ovladači nebude uvedena správně, objeví se v logu chyba:
 Local broker: ERROR [B3000]: Could not open persistent message store:  
 com.sun.messaging.jmq.jmsserver.util.BrokerException: [B3024]: Failed to load JDBC driver: com.sybase.jdbc4.jdbc.SybDriver  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.comm.CommDBManager.initDBDriver(CommDBManager.java:495)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.DBManager.<init>(DBManager.java:259)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.DBManager.getDBManager(DBManager.java:182)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.JDBCStore.<init>(JDBCStore.java:112)  
      at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)  
      at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)  
      at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)  
      at java.lang.reflect.Constructor.newInstance(Constructor.java:525)  
      at java.lang.Class.newInstance0(Class.java:372)  
      at java.lang.Class.newInstance(Class.java:325)  
      at com.sun.messaging.jmq.jmsserver.persist.StoreManager.getStore(StoreManager.java:169)  
      at com.sun.messaging.jmq.jmsserver.Globals.getStore(Globals.java:995)  
      at com.sun.messaging.jmq.jmsserver.Broker._start(Broker.java:955)  
      at com.sun.messaging.jmq.jmsserver.Broker.start(Broker.java:456)  
      at com.sun.messaging.jmq.jmsserver.Broker.main(Broker.java:2151)  
 Caused by: java.lang.ClassNotFoundException: com.sybase.jdbc3.jdbc.SybDriver  
      at java.net.URLClassLoader$1.run(URLClassLoader.java:366)  
      at java.net.URLClassLoader$1.run(URLClassLoader.java:355)  
      at java.security.AccessController.doPrivileged(Native Method)  
      at java.net.URLClassLoader.findClass(URLClassLoader.java:354)  
      at java.lang.ClassLoader.loadClass(ClassLoader.java:423)  
      at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)  
      at java.lang.ClassLoader.loadClass(ClassLoader.java:356)  
      at java.lang.Class.forName0(Native Method)  
      at java.lang.Class.forName(Class.java:186)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.comm.CommDBManager.initDBDriver(CommDBManager.java:467)  
      ... 14 more|#]  

3) Jelikož JMS broker nepodporuje Sybase ASE je třeba pro Sybase ASE vytvořit databázové schema. Na konec souboru ~/glassfish3122/glassfish/domains/domain1/imq/instances/imqbroker/props/config.properties je třeba přidat následující položky:
 # Beginning of properties to plug in a Sybase ASE database  
 #  
 # User name used to open database connection. Replace username.  
 #imq.persist.jdbc.sybase.user=<username>  
 # Optional property to specify whether the database requires a password.  
 #imq.persist.jdbc.sybase.needpassword=[true|false]  
 # Vendor specific JDBC driver.  
 imq.persist.jdbc.sybase.driver=com.sybase.jdbc3.jdbc.SybConnectionPoolDataSource  
 # Vendor specific properties.  
 # Vendor specific database url to get a database connection.  
 # Replace hostname, port and sid in imq.persist.jdbc.oracle.property.url.  
 # imq.persist.jdbc.oracle.property.url=jdbc:oracle:thin:@<hostname>:<port>:<sid>  
 imq.persist.jdbc.sybase.property.implicitCachingEnabled=true  
 imq.persist.jdbc.sybase.property.maxStatements=25  
 # Properties to define the tables used by the broker. Do not modify the schema.  
 # Version table  
 imq.persist.jdbc.sybase.table.MQVER41=\  
      CREATE TABLE ${name} (\  
        STORE_VERSION INTEGER NOT NULL,\  
        LOCK_ID VARCHAR(100),\  
        PRIMARY KEY(STORE_VERSION))  
 # Configuration change record table  
 imq.persist.jdbc.sybase.table.MQCREC41=\  
      CREATE TABLE ${name} (\  
        RECORD IMAGE NOT NULL,\  
        CREATED_TS NUMERIC NOT NULL,\  
        PRIMARY KEY(CREATED_TS))  
 # Broker table  
 imq.persist.jdbc.sybase.table.MQBKR41=\  
      CREATE TABLE ${name} (\  
        ID VARCHAR(100) NOT NULL,\  
        URL VARCHAR(100) NOT NULL,\  
        VERSION INTEGER NOT NULL,\  
        STATE INTEGER NOT NULL,\  
        TAKEOVER_BROKER VARCHAR(100),\  
        HEARTBEAT_TS NUMERIC,\  
        PRIMARY KEY(ID))  
 # Store session table  
 imq.persist.jdbc.sybase.table.MQSES41=\  
      CREATE TABLE ${name} (\  
        ID BIGINT NOT NULL,\  
        BROKER_ID VARCHAR(100) NOT NULL,\  
        IS_CURRENT INTEGER NOT NULL,\  
        CREATED_BY VARCHAR(100) NOT NULL,\  
        CREATED_TS BIGINT NOT NULL,\  
        PRIMARY KEY(ID))  
 imq.persist.jdbc.sybase.table.MQSES41.index.IDX1=\  
      CREATE INDEX ${index} ON ${name} (\  
        BROKER_ID)  
 imq.persist.jdbc.sybase.table.MQSES41.index.IDX2=\  
      CREATE INDEX ${index} ON ${name} (\  
        BROKER_ID, IS_CURRENT)  
 # Destination table  
 imq.persist.jdbc.sybase.table.MQDST41=\  
      CREATE TABLE ${name} (\  
        ID VARCHAR(100) NOT NULL,\  
        DESTINATION IMAGE NOT NULL,\  
        IS_LOCAL INTEGER NOT NULL,\  
        CONNECTION_ID NUMERIC NULL,\  
        CONNECTED_TS NUMERIC,\  
        STORE_SESSION_ID NUMERIC,\  
        CREATED_TS NUMERIC NOT NULL,\  
        PRIMARY KEY(ID))  
 imq.persist.jdbc.sybase.table.MQDST41.index.IDX1=\  
      CREATE INDEX ${index} ON ${name} (\  
        STORE_SESSION_ID)  
 # Interest (consumer) table  
 imq.persist.jdbc.sybase.table.MQCON41=\  
      CREATE TABLE ${name} (\  
        ID NUMERIC NOT NULL,\  
        CLIENT_ID VARCHAR(1024),\  
        DURABLE_NAME VARCHAR(1024),\  
        CONSUMER IMAGE NOT NULL,\  
        CREATED_TS NUMERIC NOT NULL,\  
        PRIMARY KEY(ID))  
 # Interest list (consumer state) table  
 imq.persist.jdbc.sybase.table.MQCONSTATE41=\  
      CREATE TABLE ${name} (\  
        MESSAGE_ID VARCHAR(100) NOT NULL,\  
        CONSUMER_ID NUMERIC NOT NULL,\  
        STATE INTEGER,\  
        TRANSACTION_ID NUMERIC,\  
        CREATED_TS NUMERIC NOT NULL,\  
        PRIMARY KEY(MESSAGE_ID, CONSUMER_ID))  
 imq.persist.jdbc.sybase.table.MQCONSTATE41.index.IDX1=\  
      CREATE INDEX ${index} ON ${name} (\  
        TRANSACTION_ID)  
 imq.persist.jdbc.sybase.table.MQCONSTATE41.index.IDX2=\  
      CREATE INDEX ${index} ON ${name} (\  
        MESSAGE_ID)  
 # Message table  
 imq.persist.jdbc.sybase.table.MQMSG41=\  
      CREATE TABLE ${name} (\  
        ID VARCHAR(100) NOT NULL,\  
        MESSAGE IMAGE NOT NULL,\  
        MESSAGE_SIZE NUMERIC,\  
        STORE_SESSION_ID NUMERIC NOT NULL,\  
        DESTINATION_ID VARCHAR(100),\  
        TRANSACTION_ID NUMERIC,\  
        CREATED_TS NUMERIC NOT NULL,\  
        PRIMARY KEY(ID))  
 imq.persist.jdbc.sybase.table.MQMSG41.index.IDX1=\  
      CREATE INDEX ${index} ON ${name} (\  
        STORE_SESSION_ID, DESTINATION_ID)  
 # Property table  
 imq.persist.jdbc.sybase.table.MQPROP41=\  
      CREATE TABLE ${name} (\  
        PROPNAME VARCHAR(100) NOT NULL,\  
        PROPVALUE IMAGE,\  
        PRIMARY KEY(PROPNAME))  
 # Transaction table  
 imq.persist.jdbc.sybase.table.MQTXN41=\  
      CREATE TABLE ${name} (\  
        ID NUMERIC NOT NULL,\  
        TYPE INTEGER NOT NULL,\  
        STATE INTEGER,\  
        AUTO_ROLLBACK INTEGER NOT NULL,\  
        XID VARCHAR(256),\  
        TXN_STATE IMAGE NOT NULL,\  
        TXN_HOME_BROKER IMAGE,\  
        TXN_BROKERS IMAGE,\  
        STORE_SESSION_ID NUMERIC NOT NULL,\  
        EXPIRED_TS NUMERIC NOT NULL,\  
        ACCESSED_TS NUMERIC NOT NULL,\  
        PRIMARY KEY(ID))  
 imq.persist.jdbc.sybase.table.MQTXN41.index.IDX1=\  
      CREATE INDEX ${index} ON ${name} (\  
        STORE_SESSION_ID)  
 # JMS Bridge TM LogRecord table  
 imq.persist.jdbc.sybase.table.MQTMLRJMSBG41=\  
      CREATE TABLE ${name} (\  
        XID VARCHAR(256) NOT NULL,\  
        LOG_RECORD IMAGE NOT NULL,\  
        NAME VARCHAR(100) NOT NULL,\  
        BROKER_ID VARCHAR(100) NOT NULL,\  
        CREATED_TS NUMERIC NOT NULL,\  
        UPDATED_TS NUMERIC NOT NULL,\  
        PRIMARY KEY(XID))  
 imq.persist.jdbc.sybase.table.MQTMLRJMSBG41.index.IDX1=\  
      CREATE INDEX ${index} ON ${name} (BROKER_ID)  
 imq.persist.jdbc.sybase.table.MQTMLRJMSBG41.index.IDX2=\  
      CREATE INDEX ${index} ON ${name} (NAME)  
 # JMS Bridges table  
 imq.persist.jdbc.sybase.table.MQJMSBG41=\  
      CREATE TABLE ${name} (\  
        NAME VARCHAR(100) NOT NULL,\  
        BROKER_ID VARCHAR(100) NOT NULL,\  
        CREATED_TS NUMERIC NOT NULL,\  
        UPDATED_TS NUMERIC NOT NULL,\  
        PRIMARY KEY(NAME))  
 # End of properties to plug in an Sybase ASE database  
Některé z víše uvedených položek je samozřejmě možné upravit podle vlastních potřeb nebo přidat další.
Kdybychom toto neudělali skončili bychom s touto chybou v logu:
 Local broker: ERROR [B3000]: Could not open persistent message store:  
 com.sun.messaging.jmq.jmsserver.util.BrokerException: Table definition not found for sybase  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.comm.CommDBManager.loadTableSchema(CommDBManager.java:1215)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.DBManager.getDBManager(DBManager.java:183)  
      at com.sun.messaging.jmq.jmsserver.persist.jdbc.JDBCStore.<init>(JDBCStore.java:112)  
      at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)  
      at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)  
      at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)  
      at java.lang.reflect.Constructor.newInstance(Constructor.java:525)  
      at java.lang.Class.newInstance0(Class.java:372)  
      at java.lang.Class.newInstance(Class.java:325)  
      at com.sun.messaging.jmq.jmsserver.persist.StoreManager.getStore(StoreManager.java:169)  
      at com.sun.messaging.jmq.jmsserver.Globals.getStore(Globals.java:995)  
      at com.sun.messaging.jmq.jmsserver.Broker._start(Broker.java:955)  
      at com.sun.messaging.jmq.jmsserver.Broker.start(Broker.java:456)  
      at com.sun.messaging.jmq.jmsserver.Broker.main(Broker.java:2151)|#]  
Více o konfiguraci JMS brokeru je uvedeno zde.
Nyní stačí zapnout Glassfish a zkontrolovat zda se v databázi vytvořili tabulky uvedené ve schematu. Poté je už možné využívat JMS.
V případě zájmu si můžete přečíst více o konfiguraci persistentního uložiště pro Oracle Glassfish Message Queue.
Nastaly problémy, které zde nejsou uvedeny? Podělte se o ně v diskuzi níže.

pátek 16. listopadu 2012

Několik tipů pro MyBatis 3

Ač je uživatelská příručka MyBatis 3 napsaná přehledně, lze se setkat s některými problémy či možnostmi, které jsou v ní uvedeny pouze okrajově nebo vůbec.
  1. Použil jsem připojení k databázi Sybase v které jsem volal proceduru. Na vstupu procedury byl parametr typu int. Mapování v xml vypadalo následovně:
    <select id="getPostOffice" statementType="CALLABLE" parameterType="biz.prodejna.pasport.PostRequest" resultType="java.lang.Integer">  
        {call search_post (#{psc})}  
    </select>
    psc bylo typu Integer. Mapoval jsem tedy java.lang.Integer z javy na int z databáze. Výsledkem byla následující chyba:
     Cause: java.sql.SQLException: JZ006: Caught IOException: java.io.IOException: JZ0SL: Unsupported SQL type 1111.  
     ; SQL []; JZ006: Caught IOException: java.io.IOException: JZ0SL: Unsupported SQL type 1111.; nested exception is java.sql.SQLException: JZ006: Caught IOException: java.io.IOException: JZ0SL: Unsupported SQL type 1111.  
    
    Řešením je explicitní uvedení databázového typu pomocí jdbcType:
     <select id="getPostOffice" statementType="CALLABLE" parameterType="biz.prodejna.pasport.PostRequest" resultType="java.lang.Integer">   
       {call search_post (#{psc,jdbcType=INTEGER})}   
     </select>  
    

    Pozor na přebytečné mezery. Pokud budou mezery např. kolem znaku "=" nebo za čárkou, vznikne chyba
     org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.builder.BuilderException: Improper inline parameter map format. Should be: #{propName,attr1=val1,attr2=val2}  
    

  2. Často se volá procedura/select, jejíž výsledek je třeba namapovat na třídu. Názvy členských proměnných třídy se však vždy neshodují s názvy sloupců jež jsou výstupem z databáze. K tomu účelu je v MyBatis anotace @Results. Zde je ukázka použití, kde místo předchozího mapování v xml je použita anotace v rozhranní mapperu:
     @Select("{call psc_detail_post (#{psc})}")  
       @Results(value = {  
         @Result(property = "psc"),  
         @Result(property = "premisesName", column = "nazev_provozovny"),  
         @Result(property = "municipality", column = "obec"),  
         @Result(property = "municipalityPart", column = "cast_obce"),  
         @Result(property = "street", column = "ulice"),  
         @Result(property = "descriptiveNumber", column = "cislo_popisne"),  
         @Result(property = "descriptiveCharacter", column = "cislo_popisne_znak"),  
         @Result(property = "landmarkNumber", column = "cislo_orientacni"),  
         @Result(property = "landmarkCharacter", column = "cislo_orientacni_znak"),  
         @Result(property = "email"),  
         @Result(property = "phone", column = "telefon")})  
       biz.prodejna.pasport.PostResponse getDetailPost(Integer psc);  
    

    Zde stojí za povšimnutí parametry psc a email. Jelikož se shodují názvy členských proměnných a výstupních sloupců, není třeba uvádět parametr column.

úterý 23. října 2012

Nastavení citlivosti myši v Ubuntu 12.04

Koupil jsem si novou myš s rozlišením1600 dpi. Po připojení k počítači byla ale příliš rychlá. Nastavení citlivosti v Unity nefunguje. Osvědčil se mi však následující postup:
  1. Spustit příkaz
    xinput --list --short
    Výpis bude podobný tomuto:
     ⎡ Virtual core pointer               id=2     [master pointer (3)]  
     ⎜  ↳ Virtual core XTEST pointer            id=4     [slave pointer (2)]  
     ⎜  ↳ ELECOM ELECOM LASER MOUSE             id=8     [slave pointer (2)]  
     ⎣ Virtual core keyboard               id=3     [master keyboard (2)]  
       ↳ Virtual core XTEST keyboard            id=5     [slave keyboard (3)]  
       ↳ Power Button                   id=6     [slave keyboard (3)]  
       ↳ Power Button                   id=7     [slave keyboard (3)]  
       ↳  USB Keyboard                  id=9     [slave keyboard (3)]  
       ↳  USB Keyboard                  id=10     [slave keyboard (3)]  
       ↳ USB 2.0 Camera                  id=11     [slave keyboard (3)]
    
    Povšimněme si názvu myši, zde "ELECOM ELECOM LASER MOUSE". 
  2. Spustíme příkaz pro zpomalení. Defaultně je hodnota 1. Čím vyšší číslo, tím větší zpomalení myši. Mě vyhovovala hodnota 4.
    xinput --set-prop "ELECOM ELECOM LASER MOUSE" "Device Accel Constant Deceleration" 4
  3. Vypnutí zrychlení myši. V mém případě byla 1 defaultní hodnota
    xinput --set-prop "ELECOM ELECOM LASER MOUSE" "Device Accel Velocity Scaling" 1
  4. Pokud uvedené řešení funguje, můžeme si nastavit spouštění skriptu při přihlášení. Stačí vytvořit soubor např.
     gedit ~/.xinitrc  
    
    s obsahem
     #!/usr/bin/env bash  
     xinput --set-prop "ELECOM ELECOM LASER MOUSE" "Device Accel Constant Deceleration" 4  
     xinput --set-prop "ELECOM ELECOM LASER MOUSE" "Device Accel Velocity Scaling" 1  
    
    Povolit jeho spouštění příkazem
    chmod +x ~/.xinitrc
    a přidat do "Aplikace po spuštění..."

čtvrtek 31. května 2012

RESTful webová služba 1. díl

  1. Nainstaluji si server glassfish. Ten je možné zdarma získat např. zde. Stačit vám bude ale i Tomcat nebo jiný servletový kontejner.
  2. Vytvořím si v Netbeans novou webovou aplikaci:
  3. V tomto okně je třeba dát Add a následně vybrat nainstalovaný servletový kontejner.
  4. Nyní je třeba nakonfigurovat aplikaci. To je možné dvěma způsoby:

    1.  Prostřednictvím souboru web.xml
    2.  <?xml version="1.0" encoding="UTF-8"?>  
       <web-app xmlns="http://java.sun.com/xml/ns/javaee"  
            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_3_0.xsd" version="3.0">  
         <servlet>  
           <servlet-name>JerseyServlet</servlet-name>  
           <servlet-class>  
             com.sun.jersey.spi.container.servlet.ServletContainer  
           </servlet-class>  
           <load-on-startup>1</load-on-startup>  
         </servlet>  
         <servlet-mapping>  
           <servlet-name>JerseyServlet</servlet-name>  
           <url-pattern>/resources/*</url-pattern>  
         </servlet-mapping>  
       </web-app>  
      

    3. Pomocí anotace @ApplicationPath
    4. 1:  package biz.prodejna.examples.rest;  
      2:    
      3:  import javax.ws.rs.ApplicationPath;  
      4:  import javax.ws.rs.core.Application;  
      5:    
      6:  @ApplicationPath("resources")  
      7:  public class JaxRsConfig extends Application {  
      8:  }  
      

  5. A nyní konečně mohu napsat nějakou svoji třídu.
  6. RESTful webovou službu mohu volat jako bezparametrickou nebo s parametry. V druhém případě mám v podstatě 3 možnosti, jak parametry vkládat:
    • Jako součást URL. Příklad: http://localhost:8080/example-rest/resources/names/jmeno/prijmeni
    • Jako parametry metody GET. Příklad: http://localhost:8080/example-rest/resources/names?jmeno=Adam&prijmeni=Oliva
    • V těle HTTP requestu. Zde mohou být parametry naformátovány v libovolném MIME typu.

    V tomto bodě ukáži pouze první případ. Další případy si nechám do některého z dalších dílů.

    1:  package biz.prodejna.examples.rest;  
    2:    
    3:  import javax.ws.rs.GET;  
    4:  import javax.ws.rs.Path;  
    5:  import javax.ws.rs.PathParam;  
    6:  import javax.ws.rs.Produces;  
    7:    
    8:  @Path("names")  
    9:  public class Names {  
    10:    
    11:    @GET  
    12:    @Path("{first}/{last}")  
    13:    @Produces("text/plain")  
    14:    public String getFullName(  
    15:        @PathParam("first") String firstName, @PathParam("last") String lastName) {  
    16:      return firstName + " " + lastName;  
    17:    }  
    18:  }  
    

    Vysvětlím jednotlivé anotace:
    @Path u třídy udává, pod jakou URL bude služba dostupná.
    @GET definuje, jaká bude použita HTTP metoda.
    @Path u metody definuje, jaké bude pořadí parametrů. Nebo přesněji: jaký tvar bude mít zbytek URL.
    @Produces udává MIME typ v těle HTTP odpovědi.
    @PathParam mapuje parametry metody na části v anotaci @Path

  7. Nyní je už možné aplikaci deployovat na server a zkusit ji zavolat např. z webového prohlížeče: http://localhost:8080/example-rest/resources/names/Adam/Oliva

  8. Proč URL vypadá právě takhle? Vysvětlím:
    example-rest -název aplikace
    resources -definováno ve web.xml nebo anotací @ApplicationPath
    names -definováno anotací třídy @Path
Pokud máte zájem o pokračování tohoto blogu, informujte mě o tom. Co lze očekávat příště:
  • služba vracející odpověď jako xml
  • služba očekávající parametry jako parametry URL
  • tvorba klienta s využitím implementace Jersey.
  • služba očekávající na vstupu XML dokument.