суббота, 27 марта 2010 г.

Будущее построения архитектуры Java EE приложений становится яснее

Автор: Adam Bien

Оригинальный текст: http://www.adam-bien.com/roller/abien/entry/future_of_enterprise_java_is



Известный JavaEE архитектор-фрилансер недавно опубликовал свое видение будущего в построении архитектуры корпоративных приложений. Далее идет вольный перевод.

Java EE 6 и Spring 6 оказались очень похожи -- как минимум архитектура и дизайн отличаются лишь в деталях. Не вижу отличий и в процессе разработки, где JPA и SessionBean-ы в Glassfish можно заменить технологиями Spring.

Spring также идет со своим сервером приложений, который с 7 октября 2008 является open source сервером с коммерческой поддержкой. Если вам понадобится получить патчи старых версий Spring -- вам будет необходимо преобрести коммерческую поддержку от SpringSource/VMWare. Для серьезных проектов вы будете вынуждены преобрести два пакета поддержки -- для сервера приложений от вендора и один от SpringSource, хотя этом случае одновременное использование Java EE 5/6 опровдать сложно. В перспективе я вижу два возможных варианта:

  • Развертывание Spring-а на проприетарный tc сервер

  • Развертывание Java EE 6 приложений без Spring-а

Обозначенная выше дилемма также справедлива и для проектов по миграции -- стоит ли использовать стек Java EE, либо мигрировать на Spring. Это скорее стратегическая или политическая дилемма, нежели технологическая. Конечно, можно еще самостоятельно собирать и распространять Spring, однако такой подход недопустим в большинстве коммерческих проектов.

Я считаю, что будущее корпоративной Java очень чистое -- мы полностью используем либо Spring, либо стек Java EE, но не эти технологии вместе.

Oracle опубликовал планы развития Glassfish

Oracle Glassfish roadmap

25 марта Oracle официально представил планы по развитию недавно преобретенного вместе с Sun Microsystems сервера приложений Glassfish. Самая важная новость -- никаких изменений в работе сообщества над проектом не произойдет, Oracle заинтересован в дальнейшем развитии продукта.

Open source версия Glassfish остается, а в дополнение к ней Oracle будет поставлять свой Oracle GlassFish Server с преферансом и куртизанками со своей поддержкой, лицензированием и закрытыми дополнениями. Были опровергнуты утверждения о том, что прекратится поддержка кластеризации, проект станет закрытым для внешних разработчиков, Oracle не понимает Open source и т.д.

Планы по развитию затрагивают следующие аспекты: следующие релизы версий 2 и 3 через 100 дней (в основном интеграция с технологиями Oracle), векторы технологического развития Glassfish, интеграции с семейством Oracle Fusion Middleware, анонс версий 3 и 4.


Основными ресурсами по Glassfish становятся: glassfish.org -- ресурсы сообщества (листы рассылки, форумы, вики и т.д.), бинарники и исходники; Oracle.com -- коммерческая информация (официальная документация, поддержка), лицензированная поставка Oracle, причем лицензии будут как production, так и evalution.

100 дневные релизы

Glassvish c2.1.2: ребрендинг, патчи, сохранение кластеризация и HA, поддержка новых программно-аппаратных платформ, использование в других продуктах Oracle.

Glassvish c3.0.1: ребрендинг, патчи, многоязычность, дополнения для OracleFlassFish Sever и базовая интеграция с продуктами семейства Oracle Middleware.

Последующие релизы

Glassfish v3.1, 2010: Кластеризация, HA, репликация, коммерческие дополнения;

Glassfish v3.2, 2011: улучшение управления кластеризацией/HA, интеграция с Oracle Identity Management, поддержка виртуализации, обновление по спецификации Java EE 6, начальная поддержка Java EE 7;

Glassfish v4: базовая платформа -- общая с Weblogic, поддержка Java EE 7.

В общем и целом новости положительные -- проект продолжает быть свободным reference implementation спецификации.

четверг, 25 марта 2010 г.

Struts 2 на Google App Engine. Рецепты настройки

image

Для использования одного из самых удобных веб фреймоврков для java на популярной облачной платформе Google AppEngine необходимо совершить ряд действий. Данная статья появилась в результате моих продолжительных экспериментов.

Используемые библиотеки

