Menu

quinta-feira, 15 de abril de 2021

Como resolver o erro br.gov.sp.fazenda.dsge.model.dao.util.DSGEDAOHelper.currentSession(DSGEDAOHelper.java:103) no Emissor de NF-e do Sebrae no Linux

Ontem precisei instalar o emissor de nota fiscal eletrônica do Sebrae no computador do meu pai. Ele faz vendas pelo Mercado Livre há muito tempo e recentemente passou a ser necessário que todo vendedor faça a emissão de NF-e. Há mais de 5 anos meus pais usam a distribuição Linux Ubuntu sem nenhum problema nesse período.

Eu tenho usado a distribuição Fedora nos últimos anos e testei a instalação no meu computador para identificar se ela seria possível. O próprio manual de instalação indica o suporte a Linux, então não imaginei ter problemas.

De fato, segui as instruções do manual e a instalação no Fedora funcionou sem problemas. Passei as mesmas instruções ao meu pai, mas surpreendentemente ocorreu um erro:

java.lang.NullPointerException
	at br.gov.sp.fazenda.dsge.model.dao.util.DSGEDAOHelper.currentSession(DSGEDAOHelper.java:103)
	at br.gov.sp.fazenda.dsge.model.dao.util.DSGEDAOHelper.beginTransaction(DSGEDAOHelper.java:130)
	at br.gov.sp.fazenda.dsge.model.dao.util.DSGEDAOInterceptorHelper.intercept(DSGEDAOInterceptorHelper.java:54)
	at br.gov.sp.fazenda.dsen.model.business.ParametroBusiness$$EnhancerByCGLIB$$4f0e102.carregaParametrosAppContext()
	at br.gov.sp.fazenda.dsen.model.facade.ParametroFacade.carregaParametrosAppContext(ParametroFacade.java:40)
	at br.gov.sp.fazenda.dsen.controller.DSENSystemController.c(DSENSystemController.java:104)
	at br.gov.sp.fazenda.dsen.controller.DSENSystemController.execAfterStartApp(DSENSystemController.java:67)
	at br.gov.sp.fazenda.dsge.controller.DSGESystemController.startApp(DSGESystemController.java:151)
	at br.gov.sp.fazenda.dsen.view.DSENDesktop$1.run(DSENDesktop.java:138)

A principio achei que fosse algo trivial, talvez a versão do Java ou algo assim, mas o erro se mostrou bem mais complicado para resolver.

Pesquisando no Google encontrei várias pessoas com o mesmo erro como [1], [2] e [3]. Alguns relatos são tem quase 13 anos e sugerem os mesmos passos do manual para criação do diretório /database e definição de permissão de acesso com o chmod ou então recomendam que seja utilizada outra versão do Java. Experimentei todas as soluções indicadas, mas nada resolveu o erro.

Após várias experiências para entender a razão do emissor funcionar apenas no Fedora, minha última suspeita estava na versão do icedtea-web. O Fedora é a única distribuição que já empacota a versão 2.0 alpha, enquanto praticamente todas as outras distribuições linux incluem apenas a versão 1.8 em seus repositórios.

Acessei o repositório do projeto no Github e constatei que não existiam pacotes deb para instalação da versão 2.0, estando disponível apenas o código fonte. As instruções para build do projeto também estavam desatualizadas e não funcionavam mais.

Essa tarefa, que concentra discussões sobre build da versão 2.0 que me ajudou a entender como fazer o build do icedtea. Para auxiliar quem se deparar com o mesmo problema e considerando que essa solução não está documentada em nenhum lugar, deixo as instruções abaixo.

Primeiro passo, instale as dependências para o build:

sudo apt install mvn git cargo openjdk-8-jdk

É importante destacar que o build funcionou apenas com a JDK8, não consegui fazer funcionar com a JDK11.

Por alguma razão, o build não utiliza a variável JAVA_HOME definida no ambiente, então é necessário definir a variável JRE:

