Saturday, February 16, 2019

Writing Telegram Bots in Java - some quick thoughts

So I've started writing some Java Telegram Bot in Java.

Good parts


  • Incredibly easy to use and setup
  • Reasonably documented (it could be better, actually)
  • Very easy to learn, Telegram has some great ideas.

Bad parts

  • Telegram commands suggestions, I don't know why, does not accept manually entered parameters to the commands. If you choose from the [/] button, it just sends the command (instead of adding it to the input text, letting the user to add some parameter BEFORE sending). It's a natural thing for me and makes something that should be easy a little bit complex.
  • API could have some built-in parameters parsing tools.
  • Using reflection to find methods that return Reply makes the code confusing and not very reliable. I'd use interfaces instead.
  • The Godfather bot is a good idea, except when I need to manage the commands of my bot using it. It's cumbersome. I'd rather use a regular web UI for it.
It's a pretty addicting thing, actually. It's a lot of fun to use. Too bad some bots are offline

Monday, March 23, 2015

Adding a date range filter to Primefaces 4 DataTable

This post is an addendum to this other post 

This is some sort of a hack, but it seems to work.

First, the xhtml

                    <f:facet name="header">

                        <p:outputLabel
                            for="from"
                            value="Filter from:" />
                        <p:calendar
                            id="from"
                            value="#{sitePasswordHistoryMB.filterFrom}"
                            pattern="MM/dd/yyyy HH:mm:ss">
                            <p:ajax
                                event="dateSelect"
                                listener="#{sitePasswordHistoryMB.handleUserFilterSelection}"
                                update=":am:pas" />
                        </p:calendar>


                        <p:outputLabel
                            for="to"
                            value="&#160;Filter until:" />
                        <p:calendar
                            id="to"
                            value="#{sitePasswordHistoryMB.filterTo}"
                            pattern="MM/dd/yyyy HH:mm:ss">
                            <p:ajax
                                event="dateSelect"
                                listener="#{sitePasswordHistoryMB.handleUserFilterSelection}"
                                update=":am:pas" />
                        </p:calendar>

                    </f:facet>


The Managed Bean

    private Date filterFrom;
    private Date filterTo;

    @PostConstruct
    public void init() {
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DATE, -1);
       
        filterFrom = cal.getTime(); //yesterday
        filterTo = new Date(); //today
       
        refresh();
    }

    private void refresh() {
        try {
            this.model = new LazyDataModel<SitePasswordHistory>(){
                private static final long    serialVersionUID    = 1L;
                @Override
                public List<SitePasswordHistory> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, String> filters) {
                   
                    filters.put("from", String.valueOf(filterFrom.getTime()));
                    filters.put("to", String.valueOf(filterTo.getTime()));
                   
                    List<SitePasswordHistory> result = sitePasswordHistoryEJB.getResultList(first, pageSize, sortField, sortOrder, filters);
                   
                    filters.put("from", String.valueOf(filterFrom.getTime()));
                    filters.put("to", String.valueOf(filterTo.getTime()));

                    model.setRowCount((int)sitePasswordHistoryEJB.count(filters));
                   
                    return result;
                }
            };
        } catch (Exception e) {
            jsfUtilEJB.addErrorMessage(e,"Could not list");
        }
    }


