非推奨となっていたJava.facesをCDIにリプレイスしてみた

 7年前くらいに作ったWebLogicで動かしているアプリがあるのですが、Java.faces系のDI実装がだいぶ前から非推奨となっていたので、どこかで対応しないとなあ~と思っていてから数年経ちましたが、時間が取れたのでCDI実装に置き換えてみました。

 まず、簡単なところでは、javax.faces.bean.RequestScoped 等のスコープ指定を CDIのjavax.enterprise.context.RequestScoped に、
javax.faces.bean.ManagedBean を javax.inject.Named に
この辺りは単純にimportするライブラリを変更するだけです。ただし、@ViewScopedと@SessionScopedについては、Serializableを実装する必要があるようです。

//import javax.faces.bean.ManagedBean;
//import javax.faces.bean.SessionScoped;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;

//@ManagedBean(name = "xxxController")
@Named(value = "xxxController")
@SessionScoped
public class XXXController implements Serializable {

以下略

 次に、javax.faces.bean.ManagedProperty ですが、こちらは2パターンの対応が必要となり、@ManagedPropertyでDIしている箇所は、@Injectと@Namedに置き換えるだけです。

public class UserService implements Serializable {

//	@ManagedProperty("#{userDto}")
	@Inject
	@Named("userDto")
	UserDto user;"

以下略

 ManagedPropertyの別パターンで、今回対象のアプリではxhtmlからのPOSTデータ授受に使用していたのですが、javax.faces.annotation.ManagedProperty に置き換えても動かなかったので、下記のようにFacesContext経由でデータを受け取るようにしました。

public class UserController{

//    @ManagedProperty(value=""#{param.data}"")
//    private User user;

