Monthly Archives: October 2013

Готовим maven правильно: deployment в Tomcat

Written by elwood

cargo-banner

Задача

Есть многомодульный проект, собираемый с помощью maven. Среди модулей имеются несколько WAR-приложений, которые должны деплоиться в сервлет-контейнер (рассмотрим Томкат, но всё применимо и к любому другому контейнеру или серверу приложений). Причём желательно иметь возможность выборочно собирать и деплоить только одно из этих приложений. Развёртываться всё это добро должно уметь как в локальный контейнер программиста, так и на удалённый тестовый сервер.

Решение

У нас есть один главный модуль (тот, у которого packaging = pom) и несколько модулей. В pom.xml главного модуля мы пропишем два набора профилей. Один набор профилей будет отвечать за то, собирать ли указанный модуль или нет. Второй набор профилей будет отвечать за настройки окружения. Таким образом, мы превращаем мейвеновские профили в аналог USE-флагов в Gentoo Linux. Также в pom.xml главного модуля мы сконфигурируем плагин cargo-maven2-plugin, указав ему skip = true. А в тех дочерних модулях, которые предполагаются к развёртыванию, мы этот флаг установим в false. Таким образом, мы сможем вызывать cargo:deploy для главного модуля, но срабатывать он будет только у тех дочерних, которые мы укажем. Иные варианты, к сожалению, работать не будут (например, если конфигурировать плагин для дочернего модуля и вызывать напрямую у него, то при наличии зависимостей maven не сможет собрать модуль). А два набора профилей дадут нам возможность выборочно собирать и развёртывать приложения туда, куда нам хочется.

Код

pom.xml главного модуля:

<modules>
  <!-- Модули, собираемые всегда -->
  <module>common-dependency</module>
</modules>
<packaging>pom</packaging>
 
<profiles>
  <!-- Профили, относящиеся к настройкам окружения - при сборке мы должны указать только 1 профиль -->
  <profile>
    <id>env-igor-dev</id>
    <properties>
      <tomcatHost>localhost</tomcatHost>
      <tomcatPort>8091</tomcatPort>
      <tomcatManagerUser>tomcat</tomcatManagerUser>
      <tomcatManagerPassword>1</tomcatManagerPassword>
    </properties>
  </profile>
  <profile>
    <id>env-test</id>
    <properties>
      <tomcatHost>test.com</tomcatHost>
      <tomcatPort>8080</tomcatPort>
      <tomcatManagerUser>tom</tomcatManagerUser>
      <tomcatManagerPassword>fsKf2_3</tomcatManagerPassword>
    </properties>
  </profile>
 
  <!-- Профили, относящиеся к тому, что мы хотим собрать. При сборке можно указать 
       любую комбинацию этих профилей. -->
  <profile>
    <id>build-war1</id>
    <modules>
      <module>webapp-1</module>
    </modules>
  </profile>
  <profile>
    <id>build-war2</id>
    <modules>
      <module>webapp-2</module>
    </modules>
  </profile>
</profiles>
 
<build>
  <plugins>
    <plugin>
      <groupId>org.codehaus.cargo</groupId>
      <artifactId>cargo-maven2-plugin</artifactId>
      <configuration>
        <configuration>
          <properties>
            <cargo.hostname>${tomcatHost}</cargo.hostname>
            <cargo.protocol>http</cargo.protocol>
            <cargo.servlet.port>${tomcatPort}</cargo.servlet.port>
            <cargo.remote.username>${tomcatManagerUser}</cargo.remote.username>
            <cargo.remote.password>${tomcatManagerPassword}</cargo.remote.password>
          </properties>
          <type>runtime</type>
        </configuration>
        <container>
          <containerId>tomcat6x</containerId>
          <type>remote</type>
        </container>
        <!-- skip in parent pom and by default in all submodules -->
        <skip>true</skip>
      </configuration>
    </plugin>
  </plugins>
</build>

pom.xml одного из приложений:

<build>
  <plugins>
    <plugin>
      <artifactId>maven-war-plugin</artifactId>
      <configuration>
        <warName>war1</warName>
      </configuration>
    </plugin>
    <plugin>
      <groupId>org.codehaus.cargo</groupId>
      <artifactId>cargo-maven2-plugin</artifactId>
      <configuration>
        <deployables>
          <deployable>
            <location>webapp-1/target/war1.war</location>
          </deployable>
        </deployables>
        <skip>false</skip>
      </configuration>
    </plugin>
  </plugins>
</build>

pom.xml второго приложения составляется аналогично

Результат

Приложение собирается и деплоится одной командой
mvn clean package cargo:redeploy -P env-igor-dev,build-war1,build-war2

Update

Если у вас в war-приложении есть META-INF/context.xml, а в нём задан context path (например <Context antiJARLocking="true" path="/">), то cargo проигнорирует свойство deployable -> properties -> context и загрузит варник в ROOT.war. Поэтому если вам нужно, чтобы варник деплоился туда куда надо, настройте maven resources plugin с filtering=true чтобы path устанавливать в нужное вам значение.

Нашёл баг в Mono

Written by elwood

На днях простудился, и пока сидел дома, вспомнил о своей библиотеке для создания консольных окошек в псевдографике. Одна из самых важных задач в её разработке – сделать её совместимой с Linux, потому как в Windows такого рода инструменты в принципе мало кому могут пригодиться. И чтобы она работала под Linux в Mono, я в своё время потратил немало времени. Научил её выводить в терминал псевдографику (сейчас для этого используется ncurses, но только как буфер для вывода), потом разобрался с тем, как обрабатывать пользовательский ввод (для этого я попробовал LibTermKey, и мне понравилось). Кстати, особенности реализации event-driven модели для консольного ввода я уже описывал в заметке об eventfd.

А в этот раз я исследовал проблему того, что после выхода из программы у терминала, из-под которого она запускалась (и gnome-terminal, и xterm), сбивались настройки, и в нём становилось невозможно работать до тех пор, пока пользователь не выполнял команду stty sane.

mono-bug

После локализации воспроизведения проблемы в отдельном консольном приложении выяснилось забавное. Если внутри блока работы с ncurses (между вызовами initscr() и endwin() ) выполнить один из read-методов консоли (Console.ReadLine(), Console.ReadKey()), то терминал убивается после выхода из приложения. Если же read-методы не вызывать вообще, то всё завершается корректно. Но если один из read-методов вызвать до инициализации ncurses, то всё тоже работает хорошо, и уже не важно, сколько раз и где после этого будут вызваны “опасные” read-методы. То есть, первый вызов read-методов в Mono Runtime имеет какие-то побочные эффекты, приводящие к таким последствиям.

// If uncomment next line - all will work OK
// May be first access to Console.ReadKey(Line) or Console.KeyAvailable
// has side effects and modifies current term settings
 
//bool x = Console.KeyAvailable;
 
IntPtr stdscr = initscr ();
cbreak ();
noecho ();
nonl ();
intrflush (stdscr, false);
keypad (stdscr, true);
start_color ();
mvaddstr (4, 5, "Test string!");
refresh ();
 
// If remove this line and dont call reading methods inside ncurses code -
// all will work OK. But after first call (if reading method called before ncurses init)
// we can call it safe.
Console.ReadLine (); // or Console.ReadKey()
 
endwin ();

Но ведь не будешь же вызывать Console.ReadKey() при старте приложения, прося пользователя нажать клавишу, чтобы всё у нас было хорошо 🙂 К счастью, оказалось достаточно обратиться к свойству Console.KeyAvailable, чтобы выполнить эту инициализацию “первого чтения”. Таким образом, проблема была побеждена малой кровью.

Однако, я всё-таки собрался с силами и оформил баг в багзилле Mono. Надеюсь, это поведение будет исправлено, или хотя бы документировано (если так оно и должно работать).