Я использую обычно следующий набор библиотек:

  • commons-fileupload-1.2.1.jar

  • commons-io-1.3.2.jar

  • commons-logging-1.0.4.jar

  • commons-logging-api-1.1.jar

  • freemarker-2.3.13.jar

  • ognl-2.6.11.jar

  • struts2-core-2.1.6.jar

  • xwork-2.1.2.jar


Я пользуюсь IntelliJ Idea 9 и она подкачивает зависимости автоматически после того, как я указываю использование Struts 2 при создании проекта. Также существует возможность развертывать приложения на облачной платформе через специальное меню IDE, и вообще поддержка Appengine на очень хорошем уровне.

Далее необхоимо обойти или адаптироваться к ограничениям облачной платформы, ибо Appengine запрещает прямой доступ к тредам, файловым и многим другим ресурсам.

Настройка Ognl SecurityManager-а

Необходимо создать листнер, настраивающий Ognl SecurityManager при старте приложения:
  1. package su.mvc.util;
  2.  
  3. import ognl.OgnlRuntime;
  4.  
  5. import javax.servlet.ServletContextEvent;
  6. import javax.servlet.ServletContextListener;
  7.  
  8. public class GaeContextListener implements ServletContextListener {
  9.   public void contextInitialized(ServletContextEvent servletContextEvent) {
  10.     OgnlRuntime.setSecurityManager(null);
  11.   }
  12.  
  13.   public void contextDestroyed(ServletContextEvent servletContextEvent) {
  14.     //
  15.   }
  16. }


А далее подключить его в дескрипторе развертывания приложения web.xml:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns="http://java.sun.com/xml/ns/javaee"
  3.       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
  5.      http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  6.       version="2.5">
  7.  
  8.   <!-- Listeners -->
  9.   <listener>
  10.     <listener-class>su.mvc.util.GaeContextListener</listener-class>
  11.   </listener>
  12.   <!-- ... -->
  13.  
  14. </web-app>


Преобразование типов Test и Key

В Appengine широко используются следующие типы:

  • com.google.appengine.api.datastore.Text для управления большими текстовыми данными

  • com.google.appengine.api.datastore.Key используется в качестве @Id для сущностей. К сожалению, AppEngine позволяет корректно работать с отношениями сущностей только в случае использования Key в качестве @Id


Для использования этих типов на веб формах необходимо определить прямое и обратное преобразование этих типов к строке:
  1. package su.mvc.util.converters;
  2.  
  3. import com.google.appengine.api.datastore.Text;
  4. import ognl.DefaultTypeConverter;
  5.  
  6. import java.util.Map;
  7.  
  8. public class TextConverter extends DefaultTypeConverter {
  9.   @Override
  10.   public Object convertValue(Map map, Object o, Class toType) {
  11.     if (toType == Text.class) {
  12.       String value = ((String[]) o)[0];
  13.       return new Text(value);
  14.     } else if (toType == String.class) {
  15.       Text text = (Text) o;
  16.       return text.getValue();
  17.     }
  18.  
  19.     return null;
  20.   }
  21. }


  1. package su.mvc.util.converters;
  2.  
  3. import com.google.appengine.api.datastore.Key;
  4. import com.google.appengine.api.datastore.KeyFactory;
  5. import ognl.DefaultTypeConverter;
  6.  
  7. import java.util.Map;
  8.  
  9. public class KeyConverter extends DefaultTypeConverter {
  10.   @Override
  11.   public Object convertValue(Map map, Object o, Class toType) {
  12.     if (toType == Key.class) {
  13.       String s = ((String[]) o)[0];
  14.       return KeyFactory.stringToKey(s);
  15.     } else if (toType == String.class) {
  16.       Key k = (Key) o;
  17.       return KeyFactory.keyToString(k);
  18.     }
  19.  
  20.     return null;
  21.   }
  22. }


Осталось задекларировать созданные конверторы в файле xwork-conversion.properties:

  1. сom.google.appengine.api.datastore.Text=su.mvc.util.converters.TextConverter
  2. com.google.appengine.api.datastore.Key=su.mvc.util.converters.KeyConverter


Использование Convention плагина

Горячо любимый мною Convention плагин используется для реализации концепции Convention-Over-Configuration и существенно сокращает время на конфигурирование приложение. К сожалению при работе со Struts 2 придется забыть об этом плагине.

Патч Freemarker на TextBlock

