回答編集履歴

4 )が足りないのを修正

mit0223

mit0223 score 2778

2017/01/25 19:45  投稿

現在編集中のユーザの情報(tmpId, tmpName)はリクエスト、セッションを越えて共有できる必要がありますので、アプリケーションスコープの変数として保持する必要があります。
[Servlet スコープとは?](http://d.hatena.ne.jp/okoba23/20080504/1209914754)のapplicationスコープのサンプルを参照してください。 ServletContext に編集中のユーザの ID と名前を setAttribute し、あとで getAttribute することで現在編集中のユーザの情報を管理することができると思います。
ただし、掲示板が複数ある場合は、掲示板毎に管理する必要があるので、ハッシュテーブルなどを使う必要があると思います。
また、複数のユーザがまったく同時に編集しようとすると、今、編集中のユーザがいないことを確認する if 文とユーザ情報を設定する setAttribute 呼び出しを同時に実行してしまう可能性があるため、 synchronized なメソッドで実装して排他制御する必要があると思います。
---
以下に排他制御について書いてみますが、手元に servlet をデバッグする環境がないので、すべて未検証(コンパイルエラーがでるかもしれない)であることをご了承ください。
いろいろ、ググッて調べたのですが、「もし、未登録であれば登録する」というロジックで満足に排他制御できているサンプルが見つかりませんでした。ServletContext は複数のスレッド(ユーザからのリクエスト)から同時にアクセスされるので、「もし、未登録であれば」という if 文を2つのスレッドが同時に実行すると、両方のスレッドが「登録する」処理を実行してしまうので、バグってしまいます。また、この手のバグはなかなか顕在化しないので、たちが悪いです。
まず、ServletContext にマップを登録する処理ですが、初期化イベントのリスナーで実行しておくべきです。アプリケーションが動作する前に実行されるので、排他制御の必要がありません。
```java
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.concurrent.ConcurrentHashMap;
@javax.servlet.annotation.WebListener
public class WebListener implements ServletContextListener {
   @Override
   public void contextDestroyed(ServletContextEvent paramServletContextEvent) {}
   @Override
   public void contextInitialized(ServletContextEvent paramServletContextEvent) {
       paramServletContextEvent.getServletContext().setAttribute("tmpMap",
         new ConcurrentHashMap<String, Integer>();
         new ConcurrentHashMap<String, Integer>());
   }
}
```
こうしておいて、サーブレット側では ConcurrentHashMap の putIfAbsent を使って排他制御します。
```java
package wiki;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import Account.AccountBean;
public class UpdateUserCheck extends HttpServlet {
   @Override
   public synchronized void doGet(HttpServletRequest request, HttpServletResponse response)
           throws ServletException, IOException {
       HttpSession session = request.getSession(false);
       ServletContext sc = getServletContext(); // アプリケーションスコープの取得
       String name = request.getParameter("name"); // 編集中のページ名を取得
       AccountBean account = (AccountBean) session.getAttribute("account");
       /* 初期化イベントリスナーで登録したMapを取得する */
       ConcurrentHashMap<String, Integer> map = autoCast(sc.getAttribute("tmpMap")); // メソッドでキャスト
       String message = null;
       final Integer currentUid = autoCast(map.putIfAbsent(name, account.getId()));
       if (currentUid != null) {
           if (! currentUid.equals(account.getId())) {
               message = "他のユーザーにて編集中です";
               request.setAttribute("message", message);
               request.getRequestDispatcher("list").forward(request, response);
           } else {
               request.getRequestDispatcher("/wikiView/update.jsp").forward(request, response);
           }
       } else {
           request.getRequestDispatcher("/wikiView/update.jsp").forward(request, response);
       }
   }
   /* 戻り値の型に合わせてキャスト */
   @SuppressWarnings("unchecked")
   public static <T> T autoCast(Object obj) {
       T castedObject = (T) obj;
       return castedObject;
   }
}
```
また、ユーザが編集を終了するときに map から削除するようにしておかないと、上記コードは動作しません。さらに編集状態のまま、ログアウト時や放置されてセッションがタイムアウトした時も編集状態を開放する処理が必要であると思われます。
3 コーディング例で new new ってなってたのを修正

mit0223

mit0223 score 2778

2017/01/25 19:43  投稿

現在編集中のユーザの情報(tmpId, tmpName)はリクエスト、セッションを越えて共有できる必要がありますので、アプリケーションスコープの変数として保持する必要があります。
[Servlet スコープとは?](http://d.hatena.ne.jp/okoba23/20080504/1209914754)のapplicationスコープのサンプルを参照してください。 ServletContext に編集中のユーザの ID と名前を setAttribute し、あとで getAttribute することで現在編集中のユーザの情報を管理することができると思います。
ただし、掲示板が複数ある場合は、掲示板毎に管理する必要があるので、ハッシュテーブルなどを使う必要があると思います。
また、複数のユーザがまったく同時に編集しようとすると、今、編集中のユーザがいないことを確認する if 文とユーザ情報を設定する setAttribute 呼び出しを同時に実行してしまう可能性があるため、 synchronized なメソッドで実装して排他制御する必要があると思います。
---
以下に排他制御について書いてみますが、手元に servlet をデバッグする環境がないので、すべて未検証(コンパイルエラーがでるかもしれない)であることをご了承ください。
いろいろ、ググッて調べたのですが、「もし、未登録であれば登録する」というロジックで満足に排他制御できているサンプルが見つかりませんでした。ServletContext は複数のスレッド(ユーザからのリクエスト)から同時にアクセスされるので、「もし、未登録であれば」という if 文を2つのスレッドが同時に実行すると、両方のスレッドが「登録する」処理を実行してしまうので、バグってしまいます。また、この手のバグはなかなか顕在化しないので、たちが悪いです。
まず、ServletContext にマップを登録する処理ですが、初期化イベントのリスナーで実行しておくべきです。アプリケーションが動作する前に実行されるので、排他制御の必要がありません。
```java
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.concurrent.ConcurrentHashMap;
@javax.servlet.annotation.WebListener
public class WebListener implements ServletContextListener {
   @Override
   public void contextDestroyed(ServletContextEvent paramServletContextEvent) {}
   @Override
   public void contextInitialized(ServletContextEvent paramServletContextEvent) {
       paramServletContextEvent.getServletContext().setAttribute("tmpMap",
         new new ConcurrentHashMap<String, Integer>();
         new ConcurrentHashMap<String, Integer>();
   }
}
```
こうしておいて、サーブレット側では ConcurrentHashMap の putIfAbsent を使って排他制御します。
```java
package wiki;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import Account.AccountBean;
public class UpdateUserCheck extends HttpServlet {
   @Override
   public synchronized void doGet(HttpServletRequest request, HttpServletResponse response)
           throws ServletException, IOException {
       HttpSession session = request.getSession(false);
       ServletContext sc = getServletContext(); // アプリケーションスコープの取得
       String name = request.getParameter("name"); // 編集中のページ名を取得
       AccountBean account = (AccountBean) session.getAttribute("account");
       /* 初期化イベントリスナーで登録したMapを取得する */
       ConcurrentHashMap<String, Integer> map = autoCast(sc.getAttribute("tmpMap")); // メソッドでキャスト
       String message = null;
       final Integer currentUid = autoCast(map.putIfAbsent(name, account.getId()));
       if (currentUid != null) {
           if (! currentUid.equals(account.getId())) {
               message = "他のユーザーにて編集中です";
               request.setAttribute("message", message);
               request.getRequestDispatcher("list").forward(request, response);
           } else {
               request.getRequestDispatcher("/wikiView/update.jsp").forward(request, response);
           }
       } else {
           request.getRequestDispatcher("/wikiView/update.jsp").forward(request, response);
       }
   }
   /* 戻り値の型に合わせてキャスト */
   @SuppressWarnings("unchecked")
   public static <T> T autoCast(Object obj) {
       T castedObject = (T) obj;
       return castedObject;
   }
}
```
また、ユーザが編集を終了するときに map から削除するようにしておかないと、上記コードは動作しません。さらに編集状態のまま、ログアウト時や放置されてセッションがタイムアウトした時も編集状態を開放する処理が必要であると思われます。
2 コーディング例の追加

mit0223

mit0223 score 2778

2017/01/25 19:32  投稿

現在編集中のユーザの情報(tmpId, tmpName)はリクエスト、セッションを越えて共有できる必要がありますので、アプリケーションスコープの変数として保持する必要があります。
[Servlet スコープとは?](http://d.hatena.ne.jp/okoba23/20080504/1209914754)のapplicationスコープのサンプルを参照してください。 ServletContext に編集中のユーザの ID と名前を setAttribute し、あとで getAttribute することで現在編集中のユーザの情報を管理することができると思います。
ただし、掲示板が複数ある場合は、掲示板毎に管理する必要があるので、ハッシュテーブルなどを使う必要があると思います。
また、複数のユーザがまったく同時に編集しようとすると、今、編集中のユーザがいないことを確認する if 文とユーザ情報を設定する setAttribute 呼び出しを同時に実行してしまう可能性があるため、 synchronized なメソッドで実装して排他制御する必要があると思います。
また、複数のユーザがまったく同時に編集しようとすると、今、編集中のユーザがいないことを確認する if 文とユーザ情報を設定する setAttribute 呼び出しを同時に実行してしまう可能性があるため、 synchronized なメソッドで実装して排他制御する必要があると思います。
---
以下に排他制御について書いてみますが、手元に servlet をデバッグする環境がないので、すべて未検証(コンパイルエラーがでるかもしれない)であることをご了承ください。
いろいろ、ググッて調べたのですが、「もし、未登録であれば登録する」というロジックで満足に排他制御できているサンプルが見つかりませんでした。ServletContext は複数のスレッド(ユーザからのリクエスト)から同時にアクセスされるので、「もし、未登録であれば」という if 文を2つのスレッドが同時に実行すると、両方のスレッドが「登録する」処理を実行してしまうので、バグってしまいます。また、この手のバグはなかなか顕在化しないので、たちが悪いです。
まず、ServletContext にマップを登録する処理ですが、初期化イベントのリスナーで実行しておくべきです。アプリケーションが動作する前に実行されるので、排他制御の必要がありません。
```java
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.concurrent.ConcurrentHashMap;
@javax.servlet.annotation.WebListener
public class WebListener implements ServletContextListener {
   @Override
   public void contextDestroyed(ServletContextEvent paramServletContextEvent) {}
   @Override
   public void contextInitialized(ServletContextEvent paramServletContextEvent) {
       paramServletContextEvent.getServletContext().setAttribute("tmpMap",
         new new ConcurrentHashMap<String, Integer>();
   }
}
```
こうしておいて、サーブレット側では ConcurrentHashMap の putIfAbsent を使って排他制御します。
```java
package wiki;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import Account.AccountBean;
public class UpdateUserCheck extends HttpServlet {
   @Override
   public synchronized void doGet(HttpServletRequest request, HttpServletResponse response)
           throws ServletException, IOException {
       HttpSession session = request.getSession(false);
       ServletContext sc = getServletContext(); // アプリケーションスコープの取得
       String name = request.getParameter("name"); // 編集中のページ名を取得
       AccountBean account = (AccountBean) session.getAttribute("account");
       /* 初期化イベントリスナーで登録したMapを取得する */
       ConcurrentHashMap<String, Integer> map = autoCast(sc.getAttribute("tmpMap")); // メソッドでキャスト
       String message = null;
       final Integer currentUid = autoCast(map.putIfAbsent(name, account.getId()));
       if (currentUid != null) {
           if (! currentUid.equals(account.getId())) {
               message = "他のユーザーにて編集中です";
               request.setAttribute("message", message);
               request.getRequestDispatcher("list").forward(request, response);
           } else {
               request.getRequestDispatcher("/wikiView/update.jsp").forward(request, response);
           }
       } else {
           request.getRequestDispatcher("/wikiView/update.jsp").forward(request, response);
       }
   }
   /* 戻り値の型に合わせてキャスト */
   @SuppressWarnings("unchecked")
   public static <T> T autoCast(Object obj) {
       T castedObject = (T) obj;
       return castedObject;
   }
}
```
また、ユーザが編集を終了するときに map から削除するようにしておかないと、上記コードは動作しません。さらに編集状態のまま、ログアウト時や放置されてセッションがタイムアウトした時も編集状態を開放する処理が必要であると思われます。
1 排他制御の必要性を追記

mit0223

mit0223 score 2778

2017/01/22 10:37  投稿

現在編集中のユーザの情報(tmpId, tmpName)はリクエスト、セッションを越えて共有できる必要がありますので、アプリケーションスコープの変数として保持する必要があります。
[Servlet スコープとは?](http://d.hatena.ne.jp/okoba23/20080504/1209914754)のapplicationスコープのサンプルを参照してください。 ServletContext に編集中のユーザの ID と名前を setAttribute し、あとで getAttribute することで現在編集中のユーザの情報を管理することができると思います。
ただし、掲示板が複数ある場合は、掲示板毎に管理する必要があるので、ハッシュテーブルなどを使う必要があると思います。
ただし、掲示板が複数ある場合は、掲示板毎に管理する必要があるので、ハッシュテーブルなどを使う必要があると思います。
また、複数のユーザがまったく同時に編集しようとすると、今、編集中のユーザがいないことを確認する if 文とユーザ情報を設定する setAttribute 呼び出しを同時に実行してしまう可能性があるため、 synchronized なメソッドで実装して排他制御する必要があると思います。

思考するエンジニアのためのQ&Aサイト「teratail」について詳しく知る