SonaQubeをCentOSにインストールする
SonarQubeサーバの実行には、JavaとMySQL等のデータベースが必要です。 データベースは初期設定でH2を使うようになっており、必ずしも用意しなければならないものではありません。しかし、これはあくまでも評価用であり、実際に利用するときは別途データベースを用意することが推奨されています。
この記事では、CentOS 6.5でMySQL 5.6を用意することにします。
Javaインストール
OracleのサイトからJavaをダウンロードし、yumでインストールします。Javaのダウンロードには、ライセンス条項に同意する必要があります。ライセンスに同意した上でLinux上でダウンロードするには、以下のコマンドを実行します。
wget --no-check-certificate --no-cookies - --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u5-b13/jdk-8u5-linux-x64.rpm
sudo yum -y install jdk-8u5-linux-x64.rpm
Javaのバージョンが表示されたら成功です。
$ java -version java version "1.8.0_05" Java(TM) SE Runtime Environment (build 1.8.0_05-b13) Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)
MySQLインストール
CentOSのレポジトリに含まれるMySQLは結構古い(5.1)ため、Oracleのレポジトリから新しいバージョンをyumでインストールします。
sudo yum -y install http://dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm sudo yum -y install mysql-community-server
Oracleリポジトリからのインストールでは、データベースの作成等は行われないため、手動で実行する必要があります。
sudo -u mysql mysql_install_db sudo service mysqld start sudo mysql_secure_installation
SonarQube インストール
ZIPをダウンロードして展開するだけですが、ここでは Sonar native packages を利用してyumでインストールします。
sudo wget -O /etc/yum.repos.d/sonar.repo http://downloads.sourceforge.net/project/sonar-pkg/rpm/sonar.repo sudo yum -y install sonar
データベース作成
インストールしたMySQL上に、SonarQube用のデータベースを作成します。
CREATE DATABASE sonar CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER 'sonar' IDENTIFIED BY 'sonar'; GRANT ALL ON sonar.* TO 'sonar'@'%' IDENTIFIED BY 'sonar'; GRANT ALL ON sonar.* TO 'sonar'@'localhost' IDENTIFIED BY 'sonar'; FLUSH PRIVILEGES;
SonarQubeのDB設定
SonarQubeのJDBC設定を、MySQLを使うよう変更します。
# sonar.jdbc.url=jdbc:h2:tcp://localhost:9092/sonar // コメントアウトする sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true // コメントを外す
SonarQubeサーバ起動
ここまでの作業が終了したら、後は起動するだけです。
sudo service sonar start
初回起動時に、自動的にテーブルなどが作成されます。 /opt/sonar/logs/sonar.log
に Web server is started
というログが出れば準備完了です。ブラウザで http://hostname:9000/
にアクセスすると、トップページが表示されるはずです。
お疲れ様でした。これで、SonarQubeを使い始める準備が整いました。
右上の Log in リンクで管理者(初期値は admin/admin)としてログインすることで、プラグインの追加やルールの管理を行うことができます。
Tomcat再起動時に [Abandoned connection cleanup thread] がメモリリークしてるという警告が出る場合の対応
Tomcat上のWebアプリケーションでMySQLのJDBCドライバを使用すると、Tomcat再起動時に以下のような警告が出力されます。
The web application [] appears to have started a thread named [Abandoned connection cleanup thread] but has failed to stop it. This is very likely to create a memory leak.
これはMySQLにバグチケットが上がっていて、対処方法もそちらに記載があります。
MySQL Bugs: #65909: referenceThread causes memory leak in Tomcat
ServletContextListener#contextDestroyed() で、該当のスレッドを止めてやれば良い訳ですね。こんな感じです。
@WebListener public class StopLeakThreadListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { // do nothing } @Override public void contextDestroyed(ServletContextEvent sce) { try { AbandonedConnectionCleanupThread.shutdown(); } catch (final InterruptedException e) { } } }
これをやるときは、mysql-connector-java が compile スコープで必要になります。通常JDBCドライバは runtime にしていると思いますのでご注意を。
(おまけ)JDBCドライバも登録解除する
Tomcat再起動時、下記のようなログもでることがあります。
SEVERE: A web application registered the JBDC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
こちらはTomcatのメモリリーク防止機能による「JDBCドライバが残ってたから登録解除しといたよ!」というメッセージのため、無視しても問題ありません。 ただ、SEVEREでログが出ているので、何となく気持ち悪かったり、「本当に大丈夫ですか、検証しましたか、だれが責任取るんですか」みたいなことを言われる可能性が多少でもあったりする場合は、明示的にドライバを登録解除することで精神の安定を得ることができます。
上の #contextDestroyed()
メソッドに以下のようなコードを追加します。
Collections.list(DriverManager.getDrivers()).forEach(driver -> { try { DriverManager.deregisterDriver(driver); } catch (final Exception e) {} });
DriverManager.getDrivers()
は Enumeration<Driver>
を返すのですが、for文は禁止 です。
Spring SecurityのCSRF対策とServlet 3.0のMultipartをJava Configで設定する
はまったのでメモ。
やりたかったこと
HTMLのFORMをPOSTするとき <input hidden name="_csrf" value="..." />
でCSRFトークンを送りつつMultipartでファイルアップロードしたい。
Springの設定はJava Configでやっていて、web.xmlは作っていない。今さらいろいろな設定をxmlにしたくない。
やってみたこと
Springのリファレンス 16.11 Spring’s multipart (file upload) support にある通り、MultipartResolver
をコンテキストに登録してみた。
@Bean public MultipartResolver multipartResolver() { return new StandardServletMultipartResolver(); }
HTML側からPOSTすると、サーバ側のログに Invalid csrf token
というメッセージが出て、ステータスコード403が返ってきた。
原因は、Spring SecurityのCsrfFilterがリクエストパラメータからトークンを取得するときに、Multipartリクエストに対し getParameter をしているため。
解決
しばらくググってみたところ、 TERASOLUNA Global Framework のCSRF対策 に方法が書いてあった。
6.7.2.4.1. MultipartFilterを使用する方法
通常、マルチパートリクエストの場合、formから送信された値はFilter内で取得できない。 org.springframework.web.multipart.support.MultipartFilterを使用することで、マルチパートリクエストでも、Filter内で、 formから送信された値を取得することができる。
サイトにはweb.xmlでの設定例が載っているが、要はspringSecurityFilterChain
より前にMultipartFilter
を動かす必要があるということ。
Java Configでやるには、SecurityWebApplicationInitializer#beforeSpringSecurityFilterChain()
でフィルタを登録する。
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { @Override protected void beforeSpringSecurityFilterChain(ServletContext servletContext) { final MultipartFilter multipartFilter = new MultipartFilter(); servletContext.addFilter("multipartFilter", multipartFilter) .addMappingForUrlPatterns( EnumSet.allOf(DispatcherType.class), false, "/*"); } }
MultipartFilter
は、デフォルトでServlet 3.0のAPIを使うStandardServletMultipartResolver
を使うようになっているため、自前でコンテキストに登録する必要はない。もしcommons-fileuploadを使いたい場合は、MultipartFilter#DEFAULT_MULTIPART_RESOLVER_BEAN_NAME
で定義された名前 ("filterMultipartResolver") でコンテキストに CommonsMultipartResolver
を登録すればよい。
Servlet 3.0のMultipart有効化
web.xmlでの設定例や、自前Servletにつけるアノテーションはすぐに見つかるのだけど、DispatcherServlet
のJava Configの設定例が見当たらない。けど、サーブレットに <multipart-config>
をつければ良いだろうということで、WebApplicationInitializer
に設定を追加。
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected void customizeRegistration(Dynamic registration) { registration.setMultipartConfig(new MultipartConfigElement( System.getProperty("java.io.tmpdir"), -1, -1, 1024 * 1024)); } }
MultipartConfigElement
で、アップロード可能な最大ファイルサイズや、添付ファイルに書きだすサイズ閾値など指定可能。
SonarQubeでMavenのMulti-moduleかつMulti-language
SonarQube 4.2からMulti-languageがサポートされました。これにより、例えばJavaとJavascriptが1つのMavenプロジェクト内に存在する場合でも、両方の言語の解析結果を見ることができるようになりました。
Multi-languageとして解析するために特別な設定は不要です。sonar.sources
プロパティに指定されたディレクトリ以下のファイルタイプに応じて、自動的に言語が特定され、対応する品質プロファイルでの解析が行われるようになっています。
ただ、このsonar.sources
プロパティなのですが、Mavenプロジェクトではデフォルトでsrc/main/java
*1になります。結果として「Javaしか解析されないじゃん\(^o^)/」みたいなことになっちゃいます。
対策としては、POMの<properties>
要素で、sonar.sources
プロパティに解析対象としたいディレクトリをカンマ区切りで指定すればOKです。
よくありがちなMavenのwebapp構成では、以下のようにすることでwebapp以下のjs/htmlも解析対象にできます。
<properties> <sonar.sources>${project.build.sourceDirectory},${basedir}/src/main/webapp</sonar.sources> <sonar.exclusions> **/jquery*.js </sonar.exclusions> </properties>
jqueryなど、解析対象としたくないファイルは、sonar.exclusions
でうまいこと除外してください。
<properties>
はPOMファイルごとに設定でき、parentを上書きするので、モジュールごとに別々のディレクトリを解析対象とすることができます。
*1:正確には ${project.build.sourceDirectory}
SonarQubeでMavenのレポートを再利用する設定
最近リリースされたSonarQube 4.3から、SonarQube実行時にユニットテストを実行する機能である sonar.dynamicAnalysis
が非推奨になりました。*1
SonarQube自身が様々なの言語への対応を進める中で、各言語のテストフレームワーク全てを実行できるようにするのは無理があるので、この変更は仕方のないものだと思います。
自分のプロジェクトでも対応が必要になったので、どう設定したかなどまとめておきます。
Unit Test、Integration Testの実行設定
テスト実行に前提条件(DBのセットアップ、コンテナ起動など)がある場合、JUnitのカテゴリ機能を利用して実行可否を制御しておくと便利ですよね。 うちのプロジェクトでは、環境に依存せずサクサク動くテストはsurefireで実行し、DBFluteのReplaceSchemaでデータベースをセットアップしたり、SpringのWebApplicationContextを利用するような重いテストはfailsafeで実行するようにしています。
SonarではUnit TestとIntegration Testを分けてカバレッジを出せるので、surefireをUnit Test、failsafeをIntegration Testとすることにし、以下のように設定しました。
<build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.17</version> <configuration> <excludedGroups> com.example.IntegrationTest </excludedGroups> <argLine>${argLine}</argLine> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>2.17</version> <configuration> <includes> <include>**/*Test.java</include> </includes> <groups>com.example.IntegrationTest</groups> <failIfNoTests>false</failIfNoTests> <argLine>${argLine}</argLine> </configuration> <dependencies> <dependency> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.17</version> </dependency> </dependencies> </plugin> </plugins> </pluginManagement> </build> <profiles> <profile> <id>sonar</id> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <testFailureIgnore>true</testFailureIgnore> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> <configuration> <testFailureIgnore>true</testFailureIgnore> <reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory> </configuration> </plugin> </plugins> </build> </profile> </profiles>
pluginManagement
にて、Integration Testの目印としたcom.example.IntegrationTest
カテゴリを、surefireからは除外、failsafeには対象として、それぞれ設定します。
また、sonarプロファイルでfailsafeが実行されるようexecution
を設定しています。
今回はIntegration Testの件数もSonarでカウントしてもらいたかったので、failsafeのreportsDirectory
をsurefireと同じ場所に設定しました。
テストカバレッジを取得する
テストカバレッジの取得にはjacocoを使用します。これもSonar実行時のみ行えばよいので、sonarプロファイルに設定を追加します。
<plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.7.0.201403182114</version> <executions> <execution> <id>pre-test</id> <goals><goal>prepare-agent</goal></goals> </execution> <execution> <id>post-test</id> <goals><goal>report</goal></goals> </execution> <execution> <id>pre-integration-test</id> <phase>pre-integration-test</phase> <goals><goal>prepare-agent-integration</goal></goals> </execution> <execution> <id>post-integration-test</id> <phase>post-integration-test</phase> <goals><goal>report-integration</goal></goals> </execution> </executions> </plugin>
report|report-integration
ゴールで、target/jacoco.exec|target/jacoco-it.exec
が出力されます。Sonarの解析時、これらのファイルを再利用することになります。
実行
ここまで設定したら、後はSonarで解析を実行するだけです。最初にMavenでsonarプロファイルでverifyフェーズまでビルドしておき、SonarRunnerを実行します。
Mavenでやるならこんな感じで。
mvn -P sonar verify
mvn sonar:sonar
Jenkinsであれば、Mavenプロジェクトとしてジョブを作成し、ビルド後にSonarプラグインが動くようにすればOKです。
Redmineのカレンダーに祝日を表示するプラグインを作りました
先日Redmineのカレンダーで祝日を表示する方法を見つけたわけですが、本体のソースを直接修正するのがだいぶイケてない感じでした。 せっかくなので、Redmine(とRails)の勉強がてらプラグインにしました。
taktos/redmine_holidays_plugin · GitHub
インストール、使い方はREADMEをご覧ください。 プラグインの設定で祝日判定に使用する国・地域を選択できます。
日本での表示は↓のようになります。振替休日も表示してくれるのがいいですね!
公開ついでにRedmineのプラグインページに登録しておきました。
Redmineのカレンダーで祝日を表示する
2013-01-30追記
本体のコードを修正しないで済むようにプラグイン化しました。 記事はこちら↓
基本的には以下のサイトと同じですが、祝日名を表示したいので、祝日判定にholidaysを使います。
カレンダー表示時のクラス追加
holidaysのgemを入れます(redmine_backlogsプラグインが使うものでOK)
$ gem install holidays
カレンダー表示を書き換えて、祝日のとき(ついでに土日も)「holiday」クラスを追加します。
diff --git a/app/views/common/_calendar.html.erb b/app/views/common/_calendar.html.erb index 7951b68..6fafcae 100644 --- a/app/views/common/_calendar.html.erb +++ b/app/views/common/_calendar.html.erb @@ -7,8 +7,11 @@ <% day = calendar.startdt while day <= calendar.enddt %> <%= ("<td class='week-number' title='#{ l(:label_week) }'>#{(day+(11-day.cwday)%7).cweek}</td>".html_safe) if day.cwday == calendar.first_wday %> -<td class="<%= day.month==calendar.month ? 'even' : 'odd' %><%= ' today' if Date.today == day %>"> -<p class="day-num"><%= day.day %></p> +<td class="<%= day.month==calendar.month ? 'even' : 'odd' %><%= ' today' if Date.today == day %><%= ' holiday' if day.holiday?(:jp) || day.wday == 0 || day.wday == 6 %>"> +<p class="day-num"> +<% if day.holiday?(:jp) %><span style="float:left; padding: 1px;"><%= day.holidays(:jp).first[:name] %></span><% end %> +<%= day.day %> +</p> <% calendar.events_on(day).each do |i| %> <% if i.is_a? Issue %> <div class="<%= i.css_classes %> <%= 'starting' if day == i.start_date %> <%= 'ending' if day == i.due_date %> tooltip">
css修正
application.css など適当なCSSに、holidayクラスを追加します。
/* holiday */ table.cal td.odd.holiday { background-color: #e3e4e5; } table.cal td.even.holiday { background-color: #ff9999; }