Даже если Вы не используете Freemarker для реализации UI, при использовании стратсовых JSP тегов Freemarker необходимо пропатчить. Для этого необходимо создать в своем приложении класс freemarker.core.TextBlock следующего содержания:
//
//Workaround for http://groups.google.com/group/google-appengine-java/browse_thread/thread/186c3fc515e6f6ae
//

/*
* Copyright (c) 2003 The Visigoth Software Society. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
*  notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above
copyright
*  notice, this list of conditions and the following disclaimer in
*  the documentation and/or other materials provided with the
*  distribution.
*
* 3. The end-user documentation included with the redistribution, if
*  any, must include the following acknowledgement:
*    "This product includes software developed by the
*    Visigoth Software Society (http://www.visigoths.org/)."
*  Alternately, this acknowledgement may appear in the software
itself,
*  if and wherever such third-party acknowledgements normally
appear.
*
* 4. Neither the name "FreeMarker", "Visigoth", nor any of the names
of the
*  project contributors may be used to endorse or promote products
derived
*  from this software without prior written permission. For written
*  permission, please contact visigo...@visigoths.org.
*
* 5. Products derived from this software may not be called
"FreeMarker" or "Visigoth"
*  nor may "FreeMarker" or "Visigoth" appear in their names
*  without prior written permission of the Visigoth Software
Society.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Visigoth Software Society. For more
* information on the Visigoth Software Society, please see
* http://www.visigoths.org/
*/

package freemarker.core;

import java.io.IOException;

/**
* A TemplateElement representing a block of plain text.
* @version $Id: TextBlock.java,v 1.17 2004/01/06 17:06:42 szegedia
Exp $
*/
public final class TextBlock extends TemplateElement {
  private static final char[] EMPTY_CHAR_ARRAY = new char[0];
  static final TextBlock EMPTY_BLOCK = new TextBlock
(EMPTY_CHAR_ARRAY, false);
  // We're using char[] instead of String for storing the text block because
  // Writer.write(String) involves copying the String contents to a char[]
  // using String.getChars(), and then calling Writer.write(char[]). By
  // using Writer.write(char[]) directly, we avoid array copying on each
  // write.
  private char[] text;
  private final boolean unparsed;

  public TextBlock(String text) {
    this(text, false);
  }

  public TextBlock(String text, boolean unparsed) {
    this(text.toCharArray(), unparsed);
  }

  private TextBlock(char[] text, boolean unparsed) {
    this.text = text;
    this.unparsed = unparsed;
  }

  /**
   * Simply outputs the text.
   */
  public void accept(Environment env)
  throws IOException
  {
    env.getOut().write(text);
  }

  public String getCanonicalForm() {
    String text = new String(this.text);
    if (unparsed) {
      return "<#noparse>" + text + "</#noparse>";
    }
    return text;
  }

  public String getDescription() {
    String s = new String(text).trim();
    if (s.length() == 0) {
      return "whitespace";
    }
    if (s.length() > 20) {
      s = s.substring(0,20) + "...";
      s = s.replace('\n', ' ');
      s = s.replace('\r', ' ');
    }
    return "text block (" + s + ")";
  }

  TemplateElement postParseCleanup(boolean stripWhitespace) {
    if (text.length == 0) return this;
    int openingCharsToStrip = 0, trailingCharsToStrip=0;
    boolean deliberateLeftTrim = deliberateLeftTrim();
    boolean deliberateRightTrim = deliberateRightTrim();
    if (!stripWhitespace || text.length == 0 ) {
      return this;
    }
    if (parent.parent == null && previousSibling() == null) return
this;
    if (!deliberateLeftTrim) {
      trailingCharsToStrip = trailingCharsToStrip();
    }
    if (!deliberateRightTrim) {
      openingCharsToStrip = openingCharsToStrip();
    }
    if (openingCharsToStrip == 0 && trailingCharsToStrip == 0) {
      return this;
    }
    this.text = substring(text, openingCharsToStrip, text.length - trailingCharsToStrip);
    if (openingCharsToStrip > 0) {
      this.beginLine++;
      this.beginColumn = 1;
    }
    if (trailingCharsToStrip >0) {
      this.endColumn = 0;
    }
    return this;
  }