export JRE=/usr/lib/jvm/java-8-openjdk-amd64

Execute o build do projeto utilizando o Maven:

mvn clean install -DskipTests -P launchers

Se o build ocorrer sem erros o executável javaws estará no diretório launchers/target/bin/ e poderá ser utilizado para executar o arquivo JNLP do emissor de NF-e.

quarta-feira, 9 de dezembro de 2015

FoundationFaces CommandButton

While the Button component is used for navigation through HTTP GET requests, the CommandButton component generated an input that submits the parent form by default. A more detailed comparative between both can be found in this Balusc response.

Since I'm sticking to the default JSF renderers as much as possible, CommandButtonUI will be almost identical to ButtonUI. We need just to apply the style classes, which are exactly the same as the button component.

Basically, the only difference will be the tagName and the extended class (HtmlCommandButton instead of HtmlOutcomeTargetButton).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package org.foundation.faces.components;

import java.io.IOException;
import java.util.StringJoiner;
import javax.faces.application.ResourceDependencies;
import javax.faces.application.ResourceDependency;
import javax.faces.component.FacesComponent;
import javax.faces.component.html.HtmlCommandButton;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

/**
 *
 * @author hfluz <hfluz@uel.br>
 */
@ResourceDependencies({
    @ResourceDependency(library = "foundation", name = "css/foundation.min.css")
})
@FacesComponent(createTag = true, namespace = "http://foundation.faces.com/taglib",
        tagName = "commandButton")
public class CommandButtonUI extends HtmlCommandButton {

    enum PropertyKeys {
        sizing, expanded, coloring, hollow, disabled;
    }

    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        this.setStyleClass(buildStyleClass());
        setDisabled(false);
        super.encodeBegin(context);
    }

    public String buildStyleClass() {
        StringJoiner styleClass = new StringJoiner(" ");
        styleClass.add("button");
        if (getStyleClass() != null) {
            styleClass.add(getStyleClass());
        }
        if (getSizing() != null && getSizing().matches("(.*)(tiny|small|normal|large)(.*)")) {
            styleClass.add(getSizing());
        }
        if (isExpanded()) {
            styleClass.add("expanded");
        }
        if (getColoring() != null && getColoring().matches("(.*)(secondary|success|alert|warning)(.*)")) {
            styleClass.add(getColoring());
        }
        if (isHollow()) {
            styleClass.add("hollow");
        }

        if (isDisabled()) {
            styleClass.add("disabled");
        }
        return styleClass.toString();
    }

    public String getSizing() {
        return (String) getStateHelper().eval(PropertyKeys.sizing, null);
    }

    public void setSizing(String sizing) {
        getStateHelper().put(PropertyKeys.sizing, sizing);
    }

    public Boolean isExpanded() {
        return (Boolean) getStateHelper().eval(PropertyKeys.expanded, Boolean.FALSE);
    }

    public void setExpanded(Boolean expanded) {
        getStateHelper().put(PropertyKeys.expanded, expanded);
    }

    public String getColoring() {
        return (String) getStateHelper().eval(PropertyKeys.coloring, null);
    }

    public void setColoring(String coloring) {
        getStateHelper().put(PropertyKeys.coloring, coloring);
    }

    public Boolean isHollow() {
        return (Boolean) getStateHelper().eval(PropertyKeys.hollow, Boolean.FALSE);
    }

    public void setHollow(Boolean hollow) {
        getStateHelper().put(PropertyKeys.hollow, hollow);
    }
}

domingo, 6 de dezembro de 2015

FoundationFaces Button

According to JSF 2 h:button documentation:

Render an HTML "input" element of type "button". The value of the component is rendered as the button text and the outcome of the component is used to determine the target URL which is activated by onclick. If "image" attribute is specified, render it as the value of the "src" attribute after passing it to the getResourceURL() method of the ViewHandler for this application, and passing the result through the encodeResourceURL() method of the ExternalContext.