and down to the DAO

    public Collection<SitePasswordHistory> getAll(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, String> filters) {
       
        Date from = null;
        if (filters != null && filters.get("from") != null) {
            from = new Date(Long.parseLong(filters.get("from")));
            filters.remove("from");
        }
       
        Date to = null;
        if (filters != null && filters.get("to") != null) {
            to = new Date(Long.parseLong(filters.get("to")));
            filters.remove("to");
        }
(...)


        Predicate filterCondition = cb.conjunction();
       
        if (from != null) {
            filterCondition = cb.and(filterCondition, cb.greaterThan(sitePasswordHistory.get(SitePasswordHistory_.ts), from));
        }
       
        if (to != null) {
            filterCondition = cb.and(filterCondition, cb.lessThan(sitePasswordHistory.get(SitePasswordHistory_.ts), to));
        }
       
        for (Map.Entry<String, String> filter : filters.entrySet()) {
(...)


I haven't tried yet, but I believe PrimeFaces 5 has a better support for these kind of filters, since the LazyDataModel API has been changed (thus making this post incompatible with the new API...) 

Sunday, March 8, 2015

Using Oracle OIM API (simplified from OIM doc)

import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;

import javax.security.auth.login.LoginException;

import oracle.iam.identity.exception.UserSearchException;
import oracle.iam.identity.usermgmt.api.UserManager;
import oracle.iam.identity.usermgmt.api.UserManagerConstants.AttributeName;
import oracle.iam.identity.usermgmt.vo.User;
import oracle.iam.platform.OIMClient;
import oracle.iam.platform.authz.exception.AccessDeniedException;
import oracle.iam.platform.entitymgr.vo.SearchCriteria;

public class NEASOIMClient {
    private static OIMClient oimClient;

    private void init() throws LoginException {
        System.setProperty("java.security.auth.login.config", "/mypathto/conf/authwl.conf");
        System.setProperty("APPSERVER_TYPE","wls");

        System.out.println("Creating client....");
        String ctxFactory = "weblogic.jndi.WLInitialContextFactory";
        String serverURL = "t3://127.0.0.1:14000";
        String username = "xelsysadm";
        String password = "password";
        Hashtable<String,String> env = new Hashtable<String,String>();
        env.put(OIMClient.JAVA_NAMING_FACTORY_INITIAL, ctxFactory);
        env.put(OIMClient.JAVA_NAMING_PROVIDER_URL, serverURL);
        oimClient = new OIMClient(env);
        System.out.println("Logging in");
        oimClient.login(username, password.toCharArray());
        System.out.println("Log in successful");
    }

    private void getUserLogin(String psFirstName) {
        UserManager userService = oimClient.getService(UserManager.class);
        Set<String> retAttrs = new HashSet<String>();
        retAttrs.add(AttributeName.USER_LOGIN.getId());
        SearchCriteria criteria = new SearchCriteria(AttributeName.FIRSTNAME.getId(), psFirstName, SearchCriteria.Operator.EQUAL);
        try {
            List<User> users = userService.search(criteria, retAttrs, null);
            for (int i = 0; i < users.size(); i++) {
                System.out.println("First Name : " + psFirstName + "--" + users.get(i).getLogin());
            }
        } catch (AccessDeniedException ade) {
            ade.printStackTrace();
        } catch (UserSearchException use) {
            use.printStackTrace();
        }
    }

    public static void main(String args[]) {
        try {
            NEASOIMClient oimSample = new NEASOIMClient();
            oimSample.init();
            oimSample.getUserLogin("Leo*");
            oimClient.logout();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Tuesday, October 14, 2014

Some nice links

One of my favorite blogs is O'Reilly's, where he always posts 4 interesting links everyday.

I don't have O'Reilly's dedication or knowledge, but here go my list of interesting links I've been reading lately.

  1. Stability Patterns applied to a RESTful architecture. http://www.javaworld.com/article/2824163/application-performance/stability-patterns-applied-in-a-restful-architecture.html#tk.rss_all
  2. 14 Mistakes that will kill your indie dev shop - http://www.javaworld.com/article/2825874/java-app-dev/the-14-mistakes-that-will-kill-your-independent-development-shop.html#tk.rss_all
  3. Tiny Dancing Drones - https://www.indiegogo.com/projects/dancing-dronies-nexgen-drone-technology
  4. Whatsapp goes VOIP - http://www.geektime.com/2014/10/12/exclusive-whatsapp-to-pioneer-voip-call-recording/
  5. Batteries that recharge fast - http://www.extremetech.com/computing/191924-your-next-smartphone-or-ev-will-recharge-to-70-in-just-two-minutes-thanks-to-new-lithium-ion-battery-tech
  6. Is 8-bit dead? (not really a discussion, just a fancy old magazine cover) - http://www.retroist.com/2014/10/14/is-8-bit-dead/
  7. JPA Gotchas (excellent) - https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=1975
  8. Real World REST - https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=2105
  9. JSF Best Practices - https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=2518
  10. JMS Best Practices - https://oracleus.activeevents.com/2014/connect/sessionDetail.ww?SESSION_ID=3153

Wednesday, September 17, 2014

Embedding a XMPP server inside your JSF Web Application using Vysper, TomEE and PrimeFaces

I have an application that needs to notify the user when some job is done. It uses JSF and Primefaces, so this sort of notification could be implemented using atmosphere (aka Push).

But another funny approach is to use a XMPP server embedded in your java web application. Ok, ok, you don't have to embed it, you can just run an instance of a industrial-strenght XMPP server such as Openfire and Tigase. But hey, we're just playing a little, so I'll show you how to do it using Vysper, a little proof-of-concept developed using Apache Mina, that is simple enough for a few minutes play.

Before showing how to do it, it's nice to remember that threads and JEE applications usually don't mix, so we can play, but we must be aware of what are we doing.

First, you're going to need these JARs, most of them come from Vysper. Just a few huh?
  • aopalliance-1.0.jar
  • commons-codec-1.4.jar
  • commons-collections-3.1.jar
  • commons-io-1.4.jar
  • commons-lang-2.5.jar
  • commons-logging-1.1.jar
  • concurrent-1.3.4.jar
  • derby-10.2.1.6.jar
  • dnsjava-2.0.8.jar
  • ehcache-core-2.2.0.jar
  • fontbox-0.1.0.jar
  • jackrabbit-api-1.5.0.jar
  • jackrabbit-core-1.5.3.jar
  • jackrabbit-jcr-commons-1.5.3.jar
  • jackrabbit-spi-1.5.0.jar
  • jackrabbit-spi-commons-1.5.0.jar
  • jackrabbit-text-extractors-1.5.0.jar
  • jcl-over-slf4j-1.5.3.jar
  • jcr-1.0.jar
  • jempbox-0.2.0.jar
  • jetty-continuation-7.2.1.v20101111.jar
  • jetty-http-7.2.1.v20101111.jar
  • jetty-io-7.2.1.v20101111.jar
  • jetty-security-7.2.1.v20101111.jar
  • jetty-server-7.2.1.v20101111.jar
  • jetty-servlet-7.2.1.v20101111.jar
  • jetty-util-7.2.1.v20101111.jar
  • jetty-websocket-7.2.1.v20101111.jar
  • log4j-1.2.14.jar
  • lucene-core-2.3.2.jar
  • mina-core-2.0.2.jar
  • nbxml-0.7.jar
  • nekohtml-1.9.7.jar
  • pdfbox-0.7.3.jar
  • poi-3.0.2-FINAL.jar
  • poi-scratchpad-3.0.2-FINAL.jar
  • primefaces-4.0.jar
  • servlet-api-2.5.jar
  • slf4j-api-1.5.3.jar
  • slf4j-log4j12-1.5.3.jar
  • smack-3.1.0.jar
  • smackx-3.1.0.jar
  • spec-compliance-0.7.jar
  • spring-aop-3.0.5.RELEASE.jar
  • spring-asm-3.0.5.RELEASE.jar
  • spring-beans-3.0.5.RELEASE.jar
  • spring-context-3.0.5.RELEASE.jar
  • spring-core-3.0.5.RELEASE.jar
  • spring-expression-3.0.5.RELEASE.jar
  • vysper-core-0.7.jar
  • vysper-websockets-0.7.jar
  • xep0045-muc-0.7.jar
  • xep0060-pubsub-0.7.jar
  • xep0124-xep0206-bosh-0.7.jar
  • xercesImpl-2.8.1.jar
  • xml-apis-1.3.03.jar
Now, copy from Vysper the bogus certificate, so your XMPP server can "work" under a "secure" channel. It's called bogus_mina_tls.cert.

My xhtml looks like this

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:p="http://primefaces.org/ui">
<h:head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Messaging Prototype</title>
    <link rel="icon" type="image/png" href="favicon.ico"></link>   
</h:head>
<h:body>   
    <h:outputStylesheet library="css" name="style.css"  />

    <p:ajaxStatus style="width:16px;height:16px;" id="ajaxStatusPanel"> 
        <f:facet name="start"> 
            <h:graphicImage value="./ajaxloading.gif" /> 
        </f:facet> 
         
        <f:facet name="complete"> 
            <h:outputText value="" /> 
        </f:facet> 
    </p:ajaxStatus> 

    <h:form>   
        <p:messages id="messages" showDetail="true" autoUpdate="true" closable="true" />
        <p:spacer height="10" />
        <p:panel>
            <h:panelGrid columns="2">
                <p:commandButton value="Enter" action="#{messagingMB.sendMessage}" />
            </h:panelGrid>
        </p:panel>
        <p:spacer height="10" />
    </h:form>
</h:body>
</html>


Pretty simple huh? The Managed Bean is also easy.



import java.io.Serializable;

import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

import org.jivesoftware.smack.XMPPException;

@ManagedBean
@ViewScoped
public class MessagingMB implements Serializable {

    private static final long    serialVersionUID    = -9092497421080796430L;
   
    @EJB
    private JSFUtilEJB jsfUtilEJB;
   
    @PostConstruct
    public void init() {
    }

    public void sendMessage() {
        try {
            new BasicClient().test();
        } catch (XMPPException e) {
            jsfUtilEJB.addErrorMessage(e,"Could not send");
        }
    }
}


and of course, the EJBs



import javax.ejb.Stateless;
import javax.faces.application.FacesMessage;
import javax.faces.application.FacesMessage.Severity;
import javax.faces.context.FacesContext;

@Stateless
public class JSFUtilEJB {

    @SuppressWarnings("unchecked")
    public <T> T findBean(String beanName) {
        FacesContext context = FacesContext.getCurrentInstance();
        return (T) context.getApplication().evaluateExpressionGet(context, "#{" + beanName + "}", Object.class);
    }

    public long addErrorMessage(String msg) {
        return addMessage(null,FacesMessage.SEVERITY_ERROR,msg);
    }
    public long addErrorMessage(Exception e,String summary){
        return addMessage(e,FacesMessage.SEVERITY_ERROR,summary);
    }
    public long addFatalErrorMessage(Exception e,String summary){
        return addMessage(e,FacesMessage.SEVERITY_FATAL,summary);
    }
    public long addInfoMessage(String summary){
        return addMessage(null,FacesMessage.SEVERITY_INFO,summary);
    }
    public long addWarnMessage(Exception e,String summary){
        return addMessage(e,FacesMessage.SEVERITY_WARN,summary);
    }

    public long addErrorMessage(Exception e) {
        return addMessage(e,FacesMessage.SEVERITY_ERROR,e.getMessage(),e.getClass().getSimpleName());
    }

    private long addMessage(Exception e,Severity severity, String summary) {
        FacesContext context = FacesContext.getCurrentInstance();
        String clientId = null;
       
        long id = -1;
       
        if (e != null){
            id = printStackTrace(e);
            FacesMessage facesMessage = null;
            if (e.getCause() instanceof org.apache.openjpa.persistence.EntityExistsException){
                facesMessage = new FacesMessage(severity,"[Error: #"+id+"] "+summary,"You are trying are to add a new object that already exists or your're trying to violate a unique constraint)" );   
            }else{
                facesMessage = new FacesMessage(severity,"[Error: #"+id+"] "+summary,e.getMessage() );
            }
            context.addMessage(clientId , facesMessage );
        }else{
            FacesMessage facesMessage = new FacesMessage(severity,summary," ");
            context.addMessage(clientId , facesMessage );
        }

        return id;
    }

    private long addMessage(Exception e,Severity severity, String summary, String detail) {
        FacesContext context = FacesContext.getCurrentInstance();
        String clientId = null;
       
        long id = -1;
        if (e != null){
            id = printStackTrace(e);       
            FacesMessage facesMessage = new FacesMessage(severity,"["+id+"] "+summary,detail );
            context.addMessage(clientId , facesMessage );
        }else{
            FacesMessage facesMessage = new FacesMessage(severity,summary,detail );
            context.addMessage(clientId , facesMessage );
        }
       
        return id;
    }

    public long printStackTrace(Exception e){
        long uniqueId = System.currentTimeMillis();
        return uniqueId;
    }

}


and



import java.io.File;
import java.io.Serializable;

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;

import org.apache.vysper.mina.TCPEndpoint;
import org.apache.vysper.storage.StorageProviderRegistry;
import org.apache.vysper.storage.inmemory.MemoryStorageProviderRegistry;
import org.apache.vysper.xmpp.addressing.EntityImpl;
import org.apache.vysper.xmpp.authorization.AccountManagement;
import org.apache.vysper.xmpp.server.XMPPServer;

@Startup
@Singleton
public class XmppEJB implements Serializable {
   
    /**
     * <br>06/09/2014
     */
    private static final long serialVersionUID = 1L;
    private boolean started;

    @PostConstruct
    public void init() {
        try {
            // choose the storage you want to use
    //        StorageProviderRegistry providerRegistry = new JcrStorageProviderRegistry();
            StorageProviderRegistry providerRegistry = new MemoryStorageProviderRegistry();
   
            final AccountManagement accountManagement = (AccountManagement) providerRegistry.retrieve(AccountManagement.class);
   
            if(!accountManagement.verifyAccountExists(EntityImpl.parse("user1@vysper.org"))) {
                accountManagement.addUser(EntityImpl.parse("user1@vysper.org"), "password");
            }
            if(!accountManagement.verifyAccountExists(EntityImpl.parse("user2@vysper.org"))) {
                accountManagement.addUser(EntityImpl.parse("user2@vysper.org"), "password");
            }
           
            XMPPServer server = new XMPPServer("vysper.org");
            server.addEndpoint(new TCPEndpoint());
            server.setStorageProviderRegistry(providerRegistry);
            server.setTLSCertificateInfo(new File("/path/to/bogus_mina_tls.cert"), "boguspw");
           
            server.start();
            System.out.println("server is running...");
        } catch (Exception e) {
            e.printStackTrace();
        }
       
        started = true;
    }

    public boolean isStarted() {
        return this.started;
    }
   
   
   
}


and the basic client, which comes from Vysper.

import java.util.Date;

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smackx.packet.Time;
import org.jivesoftware.smackx.packet.Version;

public class BasicClient {

    static class IQListener implements PacketListener {

        public void processPacket(Packet packet) {
            IQ iq = (IQ) packet;
            String iqString = iq.toString();
            System.out.println("T" + System.currentTimeMillis() + " IQ: "
                    + iqString + ": " + iq.toXML());
        }
    }

    static class PresenceListener implements PacketListener {

        public void processPacket(Packet packet) {
            Presence presence = (Presence) packet;
            String iqString = presence.toString();
            final PacketExtension extension = presence
                    .getExtension("http://jabber.org/protocol/caps");
            if (extension != null)
                System.out.println("T" + System.currentTimeMillis() + " Pres: "
                        + iqString + ": " + presence.toXML());
        }
    }

    public void test() throws XMPPException {

        String me = "user2@vysper.org";
        String to = "user1@vysper.org";

        try {
            ConnectionConfiguration connectionConfiguration = new ConnectionConfiguration(
                    "localhost");
            connectionConfiguration.setCompressionEnabled(false);
            connectionConfiguration.setSelfSignedCertificateEnabled(true);
            connectionConfiguration.setExpiredCertificatesCheckEnabled(false);
//            connectionConfiguration.setDebuggerEnabled(true);
            connectionConfiguration
                    .setSecurityMode(ConnectionConfiguration.SecurityMode.required);
//            XMPPConnection.DEBUG_ENABLED = true;
            XMPPConnection connection = new XMPPConnection(
                    connectionConfiguration);
            connection.connect();

            SASLAuthentication saslAuthentication = connection
                    .getSASLAuthentication();
            saslAuthentication.authenticate(me, "password", "test");
            connection.login(me, "pqssword");

            connection.getRoster().setSubscriptionMode(
                    Roster.SubscriptionMode.accept_all);

            connection.addPacketListener(new IQListener(), new PacketFilter() {
                public boolean accept(Packet packet) {
                    return packet instanceof IQ;
                }
            });

            connection.addPacketListener(new PresenceListener(),
                    new PacketFilter() {
                        public boolean accept(Packet packet) {
                            return packet instanceof Presence;
                        }
                    });

            Chat chat = null;
            if (to != null) {
                Presence presence = new Presence(Presence.Type.subscribe);
                presence.setFrom(connection.getUser());
                String toEntity = to;
                presence.setTo(toEntity);
                connection.sendPacket(presence);

                chat = connection.getChatManager().createChat(toEntity,
                        new MessageListener() {
                            public void processMessage(Chat inchat,
                                    Message message) {
                                System.out.println("log received message: "
                                        + message.getBody());
                            }
                        });
            }

            connection.sendPacket(new Presence(Presence.Type.available,
                    "pommes", 1, Presence.Mode.available));

            Thread.sleep(1000);

            // query server version
            sendIQGetWithTimestamp(connection, new Version());

            // query server time
            sendIQGetWithTimestamp(connection, new Time());

            chat.sendMessage("Hello " + to + " at " + new Date());

            connection.disconnect();
        } catch (Throwable e) {
            e.printStackTrace(); // To change body of catch statement use File |
                                    // Settings | File Templates.
        }
        System.out.println("bye");
    }

    private static void sendIQGetWithTimestamp(XMPPConnection connection, IQ iq) {
        iq.setType(IQ.Type.GET);
        connection.sendPacket(iq);
        System.out.println("T" + System.currentTimeMillis()
                + " IQ request sent");
    }
}


We're almost set. Of course, now we need a XMPP client, such as Pidgin

First, let me just say thanks to this blog, because I don't know why, Vysper site has little to no information about how to configure Pidgin, so this blog post was really useful.

Let me show you how my pidgin user looks like








I know, it's in Portuguese.


And that's it. We're all set. Start your JSF web application and play.

Notice that the communication is bidirectional, so you can just use your XMPP client to send commands to the server. To do that, you just have to change this listener

                chat = connection.getChatManager().createChat(toEntity,
                        new MessageListener() {
                            public void processMessage(Chat inchat,
                                    Message message) {
                                System.out.println("log received message: "
                                        + message.getBody());
                            }
                        });


I wonder if we could just create a DSL to process some commands and if we could find some autocomplete pidgin plugin to write commands using this DSL. Suggestions are welcome. ;-)

ps. The EJB does not shutdown the server gracefully. But I bet there's some EJB annotation to do that in an event of a server shutdown.

Saturday, January 18, 2014

How to send an array of objects using Soap UI


Let me show you an example and maybe it can help you.

    package mypackage;

    import java.io.Serializable;
    import java.util.Date;

    public class Thing implements Serializable{

        private static final long serialVersionUID = 4205832525113691806L;
        private String name;
        private Date date;
        private Long longg;

        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Date getDate() {
            return date;
        }
        public void setDate(Date date) {
            this.date = date;
        }
        public Long getLongg() {
            return longg;
        }
        public void setLongg(Long longg) {
            this.longg = longg;
        }
        @Override
        public String toString() {
            return "Thing [name=" + name + ", date=" + date + ", longg=" + longg + "]";
        }
    }

and the web service

    package mypackage;

    import java.util.Arrays;

    import javax.ejb.Stateless;
    import javax.jws.WebService;

    @WebService
    @Stateless
    public class WS {   
        public void doSomething(Thing[] things){
            System.out.println(Arrays.asList(things));
        }
    }

then if you use soapUI to generate the request for you, you'll get something like this



and the result will be (in your server logs)

    [Thing [name=aeoliam venit, date=Sun Sep 28 22:49:45 BRT 2008, longg=10]]

but, of course, you want to send an array of these things, so...





and voila, the result will be

    [Thing [name=aeoliam venit, date=Sun Sep 28 22:49:45 BRT 2008, longg=10], Thing [name=aeoliam venit, date=Sun Sep 28 22:49:45 BRT 2008, longg=10]]

gotcha? :-)

it´s incredible that we just can't find anywhere this answer.

Tuesday, January 14, 2014

Primefaces DataTable Lazy Loading with pagination, filtering and sorting using JPA Criteria, @ViewScoped

Primefaces datatable lazy pagination works, but I was really frustrated after searching for a full example using Criteria in the web. So I've mixed solutions from

  • http://stackoverflow.com/questions/13972193/how-to-query-data-for-primefaces-datatable-with-lazy-loading-and-pagination
  • http://www.primefaces.org/showcase/ui/datatableLazy.jsf
  • http://www.javacodegeeks.com/2012/04/lazy-jsf-primefaces-datatable.html

To put all together in a complete example using

  • Primefaces 4.0
  • TomEE 1.6.0+ 
  • Criteria JPA
With


  • Filtering
  • Sorting
  • Pagination
  • Using @ViewScoped Managed Beans
So let's go

xhtml snippet

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:p="http://primefaces.org/ui">
<h:body>
    <h:form id="am">

            <p:dataTable
                var="element"
                value="#{inventoryManagerMB.model}"
                lazy="true"
                paginator="true"
                rows="10"
                paginatorTemplate="{RowsPerPageDropdown} {FirstPageLink} {PreviousPageLink} {CurrentPageReport} {NextPageLink} {LastPageLink}"
                rowsPerPageTemplate="10,50,100"
                id="sites">
 
            <p:column sortBy="id" filterBy="id" filterMatchMode="contains" headerText="ID">                
                <h:outputText value="#{element.id}" />
            </p:column>
            <p:column sortBy="name" filterBy="name" filterMatchMode="contains" headerText="Name">                
                <h:outputText value="#{element.name}" />
            </p:column>
            <p:column sortBy="siteType.name" filterBy="siteType.name" filterMatchMode="contains" headerText="Type">                
                <h:outputText value="#{element.siteType.name}" />
            </p:column>
            <p:column sortBy="ip" filterBy="ip" filterMatchMode="contains" headerText="IP">                
                <h:outputText value="#{element.ip} " />
            </p:column>
            <p:column sortBy="description" filterBy="description" filterMatchMode="contains" headerText="Description">                
                <h:outputText value="#{element.description}" />
            </p:column>
            </p:dataTable>
    </h:form>
</h:body>
</html>



 ManagedBean

@ManagedBean
@ViewScoped
public class InventoryManagerMB implements Serializable {

    private static final long serialVersionUID = -1201944101993687165L;

    @EJB
    private InventoryManagerEJB inventoryManagerEJB;
   
    private LazyDataModel<Site> model;
 



     @PostConstruct
    public void init() {
        try {
            this.model = new LazyDataModel<Site>(){
                private static final long    serialVersionUID    = 1L;
                @Override
                public List<Site> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, String> filters) {
                    List<Site> result = inventoryManagerEJB.getResultList(first, pageSize, sortField, sortOrder, filters);
                    model.setRowCount(inventoryManagerEJB.count(sortField, sortOrder, filters));
                    return result;
                }
            };
}


    public LazyDataModel<Site> getModel() {
        return model;
    }

    public void setModel(LazyDataModel<Site> model) {
        this.model = model;
    }
 

(...)

EJB

@Stateless
public class InventoryManagerEJB {
   
    @Inject
    private BaseService baseService;


    public List<Site> getResultList(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, String> filters) {
        List<Site> all = new ArrayList<Site>();
        all.addAll(this.baseService.getSiteDAO().getAll(first,pageSize,sortField,sortOrder,filters));
        return all;
    }

    public int count(String sortField, SortOrder sortOrder, Map<String, String> filters) {
        return this.baseService.getSiteDAO().getAll(-1,-1,null,null,filters).size();
    }
}


BaseService

@ApplicationScoped
public class BaseService implements Serializable{
   
    private static Logger log = Logger.getLogger(BaseService.class);
   
    /*
     * persistence
     */
   
    private static final long serialVersionUID = 588696475267901772L;

    @PersistenceContext

    private EntityManager entityManager;

    private SiteDAO siteDAO;


    @PostConstruct
    public void init() {
            siteDAO = new SiteDAO(entityManager);
    }



    public SiteDAO getSiteDAO() {
        return siteDAO;
    }


SiteDAO

public class SiteDAO extends GenericDAO<Site>{

    public SiteDAO(EntityManager entityManager) {
        super(entityManager);
    }


    public Collection<Site> getAll(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, String> filters) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Site> q = cb.createQuery(Site.class);
        Root<Site> site = q.from(Site.class);
        Join<Site,SiteType> siteType = site.join(Site_.siteType);
        q.select(site);


        Path<?> path = getPath(sortField, site, siteType);
        if (sortOrder == null){
            //just don't sort
        }else if (sortOrder.equals(SortOrder.ASCENDING)){
            q.orderBy(cb.asc(path));
        }else if (sortOrder.equals(SortOrder.DESCENDING)){
            q.orderBy(cb.asc(path));
        }else if (sortOrder.equals(SortOrder.UNSORTED)){
            //just don't sort
        }else{
            //just don't sort
        }
       
        //filter
        Predicate filterCondition = cb.conjunction();
        for (Map.Entry<String, String> filter : filters.entrySet()) {
            if (!filter.getValue().equals("")) {
                //try as string using like
                Path<String> pathFilter = getStringPath(filter.getKey(), site, siteType);
                if (pathFilter != null){
                    filterCondition = cb.and(filterCondition, cb.like(pathFilter, "%"+filter.getValue()+"%"));
                }else{
                    //try as non-string using equal
                    Path<?> pathFilterNonString = getPath(filter.getKey(), site, siteType);
                    filterCondition = cb.and(filterCondition, cb.equal(pathFilterNonString, filter.getValue()));
                }
            }
        }
        q.where(filterCondition);
       
        //pagination
        TypedQuery<Site> tq = entityManager.createQuery(q);
        if (pageSize >= 0){
            tq.setMaxResults(pageSize);
        }
        if (first >= 0){
            tq.setFirstResult(first);
        }
        return tq.getResultList();
    }

    private Path<?> getPath(String field, Root<Site> site, Join<Site, SiteType> siteType) {
        //sort
        Path<?> path = null;
        if (field == null){
            path = site.get(Site_.name);
        }else{
            switch(field){
                case "id":
                    path = site.get(Site_.id);
                    break;
                case "name":
                    path = site.get(Site_.name);
                    break;
                case "siteType.name":
                    path = siteType.get(SiteType_.name);
                    break;
                case "ip":
                    path = site.get(Site_.ip);
                    break;
                case "description":
                    path = site.get(Site_.description);
                    break;
            }
        }
        return path;
    }

    private Path<String> getStringPath(String field, Root<Site> site, Join<Site, SiteType> siteType) {
        //sort
        Path<String> path = null;
        if (field == null){
            path = site.get(Site_.name);
        }else{
            switch(field){
                case "id":
                    path = null;
                    break;
                case "name":
                    path = site.get(Site_.name);
                    break;
                case "siteType.name":
                    path = siteType.get(SiteType_.name);
                    break;
                case "ip":
                    path = site.get(Site_.ip);
                    break;
                case "description":
                    path = site.get(Site_.description);
                    break;
            }
        }
        return path;
    }
}


Entity

@Entity
@Table(uniqueConstraints=@UniqueConstraint(columnNames={"name"}))
public class Site implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = 8008732613898597654L;
   
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
   
    @Nameable
    @Column(nullable=false)
    private String name;
   
    private String ip;   
   
    private String description;
   
    @ManyToOne
    @JoinColumn(name="siteTypeId")
    private SiteType siteType;


(...)
}


That's it. Enjoy.