  /**
   * Scans forward the nodes on the same line to see whether there
is a
   * deliberate left trim in effect. Returns true if the left trim
was present.
   */
  private boolean deliberateLeftTrim() {
    boolean result = false;
    for (TemplateElement elem = this.nextTerminalNode();
       elem != null && elem.beginLine == this.endLine;
       elem = elem.nextTerminalNode())
    {
      if (elem instanceof TrimInstruction) {
        TrimInstruction ti = (TrimInstruction) elem;
        if (!ti.left && !ti.right) {
          result = true;
        }
        if (ti.left) {
          result = true;
          int lastNewLineIndex = lastNewLineIndex();
          if (lastNewLineIndex >=0 || beginColumn == 1) {
            char[] firstPart = substring(text, 0, lastNewLineIndex + 1);
            char[] lastLine = substring(text, 1+lastNewLineIndex);
            if (trim(lastLine).length == 0) {
              this.text = firstPart;
              this.endColumn = 0;
            } else {
              int i =0;
              while (Character.isWhitespace(lastLine [i])) {
                i++;
              }
              char[] printablePart = substring(lastLine, i);
              this.text = concat(firstPart, printablePart);
            }
          }
        }
      }
    }
    if (result) {
    }
    return result;
  }

  /**
   * Checks for the presence of a t or rt directive on the
   * same line. Returns true if the right trim directive was
present.
   */
  private boolean deliberateRightTrim() {
    boolean result = false;
    for (TemplateElement elem = this.prevTerminalNode();
       elem != null && elem.endLine == this.beginLine;
       elem = elem.prevTerminalNode())
    {
      if (elem instanceof TrimInstruction) {
        TrimInstruction ti = (TrimInstruction) elem;
        if (!ti.left && !ti.right) {
          result = true;
        }
        if (ti.right) {
          result = true;
          int firstLineIndex = firstNewLineIndex() +1;
          if (firstLineIndex == 0) {
            return false;
          }
          if (text.length > firstLineIndex
            && text[firstLineIndex-1] == '\r'
            && text[firstLineIndex] == '\n')
          {
            firstLineIndex++;
          }
          char[] trailingPart = substring(text, firstLineIndex);
          char[] openingPart = substring(text, 0, firstLineIndex);
          if (trim(openingPart).length ==0) {
            this.text = trailingPart;
            this.beginLine++;
            this.beginColumn=1;
          } else {
            int lastNonWS = openingPart.length -1;
            while (Character.isWhitespace(text[lastNonWS])) {
              lastNonWS--;
            }
            char[] printablePart = substring(text, 0,lastNonWS+1);
            if (trim(trailingPart).length == 0) {
            // THIS BLOCK IS HEINOUS! THERE MUST BE A BETTER WAY! REVISIT (JR)
              boolean trimTrailingPart = true;
              for (TemplateElement te = this.nextTerminalNode();
                 te != null && te.beginLine == this.endLine;
                 te = te.nextTerminalNode())
              {
                if (te.heedsOpeningWhitespace())
                {
                  trimTrailingPart = false;
                }
                if (te instanceof TrimInstruction && ((TrimInstruction) te).left) {
                  trimTrailingPart = true;
                  break;
                }
              }
              if (trimTrailingPart) trailingPart = EMPTY_CHAR_ARRAY;
            }
            this.text = concat(printablePart, trailingPart);
          }
        }
      }
    }
    return result;
  }
/*
  private String leftTrim(String s) {
    int i =0;
    while (i<s.length()) {
      if (!Character.isWhitespace(s.charAt(i)))
        break;
      ++i;
    }
    return s.substring(i);
  }
*/
  private int firstNewLineIndex() {
    String content = new String(text);
    int newlineIndex1 = content.indexOf('\n');
    int newlineIndex2 = content.indexOf('\r');
    int result = newlineIndex1 >=0 ? newlineIndex1 : newlineIndex2;
    if (newlineIndex1 >=0 && newlineIndex2 >=0) {
      result = Math.min(newlineIndex1, newlineIndex2);
    }
    return result;
  }

  private int lastNewLineIndex() {
    String content = new String(text);
    return Math.max(content.lastIndexOf('\r'), content.lastIndexOf
('\n'));
  }