We can extend HtmlOutcomeTargetButton, so we don't have to start from scratch.

This is our component class:

1
2
3
4
5
6
7
8
@ResourceDependencies({
    @ResourceDependency(library = "foundation", name = "css/foundation.min.css")
})
@FacesComponent(createTag = true, namespace = "http://foundation.faces.com/taglib",
        tagName = "button")
public class ButtonUI extends HtmlOutcomeTargetButton {
   //code
}
  • The @ResourceDependencies annotation defines which resources this component requires to be rendered.
  • The @FacesComponent annotation defines the name of our component tag and also which namespace it uses.
NOTE: Since JSF 2 we can define the component metadata through annotations instead of defining them through the taglib.xml file, however most IDEs won't be able to identify its attributes for auto-completion.

To make it look like Foundation Buttons we have to add class="button" to the generated HTML button and a few more attributes to our ButtonUI:

  • sizing: change the button size
  • expanded: expand the button to fill the whole row
  • coloring: change the button color related to a meaning
  • hollow: invert the colors of the background/border and the font
  • disabled: add opacity and prevent cursor events

Since all the attributes are related to the styleClass, I don't need to reimplement the HtmlOutcomeTargetButton enconding methods, all I have to do is to change the styleClass value:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        this.setStyleClass(buildStyleClass());
        super.encodeBegin(context);
    }
    public String buildStyleClass() {
        StringJoiner styleClass = new StringJoiner(" ");
        styleClass.add("button");
        if (getStyleClass() != null) {
            styleClass.add(getStyleClass());
        }
        if (getSizing() != null && getSizing().matches("(.*)(tiny|small|normal|large)(.*)")) {
            styleClass.add(getSizing());
        }
        if (isExpanded()) {
            styleClass.add("expanded");
        }
        if (getColoring() != null && getColoring().matches("(.*)(secondary|success|alert|warning)(.*)")) {
            styleClass.add(getColoring());
        }
        if (isHollow()) {
            styleClass.add("hollow");
        }
        if (isDisabled()) {
            styleClass.add("disabled");
        }
        return styleClass.toString();
    }

Let's try our component

page.xhtml:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:fo="http://foundation.faces.com/taglib">
    <h:head>
        <title>Buttons</title>
    </h:head>
    <h:body >
        <h:form prependId="false">
            <fo:button outcome="page2.xhtml" value="Tiny" sizing="tiny"/>
            <fo:button outcome="page2.xhtml" value="Small" sizing="small"/>
            <fo:button outcome="page2.xhtml" value="Normal"/>
            <fo:button outcome="page2.xhtml" value="Large" sizing="large"/>
            <fo:button outcome="page2.xhtml" value="Expanded small" sizing="small" expanded="true"/>
            <fo:button outcome="page2.xhtml" value="Expanded" expanded="true"/>
            <fo:button outcome="page2.xhtml" value="Secondary" coloring="secondary"/>
            <fo:button outcome="page2.xhtml" value="Success" coloring="success"/>
            <fo:button outcome="page2.xhtml" value="Alert" coloring="alert"/>
            <fo:button outcome="page2.xhtml" value="Warning" coloring="warning"/>
            <fo:button outcome="page2.xhtml" value="Hollow Secondary" coloring="secondary" hollow="true"/>
            <fo:button outcome="page2.xhtml" value="Hollow Success" coloring="success" hollow="true"/>
            <fo:button outcome="page2.xhtml" value="Hollow" hollow="true"/>
            <fo:button outcome="page2.xhtml" value="Hollow Alert" coloring="alert" hollow="true"/>
            <fo:button outcome="page2.xhtml" value="Hollow Warning" coloring="warning" hollow="true"/>
            <fo:button outcome="page2.xhtml" value="Disabled" disabled="true" />
        </h:form>
    </h:body>
</html>

This is the output:

The only button that wasn't displayed as expected is the disabled one because it's almost invisible. Let's see what happened.

According to foundation documentation, a disabled button should be rendered like this:

1
<a class="disabled button" href="#">Disabled Button</a>

However, our disabled button received one unexpected attribute:

1
<input value="Disabled" class="button disabled" disabled="disabled" type="button">

The disabled attribute is used by JSF to disable the button, but that way we are applying 2 differente disabled styles simultaneosly:

1
2
3
4
5
6
7
8
9
button.disabled {
    opacity: 0.25;
    cursor: not-allowed;
    pointer-events: none;
}
input:disabled, input[readonly], textarea:disabled, textarea[readonly] {
    background-color: #E6E6E6;
    cursor: default;
}

The simplest solution is to set disabled to false after we have already built our styleClass attribute and before it's used by HtmlOutcomeTargetButton encodeBegin method:

1
2
3
4
5
6
7
    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        this.setStyleClass(buildStyleClass());
        setDisabled(false);
        super.encodeBegin(context);
    }

Now our disable button is rendered how it should:

ButtonUI final source code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package org.foundation.faces.components;
import java.io.IOException;
import java.util.StringJoiner;
import javax.faces.application.ResourceDependencies;
import javax.faces.application.ResourceDependency;
import javax.faces.component.FacesComponent;
import javax.faces.component.html.HtmlOutcomeTargetButton;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
@ResourceDependencies({
    @ResourceDependency(library = "foundation", name = "css/foundation.min.css")
})
@FacesComponent(createTag = true, namespace = "http://foundation.faces.com/taglib",
        tagName = "button")
public class ButtonUI extends HtmlOutcomeTargetButton {
    enum PropertyKeys {
        sizing, expanded, coloring, hollow, disabled;
    }
    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        this.setStyleClass(buildStyleClass());
        setDisabled(false);
        super.encodeBegin(context);
    }
    public String buildStyleClass() {
        StringJoiner styleClass = new StringJoiner(" ");
        styleClass.add("button");
        if (getStyleClass() != null) {
            styleClass.add(getStyleClass());
        }
        if (getSizing() != null && getSizing().matches("(.*)(tiny|small|normal|large)(.*)")) {
            styleClass.add(getSizing());
        }
        if (isExpanded()) {
            styleClass.add("expanded");
        }
        if (getColoring() != null && getColoring().matches("(.*)(secondary|success|alert|warning)(.*)")) {
            styleClass.add(getColoring());
        }
        if (isHollow()) {
            styleClass.add("hollow");
        }
        if (isDisabled()) {
            styleClass.add("disabled");
        }
        return styleClass.toString();
    }
    public String getSizing() {
        return (String) getStateHelper().eval(PropertyKeys.sizing, null);
    }
    public void setSizing(String sizing) {
        getStateHelper().put(PropertyKeys.sizing, sizing);
    }
    public Boolean isExpanded() {
        return (Boolean) getStateHelper().eval(PropertyKeys.expanded, Boolean.FALSE);
    }
    public void setExpanded(Boolean expanded) {
        getStateHelper().put(PropertyKeys.expanded, expanded);
    }
    public String getColoring() {
        return (String) getStateHelper().eval(PropertyKeys.coloring, null);
    }
    public void setColoring(String coloring) {
        getStateHelper().put(PropertyKeys.coloring, coloring);
    }
    public Boolean isHollow() {
        return (Boolean) getStateHelper().eval(PropertyKeys.hollow, Boolean.FALSE);
    }
    public void setHollow(Boolean hollow) {
        getStateHelper().put(PropertyKeys.hollow, hollow);
    }
    public boolean isDisabled() {
        return (Boolean) getStateHelper().eval(PropertyKeys.disabled, false);
    }
    public void setDisabled(boolean disabled) {
        getStateHelper().put(PropertyKeys.disabled, disabled);
    }
}

quinta-feira, 3 de dezembro de 2015