    public String editUser(){
        FacesContext context = FacesContext.getCurrentInstance();
        User user = (User) context.getApplication().evaluateExpressionGet(context,"#{data}", User.class);

以下略

 最後に、アプリでは共有したいセッション情報等を保持する為に、@ApplicationScopedをeager指定してアプリ開始時に生成していたのですが、CDI-APIの方では存在せず、初回コール時のタイミングで生成されるようです。

//import javax.faces.bean.ApplicationScoped;
//import javax.faces.bean.ManagedBean;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

//@ManagedBean(name="XXX",eager=true)
@Named(value = "XXX")
@ApplicationScoped
public class XXXManager {

以下略

 参照している箇所も見直しが必要で結果的に下記のように置き換えました。

//        XXXManager mgr = (XXXManager) req.getSession().getServletContext().getAttribute("XXX");
        XXXManager mgr = CDI.current().select(XXXManager.class).get();
        if(mgr ==  null){
            //生成前の時
        }

 動作は一通り確認しましたが、何か機能が追加された訳でもなく、置き換え前でも動きは変わらないので、いつリリース出来るかなあ。。

weblogic 12cR2にバージョンアップしたら、commons-vfs2でFTPが失敗する

weblogicバージョンアップに伴うトラブルで前回の続き、JAX-RS周りの問題が解消したと思ったら、別のエラーが発生。
commons-vfs2でftp送信している箇所が動かなくなりました。commons-vfs2では他にファイル取得等も実装してますが、そっちの方は何も問題はありません。
ログは下記のように何が原因か解らない状況。


Caused by: org.apache.commons.vfs2.FileSystemException: Could not copy "/tmp/report/upload.txt" to "ftp://username:***@ftphost/report/upload.txt".
Caused by: org.apache.commons.vfs2.FileSystemException: Could not create folder "ftp://username:***@ftphost/report" because it already exists and is a file.

結果的に、pom.xmlに下記を追加しただけで問題が解消しました。

    <!-- https://mvnrepository.com/artifact/commons-net/commons-net -->
	<dependency>
	    <groupId>commons-net</groupId>
	    <artifactId>commons-net</artifactId>
	    <version>2.2</version>
	</dependency>

アプリケーションサーバの機能が多いと、かえって何処が依存しているか解らなくなりがちですね。

weblogic 12cR2にバージョンアップしたらJAX-RSが動かなくなった。

5年前に作ったweblogicで動かしているアプリですが、一部の機能でエラーが出ているとの連絡。よく聞くと、weblogic12cから12cR2にバージョンアップをした後に発生しているとの事。

そりゃ、何かしら影響あるよね。。

どうも、JAX-RSが下記のように2.0に変わったという事で、jerseyも2.Xにしないといけない模様。
https://docs.oracle.com/cd/E92951_01/wls/NOTES/whatsnew.htm

pom.xmlを

    <dependency>
      <groupId>com.sun.jersey</groupId>
      <artifactId>jersey-bundle</artifactId>
      <version>1.19.1</version>
    </dependency>

から

  <dependency>
    <groupId>org.glassfish.jersey.bundles</groupId>
    <artifactId>jaxrs-ri</artifactId>
    <version>2.0</version>
    <scope>provided</scope>
  </dependency>

に変更

web.xmlを

 <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> 

から

 <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>

に変更して再ビルド、概ね事無きを得ました。

対象のアプリは2,3年何も手を加えていなかったので、依存ライブラリのバージョンアップをするのは止めました。トラブル対応だし、必要以上の事はしない方はよいかと。
JavaEEって、oracleからEclipse Foundationに移管されてJakarta EE 8になってたんですね。知らなかった。。
でも、まだ全く別の問題が発生していて対応中です。。

weblogic管理画面のパスワードを忘れた。。

 2年前くらいからOEPEを入れて開発に使っているwindows端末で、webアプリをweblogicで動かしてテストしたいなと思い、weblogicの管理画面からwarファイルをデプロイしようとした所、管理画面のログインパスワードを完全に忘れていました。。
デフォルトユーザーはweblogicだったのは覚えてましたが、心当たりがあるパスワードを入れても全くダメ。で、どうやってリセット出来るか?をググっても何も解らず、困っていた所、下記を発見。
https://docs.oracle.com/cd/E28613_01/web.1211/b65928/weblogicserver.htm#i1013039
開発端末なので既存のドメインにログイン出来なくなっても、ドメインを新規で作ればとりあえず動かせると言う事で。。本番環境では致命的ですけど。

ManagedBeanが非推奨になっていた

JavaEEでJSF2とJAX-RSを使った簡単な業務Webアプリを作っていたんですが、それまで作っていた開発環境のままWebLogicでローカルテストしてある程度動作するようになった後に、さすがにこの規模のAPにWebLogicを用意するのは無理!という事になったので、前にちょっと別件で使ってみたglashfish実装のpayaramicroで動くように変更してみました。
 まず、普通にweblogicで動かしていたwarをpayaraにデプロイすると下記エラーで失敗します・・

[[FATAL] No injection source found for a parameter of type public javax.ws.rs.core.Response

 正直あまり意識してなかったのですが、、やっぱりweblogicになると内包しているライブラリに依存しやすく、ちゃんと内部を理解していないと何が作用して動作しているかが解りにくいです。。
 エラーを見る限り、jax-rs関連でエラーになっている模様。
いろいろライブラリを置き換えていくと、既にManagedBeanが非推奨になっているようだ。。
結果pom.xmlは下記になり

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.faces/javax.faces-api -->
<dependency>
    <groupId>javax.faces</groupId>
    <artifactId>javax.faces-api</artifactId>
    <version>2.3</version>
    <scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.enterprise/cdi-api -->
<dependency>
    <groupId>javax.enterprise</groupId>
    <artifactId>cdi-api</artifactId>
    <version>2.0</version>
    <scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.glassfish.jersey.containers/jersey-container-servlet -->
<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet</artifactId>
    <version>2.25.1</version>
</dependency>

@ManagedBeanを@Namedに置き換え、SessionScopedをjavax.enterprise.contextに変えたりして、ようやくpayaraで動くようになりました。

 で、とりあえず動かすことはできたとは言え、今やWebアプリを積極的にJavaEEで実装する理由が少なくなっている気がします。Wabアプリならphpとかruby on Railsでの大規模サイトの構築事例がたくさんあります。速度的なアドバンテージも今となってはキャッシュにより殆ど感じられないですし、その実装対象のシステムに何等かの制約やレガシーな理由が絡んでないと敢えてJavaEEを選択する理由は何だろう?JavaEEがいまいち普及しないのは結局の所、各実装はベンダー次第となっている事が結果的にアプリケーションサーバの実装次第となってしまい、Javaとして最大の魅力である(と筆者は思っている)OSを超えた一貫性というものが、各APサーバが内包しているライブラリがバラバラである事により、消え失せてしまっているような気がします。

JAX-RSでWeb画面にドラッグアンドドロップされたファイルを読み込む

テキストファイルをサーバへアップロードしたいという要件があり、Web画面からファイルをドラッグアンドドロップできるようにして、それをJAX-RSで処理する事にしてみました。

下記の参考にさせて頂き、それらを組合わせただけと言えばだけですが。。

Jersey(JAX-RS)でファイルアップロード
HTML5 の File API でドラッグ&ドロップする

まず、JAX-RSのルートパスを指定します

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/api")
public class RestApplication extends Application {
//何も書く事はありません・・・
}

次にファイルを処理するJAX-RS部分を作ります

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.stream.Stream;

import javax.enterprise.context.RequestScoped;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataParam;

@RequestScoped
@Path("/upload")
public class FileUploader {
	
	@POST
	@Consumes(MediaType.MULTIPART_FORM_DATA)
	public Response post(@FormDataParam("file") InputStream fileStream,
					@FormDataParam("file") FormDataContentDisposition fileDisposition) {

		int statusCode = 200;
		String out = "";

		//テキストファイルの読み込み
		try (BufferedReader br = new BufferedReader(new InputStreamReader(fileStream))) {
			try(Stream<String> lines = br.lines()){
				//1行毎の処理は省略
			}
        } catch (IOException e) {
			statusCode = 400;
			out = e.getMessage();
		}
		return Response.status(statusCode).type("text/html;charset=Shift-JIS").
			entity(out).
			build();
	}
}

これでファイルアップローダーのJAX-RSパスは、{コンテンツルート}/api/uploadになりました。

続いてJavaScriptの部分(殆ど上記参考から持ってきただけです。。)

$(function() {
   var droppable = $("#droppable");

    // イベントをキャンセルするハンドラです.
    var cancelEvent = function(event) {
        event.preventDefault();
        event.stopPropagation();
        return false;
    }

    // dragenter, dragover イベントのデフォルト処理をキャンセルします.
    droppable.bind("dragenter", cancelEvent);
    droppable.bind("dragover", cancelEvent);

    // ドロップ時のイベントハンドラを設定します.
    var handleDroppedFile = function(event) {

	    var dropfile = event.originalEvent.dataTransfer.files[0];
	    
	    var formData = new FormData();
	    formData.append( 'file', dropfile );
	      
	    var hostUrl= 'api/upload';
	    $.ajax({
	       url: hostUrl,
	       method: 'post',
	       dataType: 'json',
	       data: formData,
	       processData: false,
	       contentType: false,
	       timeout:100000
	    }).done(function(data) {
        	alert( '正常に終了しました');
	    }).fail(function( jqXHR, textStatus, errorThrown ) {
	         alert( 'エラーが発生しました\n'+ jqXHR.responseText);
	    });
	
	    // デフォルトの処理をキャンセルします.
	    cancelEvent(event);
	    return false;
    }
    // ドロップ時のイベントハンドラを設定します.
    droppable.bind("drop", handleDroppedFile);
});

最後にHTML部分のドラッグアンドドロップ部分です。
ここにドロップされたファイルがJAX-RSの処理箇所のInputStream に繋がります。

<div class="droppable" id="droppable" style="border:gray solid 1em; width:100px; padding:2em;">アップロードするファイルはココにドロップしてください。</div>      

これでとりあえずはJAX-RSでファイル処理が出来ることが確認出来ました。

最後にPOM。

<dependency>
    <groupId>javax.ws.rs</groupId>
    <artifactId>javax.ws.rs-api</artifactId>
    <version>2.0.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.sun.jersey.contribs/jersey-multipart -->
<dependency>
    <groupId>com.sun.jersey.contribs</groupId>
    <artifactId>jersey-multipart</artifactId>
    <version>1.19.3</version>
</dependency>

bootstrap2 datetimepickerを1分単位にする

ちょっと前に作ったJavaEEアプリでbootstrap2のdatatimepickerを使ったのだが、
?p=698
5分刻みは使いにくいという話が出てきて、ググッてみたところ下記を発見。
http://www.malot.fr/bootstrap-datetimepicker/
オプションのminuteStepで値を変えられるみたいなのでやってみました。
1分刻みにするとこうなります。

xhtml側は下記minuteStepのオプションを追加するだけ

<h:outputScript name="bootstrap/js/bootstrap-datetimepicker.min.js" />
<h:outputScript name="bootstrap/js/bootstrap-datetimepicker.ja.js" />
<h:outputStylesheet name="bootstrap/css/bootstrap-datetimepicker.min.css" />

$(function() {
  $('.datetimepicker').datetimepicker({
    format: 'yyyy/mm/dd hh:ii:ss',
    autoclose: true,
    todayBtn: true,
    pickerPosition: "bottom-left",
    minuteStep: 1,  
    language: 'ja'
   });
});

デフォルトだと5分刻みなのでこんな感じ

ダイアログが長くなって画面ギリギリだけど、この方が便利ということでこちらでリリース
次作るものはbootstrap3にしないとなあ。。

EJBタイマーサービスを使ってみることにした

WebLogicを動かしているサーバ上で数分間隔にとある機能を動作させたいという話があり、常駐プロセス起動とかにしたくないので、EJBタイマーサービスを使ってみることにしました。
タイマーサービスの使用は初めてなので、WebLogic12Cの挙動を調べながらの作りこみです。

結果的に下記のようになりました。
5分間隔(0分2秒,5分2秒,10分2秒,15分2秒・・・)でタイマー処理を実行しています。
なお、トランザクション処理は必要無し、処理結果のエラーハンドリングも不要、指定の間隔で単純に起動されればよいような機能です。

@Singleton
public class ChkTimer{
	@Schedule(hour="*", minute="*/5", second="2", persistent=false)
	public void chkProc(){
		ExecutorService service = Executors.newSingleThreadExecutor();
				
		service.submit(new Runnable() {
			@Override
			public void run() {
				//ここに処理を書く
			}
		}
	}
	@Timeout
	public void ChkTimeout(){
		//何もしないけど・・・
	}
}

WebLogicにおけるEJBタイマーの挙動として確認できたのは、

①タイムアウトを実装しておかないと30秒以上経つとエラーを吐いて、次のタイマーまでは動くのだが、その次以降はまったく動かなくなる

Caused By: weblogic.transaction.internal.TimedOutException: Transaction timed out after 30 seconds
中略・・
Truncated. see log file for complete stacktrace

なぜ30秒なのかというとトランザクションタイムアウト値がそうなっているっぽい。。
https://docs.oracle.com/cd/E28613_01/web.1211/b65951/ejb_jar_ref.htm#i1506703

何をするわけでもないが、とりあえず@Timeoutのアノテーションをつけたメソッドを追加

②処理時間が次回のタイマー処理開始を超えるとタイマーが止まる

例えば、5分間隔で動作させる場合、処理が5分以上掛かってしまうと、次のタイマー実行時にエラーが吐かれて動かない

<2016/11/15 16時55分07秒 JST> <Error> <EJB> <BEA-011088> <The following error occurred while invoking the ejbTimeout(javax.ejb.Timer) method of EJB ChkTimer(Application: appmgr, EJBComponent: /appmgr).
以下、略・・

なので、処理自体をExecutorServiceでスレッド処理にし、処理遅延が起きないようにしてみました

③永続化はしない方がよい

とりあえずサーバ上で空回りするようタイマー稼動テストを数日間したところ、ちゃんと指定間隔通りに動作していたので、機能を概ね実装していざデプロイしてみると、2重起動している状態に、、
なぜかといろいろ調べた結果、空回り開始時にpersistent=falseを指定していなかったので、ずっと残り続けていた模様。
一度、タイマー起動を無しにしたモジュールでデプロイした後、再度タイマーを有効にしてデプロイすることで、やっと2重起動が止まりました。

実行したいスケジュールや処理内容によっては、結構制御が難しいように思いますが、今回の要件ならOKという事で。

JSF2でbootstrapのダイアログとアイコンを使う

 前に作ったJavaEEアプリの画面に機能追加の依頼があったのですが、既存画面に追加するのも新規画面を作るのも微妙な要件だったので、ダイアログ表示でなんとかしたいと考えてました。
 で、ちょうどbootstrapを使っていたので、調べてみるとダイアログ表示はbootstrapで簡単に出来そうです。なおバージョンは2.3を使用しています。
http://bootstrapdocs.com/v2.3.2/docs/javascript.html#modals
JSF2のel式をダイアログに埋め込む為にはh:form内に書けばよいようです。ダイアログにはdatetimepickerをinputを2項目入れ下記のようになりました。

    <div class="modal hide fade" id="modal-timeinput" tabindex="-1">
      <div class="modal-dialog">
        <div class="modal-content">
          <h:form prependId="false">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">
                    <span aria-hidden="true">&times;</span>
                </button>
                <h4 class="modal-title" id="modal-label">期間指定</h4>
            </div>
            <div class="modal-body">
             <table>
              <tr>
                <th><h:outputText value="開始日時" class="from-control"/></th>
                <th><h:outputText value="終了日時" class="from-control"/></th>
              </tr>
              <tr>
                <td><h:inputText id="fromlogDate" value="#{LogDl.logStartDate}" class="datetimepicker from-control" /></td>
                <td><h:inputText id="tologDate" value="#{LogDl.logStartDate}" class="datetimepicker from-control" /></td>
              </tr>
             </table>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">閉じる</button>
                <h:commandButton id="getButton" class="btn btn-primary" value="取得" action="#{LogDl.getFile}" >
                </h:commandButton>
            </div>
          </h:form>
        </div>
      </div>
    </div>

呼び出し箇所は下記です。

<li>
  <a data-toggle="modal" data-target="#modal-timeinput" href="#" onclick="return false">
    期間指定</a></li>

これで「期間指定」を押すと下記のようなダイアログが表示されます。
fromto

次にアイコン表示も
http://bootstrapdocs.com/v2.3.2/docs/base-css.html#icons
の通りやってみましたが、なかなか表示されません。なんでかな??と思いながらブラウザの開発ツールを見ていたら、pngファイルは404 Not Foundとの事。ここでやっと気が付き、bootstrap.cssのpngファイル名に「.xhtml」を付け、やっとアイコンが表示されました。

weblogic.xmlのprefer-web-inf-classesをtrueにしたらハマった・・

 前に作ったWeblogicのWebアプリで
JAX-RSを使ってみる
weblogicよりもアプリケーションモジュールに入れるライブラリを優先させたい事情があり、weblogic.xmlにprefer-web-inf-classesのtrueを追加してみたところ、これまで問題無かったのが、デプロイ出来ない状態に。。
ログには下記のエラーが出ています。

<2016/07/26 12時58分59秒 JST> <Error> <HTTP> <AUTH-DEV01> <AdminServer> <[ACTIVE] ExecuteThread: '11' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1469505539381> <BEA-101216> <Servlet: "JAX-RS Servlet" failed to preload on startup in Web application: "web-ap".
java.lang.NoClassDefFoundError: Could not initialize class com.sun.proxy.$Proxy121

どうも、JAX-RSに関して何らかの問題が発生しているようなのですが、JAX-RSに関して明示しているライブラリはpom.xmlに書いた下記のみです。

    <dependency>
	  <groupId>com.sun.jersey</groupId>
	  <artifactId>jersey-core</artifactId>
	  <version>1.19</version>
    </dependency>

いろいろ試行錯誤した結果、上記で指定したものよりweblogicライブラリが優先的に使用されていて、1年間問題無かったのが、ライブラリの優先順位を変えた事によって、問題が発生したようなのです。最終的にcom.sun.jerseyについて上記のpomをジャージーのバンドルセットに変える事で解消しました。

    <dependency>
      <groupId>com.sun.jersey</groupId>
      <artifactId>jersey-bundle</artifactId>
      <version>1.19.1</version>
    </dependency>

いや、びっくりしました。。