  /**
   * figures out how many opening whitespace characters to strip
   * in the post-parse cleanup phase.
   */
  private int openingCharsToStrip() {
    int newlineIndex = firstNewLineIndex();
    if (newlineIndex == -1 && beginColumn != 1) {
      return 0;
    }
    ++newlineIndex;
    if (text.length > newlineIndex) {
      if (newlineIndex >0 && text[newlineIndex-1] == '\r' && text[newlineIndex] == '\n') {
        ++newlineIndex;
      }
    }
    if (new String(text).substring(0, newlineIndex).trim().length() >0) {
      return 0;
    }
// We look at the preceding elements on the line to see if we should
// strip the opening newline and any whitespace preceding it.
    for (TemplateElement elem = this.prevTerminalNode();
       elem != null && elem.endLine == this.beginLine;
       elem = elem.prevTerminalNode())
    {
      if (elem.heedsOpeningWhitespace())
      {
        return 0;
      }
    }
    return newlineIndex;
  }

  /**
   * figures out how many trailing whitespace characters to strip
   * in the post-parse cleanup phase.
   */
  private int trailingCharsToStrip() {
    String content = new String(text);
    int lastNewlineIndex = lastNewLineIndex();
    if (lastNewlineIndex == -1 && beginColumn != 1) {
      return 0;
    }
    String substring = content.substring(lastNewlineIndex +1);
    if (substring.trim().length() >0) {
      return 0;
    }
// We look at the elements afterward on the same line to see if weshould
// strip any whitespace after the last newline
    for (TemplateElement elem = this.nextTerminalNode();
       elem != null && elem.beginLine == this.endLine;
       elem = elem.nextTerminalNode())
    {
      if (elem.heedsTrailingWhitespace())
      {
        return 0;
      }
    }
    return substring.length();
  }

  boolean heedsTrailingWhitespace() {
    if (isIgnorable()) {
      return false;
    }
    for (int i=0; i<text.length; i++) {
      char c = text[i];
      if (c=='\n' || c=='\r') {
        return false;
      }
      if (!Character.isWhitespace(c)) {
        return true;
      }
    }
    return true;
  }

  boolean heedsOpeningWhitespace() {
    if (isIgnorable()) {
      return false;
    }
    for (int i = text.length -1; i>=0; i--) {
      char c = text[i];
      if (c == '\n' || c == '\r') {
        return false;
      }
      if (!Character.isWhitespace(c)) {
        return true;
      }
    }
    return true;
  }

  boolean isIgnorable() {
    if (text == null || text.length == 0) {
      return true;
    }
    if (!isWhitespace()) {
      return false;
    }
// trick here
    boolean atTopLevel = true;
    TemplateElement prevSibling = previousSibling();
    TemplateElement nextSibling = nextSibling();
    return ((prevSibling == null && atTopLevel) || nonOutputtingType(prevSibling))
       && ((nextSibling == null && atTopLevel) || nonOutputtingType(nextSibling));
  }

  private boolean nonOutputtingType(TemplateElement element) {
    return (element instanceof Macro ||
        element instanceof Assignment ||
        element instanceof AssignmentInstruction ||
        element instanceof PropertySetting ||
        element instanceof LibraryLoad ||
        element instanceof Comment);
  }

  private static char[] substring(char[] c, int from, int to) {
    char[] c2 = new char[to - from];
    System.arraycopy(c, from, c2, 0, c2.length);
    return c2;
  }

  private static char[] substring(char[] c, int from) {
    return substring(c, from, c.length);
  }

  private static char[] trim(char[] c) {
    if (c.length == 0) {
      return c;
    }
    return new String(c).trim().toCharArray();
  }

  private static char[] concat(char[] c1, char[] c2) {
    char[] c = new char[c1.length + c2.length];
    System.arraycopy(c1, 0, c, 0, c1.length);
    System.arraycopy(c2, 0, c, c1.length, c2.length);
    return c;
  }

  boolean isWhitespace() {
    return text == null || trim(text).length == 0;
  }

}



Буду рад, если Вы поделитесь своим опытом работы с вашими любимыми фреймворками на платформе GAE в коментариях :-)

вторник, 23 марта 2010 г.

Java EE 6. Обзор JPA 2.0, часть 2: Коллекции

image

Продолжение серии. Ранее: Java EE 6. Обзор JPA 2.0, часть 1: Введение. В данной статье я продолжаю рассматривать изменения в JPA.

Встроенные коллекции

Введенный в JPA 2.0 компонент ElementCollection позволяет задавать политики отображения для необычных в JPA 1.0 видов коллекций: коллекций embeddable объеков либо коллекций "простых" типов (Integer, String и т.д.). Также это компонент используется при в определениях отношений с Map, в роли ключа которого выступают любого рода объекты, а в роли значения -- embeddable или "простые" объекты.