Primefaces ImageCropper is rendered as a static image in Google Chrome

An issue I had this week was related to PrimefacesImageCropper component.

We have been using it for almost 2 years in our application that receives the photo of the university students and employees.

However, last week I found out a bug that was happening only on Google Chrome.

Sometimes when the user uploads his photo and clicks to the next step to load the image cropper, the cropper behavior just doesn't load. The users are presented to static image they can't crop and consequently they can't progress to the next step to send their photos.

If they go to the previous step and advance again or refresh the page, sometimes the cropper would load correctly.

I spent a few days trying to solve the issue and I considered the following possibilities:

  • Scripts loaded out of order (for example, imagecropper.js loaded before jquery.js or primefaces.js)
  • A resource that could not be loaded (like the crop.gif that is used to display the dotted line of the cropped area)
  • Something related to the fact that PrimeFaces.widget.ImageCropper extends from PrimeFaces.widget.DeferredWidget instead of PrimeFaces.widget.BaseWidget)
  • Our customized theme and template

I digged deep into Primefaces components and JavaScripts source code and I was able to check the possibilities above, but I wasn't able to track the issue.

The only thing I found out is that every time that the component worked, the JS code defined in the encodeScript method couldn't be found in the generated HTML code, while every time it didn't work I could find the JS code right below the image cropper markup.

In the end, I had to rewrite ImageCropperRenderer encodeScript method to render our own javascript to enable the crop feature. In our case we did that directly in our Primefaces fork, but you can do it in your project by creating the CustomImageCropperRenderer and declaring it in faces-config.xml.

Faces-config

1
2
3
4
5
6
7
    <render-kit>
        <renderer>
            <component-family>org.primefaces.component</component-family>
            <renderer-type>org.primefaces.component.ImageCropperRenderer</renderer-type>
            <renderer-class>your.package.CustomImageCropperRenderer</renderer-class>
        </renderer>
    </render-kit>

CustomImageCropperRenderer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package your.package;

import java.io.IOException;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.primefaces.component.imagecropper.ImageCropper;
import org.primefaces.component.imagecropper.ImageCropperRenderer;
import org.primefaces.model.CroppedImage;

/**
 *
 * @author hfluz
 */
public class CustomImageCropperRenderer extends ImageCropperRenderer {

    @Override
    protected void encodeScript(FacesContext context, ImageCropper cropper) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        String clientId = cropper.getClientId(context);
        writer.startElement("script", null);
        writer.writeAttribute("language", "Javascript", null);
        StringBuilder script = new StringBuilder();
        script.append("jQuery(function($){$('#")
                .append(clientId)
                .append("_image').Jcrop({");
        if (cropper.getMinSize() != null) {
            script.append("minSize:[").append(cropper.getMinSize()).append("]");
        }
        if (cropper.getMaxSize() != null) {
            script.append("maxSize:").append(cropper.getMaxSize());
        }
        script.append(",aspectRatio:").append(cropper.getAspectRatio());
        Object value = cropper.getValue();
        String select = null;
        if (value != null) {
            CroppedImage croppedImage = (CroppedImage) value;

            int x = croppedImage.getLeft();
            int y = croppedImage.getTop();
            int x2 = x + croppedImage.getWidth();
            int y2 = y + croppedImage.getHeight();

            select = "[" + x + "," + y + "," + x2 + "," + y2 + "]";
        } else if (cropper.getInitialCoords() != null) {
            select = "[" + cropper.getInitialCoords() + "]";
        }
        script.append(",setSelect:")
                .append(select)
                .append(",onChange: updateCoords")
                .append(",onSelect: updateCoords")
                .append(",onRelease: updateCoords")
                .append("});")
                .append("function updateCoords(c){$('#")
                .append(clientId)
                .append("_coords').val(c.x + \"_\" + c.y + \"_\" + c.w + \"_\" + c.h);};});");

        writer.write(script.toString());
        writer.endElement("script");
    }
}

Generated JavaScript Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
jQuery(function($) {
    $('#cropper_image').Jcrop({
        minSize: [102, 136],
        aspectRatio: 0.75,
        setSelect: null,
        onChange: updateCoords,
        onSelect: updateCoords,
        onRelease: updateCoords
    });

    function updateCoords(c) {
        $('#cropper_coords').val(c.x + "_" + c.y + "_" + c.w + "_" + c.h);
    };
});
I didn't add support to all the imageCropper properties, since we only needed minSize and aspectRatio, but they can be easily added based on the code above.

domingo, 29 de novembro de 2015

New Maven project for FoundationFaces

The first step to start a new JavaServer Faces component library is to create the project itself.

I choose the Java Application wizard of Netbeans new project Maven category, I filled the project name FoundationFaces, the group id org.foundation.faces and I clicked to finish the wizard.

Then I added the org.glassfish.javax.faces dependency to my pom.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.foundation.faces</groupId>
    <artifactId>FoundationFaces</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <developers>
        <developer>
            <id>hfluz</id>
            <name>Humberto Ferreira da Luz Jr</name>
            <email>hfluz@uel.br</email>
        </developer>
    </developers>
    <dependencies>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.faces</artifactId>
            <version>2.2.12</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>
</project>

In the next step I downloaded the Foundation for Sites resources (CSS, JS and fonts) and put them in this path:


FoundationFaces/src/main/resources/
└── META-INF
    └── resources
        └── foundation
            ├── css
            │   ├── foundation-icons.css
            │   └── foundation.min.css
            ├── fonts
            │   ├── foundation-icons.eot
            │   ├── foundation-icons.svg
            │   ├── foundation-icons.ttf
            │   └── foundation-icons.woff
            └── js
                ├── foundation.min.js
                └── jquery.min.js

And that's going to be enough to create FoundationFaces first components in the following posts.

JSF Custom Components Development

Here at the State University of Londrina IT department we adopted JavaServer Faces as the presentation layer of our Java EE apps. Since I deal with JSF every day, I'm always interested to deepen my knowledge, be it by reading books or the Java subreddit.

For convenience of our developers I already customized some Primefaces components (I keep our own fork with those customizations) and in a few cases I developed simple custom components to solve issues because it seemed to be the cleaner approach and I found very interesting to learn how JSF components work under the hood.

Earlier this year I created a github repo to develop a component library based on Zurb Foundation (just like Bootsfaces did with Boostrap).

I began creating a few Foundation for Sites components. Then I found out there was also the Foundation for Apps (which uses AngularJS). At first it seemed the latter one had more to do with JSF goals, but after each created component I felt there was some overlap between Angular and JSF custom components, which made me choose the Foundation for Sites as the base for my component library.

There isn't a lot of detailed resources about component development available. The Java EE 7 Tutorial was a good start to understand the main concepts and also to learn a few good practices. The Primefaces and Bootsfaces source code also helped a lot to understand more advanced concepts.

I intend to keep this blog updated in order to document my progress and also to help those who want another resource to understand how to develop custom components or how to change existing components to their needs. Furthermore my work might provide another responsive component library to the JSF community.

terça-feira, 30 de junho de 2015

Amazing JBoss EAP/Wildfly profile cloner

Some time ago I began to save my JBoss EAP configurations as CLI scripts. The problem is that after each new release it was common to find out my scripts were broken due to command/argument changes.

Fortunately I found out today about Profile Cloner on mastertheboss. It was developed by Tom Fonteyne and it's available at github. This tool allows you to effortlessly convert the current configuration of your JBoss/Wildfly server into a CLI script.

How to use it

git clone https://github.com/tfonteyn/profilecloner
cp target/profilecloner-2015-06-25.jar profilecloner.jar
./profilecloner.sh -f save-script.cli  --controller=localhost --username=user --password=password /profile=ha ha-copy