Значения ElementCollection всегда хранятся в отдельных таблицах, которые задаются аннотацией @CollectionTable. CollectionTable отпределяет имя таблицы и @JoinColumn или @JoinColumns в случае составного первичного ключа.

Коллекции embedded объектов

Для определения коллекции embedded объектов используется ElementCollection. Декларирование отображения embedded похоже на OneToMany, за исключением того, что целевая таблица является Embeddable, а не сущностью. Это позволяет проще объявлять коллекции простых объектов, без необходимости определения обратных связей и внесения Id.

Отличием ElementCollection от OneToMany служит то, что целевые объекты нельзя выбирать, сохранять, мержить напрямую, не зависимо от родительского объекта. Отсутстсвует политика каскадирования, состояние объектов коллекции синхронно состоянию родительского объекта. Ну в общем то все как и с embedded объектами. Однако у ElementCollection можно задавать тип выборки, и по умолчанию он lazy.

Пришло время небольшого примера:
  1. import javax.persistence.*;
  2. import java.util.List;
  3.  
  4.  
  5. @Entity
  6. public class Customer {
  7.   @Id
  8.   @Column(name="CUSTOMER_ID")
  9.   private Long id;
  10.  
  11.   private String name;
  12.  
  13.   @ElementCollection
  14.   @CollectionTable(
  15.     name="CUST_ADDRESS",
  16.     joinColumns=@JoinColumn(name="OWNER_ID")
  17.   )
  18.   private List<Address> phones;
  19.  
  20.   //:~ Дальше идут акцессоры
  21. }


  1. import javax.persistence.Column;
  2. import javax.persistence.Embeddable;
  3.  
  4.  
  5. @Embeddable
  6. public class Address {
  7.  
  8.   private String city;
  9.  
  10.   private String street;
  11.  
  12.   @Column(name="ZIP_CODE")
  13.   private String zip;
  14.  
  15.   //:~ Дальше идут акцессоры
  16. }


На выходе получится следующая схема в БД:
image

Коллекции из объектов "простых" типов

С простыми коллекциями все также просто: используется ElementCollection, для значений создается отдельная таблица, все аналогично OneToMany за исключением того, что целевой обект является простым, а не сущностью. Минусы этого подхода аналогичны перечисленным в пердыдущем пункте.

Самое время для примера:
  1. import javax.persistence.*;
  2. import java.util.List;
  3.  
  4.  
  5. @Entity
  6. public class Customer {
  7.   @Id
  8.   @Column(name="CUSTOMER_ID")
  9.   private long id;
  10.  
  11.   private String name;
  12.  
  13.   @ElementCollection
  14.   @CollectionTable(
  15.     name="CUST_ADDRESSES",
  16.     joinColumns=@JoinColumn(name="OWNER_ID")
  17.   )
  18.   @Column(name="ADDRESS")
  19.   private List<String> address;
  20.  
  21.  
  22.   //:~ Дальше идут акцессоры
  23.  
  24. }


На выходе получится следующая схема в БД:
image

понедельник, 22 марта 2010 г.

Java EE 6. Обзор JPA 2.0, часть 1: Введение


Пожалуй, наибольшее количество изменений с выходом спецификации Java EE 6 было привнесено в JPA (Java Persistence API). В серии статей, начиная с этой, я планирую рассказать подбробно о каждом из нововведений.

Введение


Технология JPA является абстракцией над JDBC и позволяет быть независимым от SQL. Все классы и интерфейсы JPA расположены в пакете javax.persistence, основными составляющими технологии являются

  • ORM (механизм объектно-реляционного отображения);

  • Entity manager API -- позволяет осуществлять базовые операции CRUD;

  • JPQL и Criteria API -- механизмы извлечения данных;

  • Управление транзакциями и блокировками (как с использованием JTA, так и без);

  • Механизмы обратных вызовов и listener-ов.



В JPA 2.0 изменения коснулись следующего:

  • Улучшилась поддержка Map-ов: теперь как ключь, так и значение могут быть простым типом, сущностью или встроенным объектом (embedded);

  • Коллекции встроенных объектов и простых типов (Integer и т.д.) могут теберь быть вынесены в отдельные таблицы. Ранее в отдельные таблицы было возможно выносить только коллекции сущностей;

  • Появилась возможность управлять упорядоченностью персистентных объектов с помощью аннотации @OrderColumn;

  • Появилась возможность удалять объекты-сироты (orphan removal): при удалении родительского объекта удаляется и дочерний;

  • Появилась поддержка пессимистической блокировки (оптимистическая поддерживается с JPA 1.0);

  • Новый Query Definition API, который позволяет строить запросы в стиле ООП (в противовес написание в строке JPQL запросов);

  • Сильно расширились возможности JPQL (язык запросов в JPA);

  • Встроенные (embedded) объекты теперь могут входить в состав других встроенных объектов, а также участвовать в связях с сущностями. Навигация по графу объектов с помощью точки была расширена и для использования со встроенными объектами;

  • Добавилась поддержка нового API для кэширования.



В данный момент единственным провайдером JPA 2.0 является EclipseLink, он же является reference implementation JPA 2.0. Пользуюсь им уже несколько месяцев и готов сказать, что нахожу этот проект очень стабильным.

В следующей статье я расскажу о нововведениях в декларировании ORM.

понедельник, 15 марта 2010 г.

Java EE 6. Спецификация Servlet 3.0: что нового



С выходом Java EE 6 на ряду со значительными изменениями в JPA 2.0 спецификации сервлетов 3.0 также притерпела ряд улучшений: упростилась разработка и процедура развертывания, конфигурирование стало более удобным, появилась поддержка асинхронных запросов и улучшилась модель безопасности. Далее я попытаюсь осветить основные изменения в API.

Программирование и развертывание сервлетов упростилось главным образом за счет введения аннотаций для декларирования сервлет (@WebServlet), фильтров (@WebFilter), листнеров (@WebListener) и ограничений безопасности (@ServletSecurity). Таким образом, и дескриптор развертывания web.xml стал опциональным элементом. Обращаю внимание, что сами компоненты Servlet API не стали POJO, привычную иерархию интерфейсов и классов никто не отменял. Также добавилась аннотация для поддержки загрузки файлов @MultipartConfig и для установки параметров инициализации @WebInitParam.


Пример Hello World сервлета


package net.ismailov.tests;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;


@WebServlet(name="hw", urlPatterns = "/hello_world")
public class HelloWorld extends HttpServlet{

  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    PrintWriter writer = response.getWriter();
    writer.println("<h1>Hello, World!</h1>");
    
  }

}


Хорошей новостью стало появление возможности динамической регистрации сервлетов:
ServletRegistration.Dynamic dynamic =
servletContext.addServlet(“DynamicTestServlet”, “net.ismailov.DynamicTestServlet”)
dynamic.addMapping(“/dynamicServlet”);



Для поддержки длительных операций добавилась возможность асинхронной работы сервлета. Для включения данной возможности необходимо:
//Либо декларативно указать поддержку асинхронного режима
@WebServlet(asyncSupported=true)
//Либо установить при динамической инициализации сервлета
dynamic.setAsyncSupported(true);


* This source code was highlighted with Source Code Highlighter.

После этого responce не перестает существовать по завершении метода. Необходимо вызвать
AsyncContext ctx = ServletRequest.startAsync(req, res)

* This source code was highlighted with Source Code Highlighter.

Теперь экземляры request и responce будут сохранены, и, по завершении выполнения асинхронного метода, могут быть использованы для вывода пользователю одним из методов AsyncContext.dispatch(/*различные параметры*/)

  @WebServlet("/MyAsyncTestServlet" asyncSupported=true)
  public class TestServlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse res) {
      ...
      AsyncContext aCtx = request.startAsync(req, res);
      ScheduledThreadPoolExecutor executor = new ThreadPoolExecutor(10);
      executor.execute(new MyAsyncService(aCtx));
    }
  }

  public class MyAsyncService implements Runnable {
    private AsyncContext ctx;
    public MyAsyncService(AsyncContext ctx) {
      this.ctx = ctx;
    }
    public void run() {
      // Может быть вызвана долгая операция с последующим выводом
      ctx.dispatch("/result.jsp");
  }



Как уже упоминалось, с новым API стало возможно задавать ограничения доступа, например:

@WebServlet(name="hw", urlPatterns = "/hello_world")
@ServletSecurity(@HttpConstraint(rolesAllowed = {"admin"}))
public class HelloWorld extends HttpServlet{


Естественно, задача задания ролей/пользователей, равно как и аутентификация, являются vendor-specific. К примеру, glassfish выдает basic http auth формочку:



Также имеется возможность накладывания ограничений на методы доступа:

@ServletSecurity(httpMethodConstraints={ @HttpMethodConstraint("GET"),
  @HttpMethodConstraint(value="POST", rolesAllowed={"user_group_1"})})



Основные нововведения в api 3.0 я постарался отразить, осталось лишь отметить, что с декабря прошлого года доступна "эталонная реализация" спецификации Java EE6: Glassfish 3.0, в которой можно поэкспериментировать с новым API.

Использование API Яндекс.Фотки в приложении Ruby on Rails

Уже пару лет пользуюсь сервисом Яндекс.Фотки для хранения всех своих фотографий. Причин выбора данного сервиса много, большинство из них выгодно отличают Фотки от конкурентных сервисов:

  • Неограниченный объем для хранения, нет никаких ограничений

  • Всегда можно получить доступ к оригинальному изображению

  • Удобно просматривать фотографии, группировать по альбомам, проводить поиск

  • Появилась возможность привязывать фотографии к картам

  • Видно, что сервис активно развивается



Недавно возникло желание на своей домашней страничке выводить последние n фотографий, загруженных на Яндекс.Фотки, тем самым убив сразу несколько зайцев:

  • домашняя страничка не требует администрирования

  • не требуется хранение и управление изображениями

  • не требуется заниматься ресайзингом изображений для превью и т.д.



Сказано -- сделано. Оказалось, что Фотки предоставляют достаточно удобный API по протоколу AtomPub.

Т.к. страничку планировал делать на платформе Ruby on Rails, после непродолжительных поисков нашел неплохую библиотеку для работы feedzirra. Устанавливалась библиотека совершенно стандартным образом:
  1. gem sources -a http://gems.github.com
  2. gem install pauldix-feedzirra
* This source code was highlighted with Source Code Highlighter.

и далее в ruby достаточно прописать ее использование:
  1. require 'feedzirra'
* This source code was highlighted with Source Code Highlighter.


Поизучав предоставляемый Яндексом API выяснилось, что список последних опубликованных пользователем фотографий можно получить по следующему URL:
http://api-fotki.yandex.ru/api/users/<ЛОГИН_ПОЛЬЗОВАТЕЛЯ>/photos/published/?limit=<КОЛИЧЕСТВО_ПОЛУЧАЕМЫХ_ИЗОБРАЖЕНИЙ>

* This source code was highlighted with Source Code Highlighter.

к тому же авторизация для этой операции не требуется. В документации был пример выходного XML, однако оказалось, что он не совсем соответствует действительности. Методом пристального дебага проблема была решена.

Таким образом, я создал некий класс-оболочку для изображения
  1. class ImageBundle
  2.  attr_accessor :url, :album_link, :title, :xxl_url
  3. end
* This source code was highlighted with Source Code Highlighter.

здесь url -- адрес превью картинки квадратной формы, album_link -- ссылка на альбом, title -- название картинки, xxl_url -- адрес картинки большого формата.

Далее в контроллере заполнил коллекцию полученных данных
  1. feed = Feedzirra::Feed.fetch_and_parse("http://api-fotki.yandex.ru/api/users/ligrimp/photos/published/?limit=35")
  2.   @images = Array.new
  3.   for image in feed.entries
  4.    image_url = image.links[3]
  5.  
  6.    i = ImageBundle.new
  7.    i.xxl_url= image_url.clone
  8.  
  9.    image_url["XL"] = "XXS"
  10.    i.url= image_url
  11.  
  12.    i.album_link = image.links[2]
  13.    i.title = image.title
  14.    @images << i
  15.   end
* This source code was highlighted with Source Code Highlighter.


Вывод на UI оказался делом техники, используя HAML для разметки и iLoad у меня получилось примерно следующее:

  1.  - for image in @images
  2.   %a{:href=>image.xxl_url, :target => "_blank", :rel=>"iLoad::Yandex.fotki", :title => image.title}
  3.    %img{:src=>image.url, :title => image.title}
* This source code was highlighted with Source Code Highlighter.

Итоговый результат можно увидеть на моей персональной страничке

суббота, 13 марта 2010 г.

Установка Flash 10 плеера на Ubuntu 9.10 x64

image
Нет ничего проще установки флеш плеера на Ubuntu 9.10 x64, требуется всего 2 шага:

  1. Скачать с сайта Adobe Labs архив;

  2. Извлечь из ахрихва файл libflashplayer.so и переместить в директорию .mozilla/plugins.