Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java EE (J2EE) и Spring > JSP — с чего начать?


Автор: nerezus 2.12.2006, 15:47
Знаком с веб-разработкой.
Знаком с синтаксисом джавы.

Хотел бы совместить. Я понял, что мне следует юзать JSP.

Что мне качать? ) Я понял, что томкат+апач?
ОС для разработки: винда. На серваке линух.

Какие советы можете дать?

Автор: tux 2.12.2006, 17:01
Apache тебе не нужен. В нем есть необходимость только при развертывании готовых приложений, да и то не всегда. В общем, обойдешься и одним Томкатом. Что касается JSP... Эта технология разработана Sun и ей активно продвигается. У JSP есть свои достоинства и недостатки. Большинство веб-приложений на Java разрабатываются с его использованием. Однако, вряд ли можно однозначно утверждать, что это лучший выбор. Почитай еще вот эту тему - http://forum.vingrad.ru/topic-44988/view/all/index.html. Возможно склонишься в сторону Velocity.

Автор: nerezus 2.12.2006, 18:15
Спссибо, поставил томкат. А как теперь хеллоуворлд в нем запустить? )

Автор: bingo 2.12.2006, 18:40
А там есть примеры (если не убрал галочку при установки), залазь на localhost и порт 8080 не забудь указать (он по-умолчанию), там найдешь необходимые ссылки.
Директория для веб-приложений tomcat/webapps, корень - ROOT.
Да что я рассказываю как маленькому, ты уже сам скорее всего разобрался.

Автор: Stampede 2.12.2006, 21:34
nerezus, очень приятно видеть, что люди тянутся в Java. Добро пожаловать в мир кроссплатформенных решений, открытых сырцов и могучей комьюнити! smile

А теперь по сути вопроса.
Цитата(nerezus @  2.12.2006,  05:47 Найти цитируемый пост)
Я понял, что мне следует юзать JSP.

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

Понимаешь, тут очень многое зависит от того, для чего тебе все это надо. Если ты занимаешься этим, рассчитывая в будущем устроиться на работу в качестве жабного веб-программиста, то тут без вариантов: JSP, Struts и все такое. Потому что это мэйнстрим и если ты всего этого не знаешь, тебя просто не поймут.

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

В общем, пиши.

ЗЫ. Хеллоуворлд запустил?

Автор: nerezus 3.12.2006, 20:49
Цитата

ЗЫ. Хеллоуворлд запустил?
 Да, что-то мне этот JSP не понравился ) Хз почему.
Надо будет сервлеты потыкать потом )

Вчера заказал книгу по J2ME ) Начну не с веба )

Автор: Dude03 4.12.2006, 00:35
Цитата(nerezus @  3.12.2006,  20:49 Найти цитируемый пост)
 Да, что-то мне этот JSP не понравился ) Хз почему.
Надо будет сервлеты потыкать потом )

Че прикольная вещится. Жаль что jsp преобразовывается в сервлет(Тормознутые какие-то)
Цитата(nerezus @  3.12.2006,  20:49 Найти цитируемый пост)
Вчера заказал книгу по J2ME ) Начну не с веба ) 

 smile 

Автор: Stampede 4.12.2006, 20:58
Цитата(nerezus @  3.12.2006,  10:49 Найти цитируемый пост)
Вчера заказал книгу по J2ME ) Начну не с веба )

"Однако вместо этого я съел пирожок" (с) сам знаешь кто smile

Автор: nerezus 4.12.2006, 22:15
Цитата

сам знаешь кто
  smile по статусу(под аватарой) предположил?  smile 

Так что там в мейнстриме? )

Автор: Stampede 4.12.2006, 22:27
Цитата(nerezus @  4.12.2006,  12:15 Найти цитируемый пост)
по статусу(под аватарой) предположил?

Дык! smile
Цитата(nerezus @  4.12.2006,  12:15 Найти цитируемый пост)
Так что там в мейнстриме?

Фсмысле?

Автор: nerezus 5.12.2006, 00:08
Цитата

Фсмысле?
 Ну джава для веба. Не верится, что только JSP ;)

Автор: check 5.12.2006, 00:22
Цитата(nerezus @  3.12.2006,  20:49 Найти цитируемый пост)
 Да, что-то мне этот JSP не понравился ) Хз почему.
Надо будет сервлеты потыкать потом )

Вчера заказал книгу по J2ME ) Начну не с веба ) 
Ну тогда наверное я вместо тебя...   Тоже поставил Томкат, запустил Хеллоуворлд и не только - понравилось.  Конечно ужаснула эта жуткая смесь HTML и Java-кода(какой уж там вью-модел-контроллер).  Но ничего не могу поделать - тянет к корням, к вебу. 

Автор: nerezus 5.12.2006, 00:26
Цитата

Конечно ужаснула эта жуткая смесь HTML и Java-кода
 Воот... мне бы что-нить MVC )

Автор: Stampede 5.12.2006, 00:43
Цитата(check @  4.12.2006,  14:22 Найти цитируемый пост)
Конечно ужаснула эта жуткая смесь HTML и Java-кода(какой уж там вью-модел-контроллер)

Цитата(nerezus @  4.12.2006,  14:26 Найти цитируемый пост)
Воот... мне бы что-нить MVC ) 

Правильной дорогой идете, товарищи! Только я так и не услашыл, вам жабный веб для какой надобности?

Автор: nerezus 5.12.2006, 06:47
Цитата

вам жабный веб для какой надобности?
 Да вот себе хомпагу решил новую сделать ))
Не, если серьезно, то пока просто попробовать хочу.

Автор: Stampede 5.12.2006, 07:12
Цитата(nerezus @  4.12.2006,  20:47 Найти цитируемый пост)
Да вот себе хомпагу решил новую сделать ))


Рассказывай, чего хочешь от хомепаги. От этого и будем плясать. Шаг за шагом. Потом оформим как туториал. Многим потом пригодится.

Я серьезно.

Автор: nerezus 5.12.2006, 10:21
Ну как минимум каркас. Т.е. ядро с подгрузкой модулей, реализующих определенный интерфейс.
Класс для работы с БД с возможностью логирования, фильтрации нежелательных запросов и т.д.
Унификация работы с AJAX. 
Унификация получения данных запроса.
Умный шаблонизатор, хотя думаю вариант с выводом XML прокатит (а потом просто на клиенте XSL преобразования сделать)

Естественно, сейчас я этого реализовать не могу на джаве, но хочу )
Вот  smile 

Автор: Stampede 5.12.2006, 11:38
Цитата(nerezus @  5.12.2006,  00:21 Найти цитируемый пост)
Т.е. ядро с подгрузкой модулей, реализующих определенный интерфейс


Не-не-не, "ты не умничай, ты пальцем покажи" (с) анекдот smile Какие модули, какой интерфейс?

Я имел в виду, что вообще будет на сайте? Ну контент, само собой. Что еще? Гостевая? Блог? Интерактив какой-нибудь?

Аякс - в каком месте? И чего ради? Аякс - это вообще целая отдельная песня. Было бы ради чего.

В общем, давай по порядку: какие разделы и что в каждом из них.

Автор: nerezus 5.12.2006, 19:36
Цитата

В общем, давай по порядку: какие разделы и что в каждом из них.
 Вот год назад на коленке сделал hunger:
http://HungeR.ru/

Из фишек:
Статичные страницы
Лента новостей. Лента релизов.
Каталог файлов(с подкатегориями)
Набор ссылок(к примеру, "друзья")
А набор фишек типа php2exe не нужен )

Сейчас страница собирается из кусков, дизайн в значительной части помешан с логикой. Этого бы делать не хотелось.
А аякс - я хотел еще FAQ прицепить. Вот там он будет нужен.

Автор: Stampede 5.12.2006, 21:36
Цитата(nerezus @  5.12.2006,  09:36 Найти цитируемый пост)
Вот год назад на коленке сделал hunger:
http://HungeR.ru/


Да, я его уже смотрел. Значит, для начала воспроизводим то что есть, так? Хорошо, поехали.

Я предлагаю следующий формат: я даю задание, ты его выполняешь. Если по ходу возникают вопросы - обсуждаем. Через несколько итераций будешь с новым движком на Java.

Если все устраивает, у меня есть наготове первое задание.

Автор: nerezus 5.12.2006, 22:35
Гут )
JSP?

Автор: Stampede 5.12.2006, 22:49
Цитата(nerezus @  5.12.2006,  12:35 Найти цитируемый пост)
JSP? 

Чур, чур меня smile

Значится, так: для начала надо вообще забыть на время про слово веб. Это важно. То есть делаем просто standalone приложение. В нем будет один главный класс - точка входа во всю прогу. Назовем его ru.hunger.Hunger.

Для простоты лукапа (look-up) сделаем этот класс синглтоном. Вообще синглтоны - это не есть хорошо (почему - потом поговорим отдельно, когда придет время), но для начала сойдет.

Кроме этого, нам понабдибится класс Configuration, в который мы временно зашьем важные параметры конфигурации: параметры базы данных, кодировки и пр. Потом все это будет читаться из файла.

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

Выходная форма Задания 1

Классы: 
  • Hunger
  • Configuration
  • TestHunger

Структуру пакетов выбрать по своему усмотрению. Запуск программы - из класса TestHunger, стандартным main(). Hunger должен выполнить свою инициализацию и выдать отчет о текущей конфигурации.

Все, на этом пока все.

И еще одно замечание: нам по ходу придется время от времени делать рефакторинг: перегруппировывать пакеты, выносить вещи в интерфейсы и пр. Это нормально. Просто если с самого начала делать все так, как оно будет в готовом виде, то целесообразность многих решений не будет вполне очевидной.

Рефакторинг сам по себе вещь достаточно муторная из-за объема правок, но на наше счастье большинство приличных IDE берут на себя большую часть рутины, так что на практике все оказывается достаточно просто. Ты какой IDE пользуешься? Если еще не выбрал, то советую взять Eclipse - у него плагинная архитектура и большое число полезных плагинов. Да и вообще штука приятная в работе. Правда, сторонники IDEA утверждают, что IDEA намного круче, но она стоит котлету денег, а пользовать кряки, как я понимаю, тебе не позволяет религия.

Автор: nerezus 6.12.2006, 00:11
Угу, т.е. просто чтение конфига классом Hunger? )

Вопрос появился: какие имена у класса стандартного логгера  и класса для чтения из файла? ) Чтобы знать, по каким словам koders.com для примеров мучать )

Автор: Stampede 6.12.2006, 00:38
Цитата(nerezus @  5.12.2006,  14:11 Найти цитируемый пост)
Вопрос появился: какие имена у класса стандартного логгера  и класса для чтения из файла?


Нет-нет, это пожалуйста самостоятельно. Вообще все нужно будет делать самостоятельно, читая для этого спеки, доки, статьи, обсуждения и пр, , потому что только таким образом можно сложить сколько-нибудь отчетливую картинку, что и как устроено в Java. Я и другие можем только корректировать этот процесс.

Взять хоть тот же "класс для чтения из файла". А нету такого класса! Потому что весь ввод-вывод завязан на идее потоков. Или вот логгер: почему он такой, какой есть, а не какой-нибудь иной? Какими соображениями руководствовались его архитекторы? Что было его идейным прототипом?

В общем, со всем надо разбираться.

Автор: nerezus 6.12.2006, 00:48
Ок, идея понятна )

Автор: batigoal 6.12.2006, 12:32
Я тоже постараюсь участвовать в этой затее, если
а) Stampede и nerezus не против;
б) позволит время.

Автор: Stampede 6.12.2006, 15:39
batigoal, я не против, но тут возникает несколько вопросов: Например, по кому будем делать зачет? По первому выполнившему задание? По последнему?

Потом, сколько веток будем поддерживать? Две разные? Запутаемя. Одну? А если возникнут разногласия?

Далее, если один запостит свое решение раньше, это неизбежно повлияет на ход выполнения задания другого. Хотелось бы избежать.

Поэтому, batigoal, учитывая, что изначальная инициатива принадлежит 
nerezus'у, и объем для изучения у него явно больше, давай ты присоединишься на принципах минимального вмешательства? То есть темп отмеряем по nerezus'у, раньше него с ответами не лезем и за окончательный принимаем вариант, который выберет nerezus. То есть ты как бы присоединяешься с правом совещательного голоса.

Я понимаю, что это не самые привлекательные условия, ну дак а что делать? Иначе пострадает качество учебного процесса.

Автор: tux 6.12.2006, 15:51
Модератор: Для более удобного наблюдения за сериалом зафиксировал тему.

Автор: batigoal 6.12.2006, 16:33
Цитата(Stampede @  6.12.2006,  16:39 Найти цитируемый пост)
Я понимаю, что это не самые привлекательные условия, ну дак а что делать?

Главное, оптимальные.

К тому же, моя скорость вполне может быть ниже (наш проект перешел в эндшпиль, потому...)

Автор: Stampede 6.12.2006, 17:16
Цитата(tux @  6.12.2006,  05:51 Найти цитируемый пост)
Для более удобного наблюдения за сериалом

Хорошо хоть мыльной оперой не обозвали smile Спасибо за закрепление темы.

Автор: nerezus 7.12.2006, 00:10
У меня на сон уходит меньше 5 часов в день, напряженка, поэтому, думаю, обучение будет медленно.
Но с конфигами через FileReader разобрался.
логирование не нашел еще(искал мало), но, думаю, на крайний случай свой напишу )

Автор: Stampede 7.12.2006, 00:29
Цитата(nerezus @  6.12.2006,  14:10 Найти цитируемый пост)
У меня на сон уходит меньше 5 часов в день, напряженка, поэтому, думаю, обучение будет медленно.


Ты Java по методу Илоны Давыдовой собрался учить? smile

По заданию: то, что с FileReader'ом разобрался, это хорошо. Только если ты внимательно читал условие, читать на самом деле пока ничего не надо. Я предлагал пока зделать заглушку будущего объекта Configuration с жестко прописанными параметрами. Потому что в дальнейшем этот объект будет инициализироваться из файла XML. А для парсения XML в Java не нужно предварительно зачитывать весь файл в память.

Автор: nerezus 8.12.2006, 20:26
Цитата

Ты Java по методу Илоны Давыдовой собрался учить?
 А что за метод?  smile 
Просто у меня напряженка в реале =)


Код

package ru.hunger;

import java.util.logging.*;
import java.io.IOException;


public class Hunger{
    private static Hunger _instance = new Hunger();
    private Configuration config;
    private Logger logger;
    private FileHandler fh;

    protected Hunger() {
        config = new Configuration();
        logger = Logger.getLogger("ru.hunger.hunger");
        try {
            fh = new FileHandler("hunger.txt");
        } catch (IOException e) {
            System.out.println("Cannot open logfile");
            e.printStackTrace();
        }
        logger.addHandler(fh);
        logger.setLevel(Level.ALL);
    }

    public static Hunger getInstance() {
        return _instance;
    }

    public String getConfigItem(String str) {
        return config.getItem(str);
    }
    
    public void log(String str) {
        logger.info(str);
    }
}



Код

package ru.hunger;

public class TestHunger {
    public static void main(String[] args) {
        Hunger hunger = Hunger.getInstance();
        System.out.println(hunger.getConfigItem("preved"));
        hunger.log("hello");
    }
}


Код

package ru.hunger;

public class Configuration {

    public String getItem(String itemName) {
        return itemName;   // заглушка
    }
}


Автор: Stampede 8.12.2006, 22:12
Цитата(nerezus @  8.12.2006,  10:26 Найти цитируемый пост)
 А что за метод?

Да был такой лохотрон в начале 90-х - аудиокассеты для обучения английскому во сне smile

То, что с временем туго - это, конечно, не очень здорово. Понимаешь, я пытаюсь напривлять твою разработку в стиле agile programming (шустрое программмирование) - слышал, наверное, про такую методологию. Ну и вот, а если на каждое задание будет уходить по нескольку дней, то эффект будет маленько смазанным. То есть на самом деле разницы большой нет, но вот психологически будет по-другому восприниматься.

Но понятно, что жизнь есть жизнь и получается так, как получается. Ладно, давай к заданию.

В принципе все очень неплохо, но есть ряд недочетов, которые лучше устранить. Перечисляю в призвольном порядке:

1. Имя переменной _instance противоречит конвенции и из-за этого несколько "режет глаз". Подробнее о соглашении об именах - вот здесь: http://java.sun.com/docs/codeconv/html/CodeConventions.doc8.html#367

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

Как это делается в штатном логгере - я, честно говоря, совершенно без понятия, так что на-ко вот скачай книжку и разберись. Заодно почитаешь там про log4j, и может быть его и выберешь. Вообще, логирование в Java - это вещь достаточно запутанная и капризная. Почему - станет ясно, когда начнешь подключать сторонние библиотеки и пытаться настраивать их вывод. В общем, сделай внешнюю конфигурацию. Книжку выкладываю вот сюда: http://bfigeiro.googlepages.com/logging.zip.

3. Тестовых классов у нас еще будет много, поэтому лучше для них создать отдельный пакет, например ru.hunger.test, и перенести туда TestHunger.

4. Классическая реализация синглтона требует приватного дефолтного конструктора, а иначе появляется возможность унаследовать от твоего класса, и вот уже у тебя в программе больше одного объекта данного типа.

5. Когда ты начнешь плотнее работать с приложением, у тебя окажется не одно рабочее окружение, а несколько. Соответственно, нужно будет как-то указывать, откуда брать конфиг. Поэтому для класса Hunger нужно как-то предусмотреть возможность задания рабочей директории. Поскольку от конфига зависит вся дальнейшая раскрутка, имеет смысл завести метод с такой сигнатурой:
Код

public void init(String workDir)

Понятно, почему это надо делать не в конструкторе? Потому что класс у нас синглтонный, конструктор приватный, а инфы о том, где брать конфигурацию, на момент создания экземпляра еще нету.

6. Рассмотрим метод getConfigItem(). Это хорошо, что параметров конфига у нас пока мало. А когда будет много, мы что же, будем дублировать все методы из класса Configuration? Нет, нам этого не надо. Лучше просто отдавать объект Configuration - и пусть кому надо смотрят в нем что хотят.

7. Метод log() нам тоже не нужен. Этак мы сами себя подстрекаем использовать один общий логгер на всю прогу. А идея-то с логгерами в том, чтобы можно было, в частности, управлять уровнями логирования на уровне иерархий классов. Так что логгеры у нас в каждом классе будут свои.

8. Ввиду предыдщих двух пунктов, не очень правильно заставлять класс-раскрутчик (TestHunger) выпытывать подробности конфигурации. Раскрутчик должен сделать всего две вещи: получить ссылку на синглтон Hunger и проинициализировать его значением пути к рабочей директории. Путь имеет смысл брать из аргументов запуска программы. Если путь не указан, то берется текущая директория. А выводом параметров конфига должен заниматься сам класс Configuration, по указке Hunger.

9. Да, классу Configuration тоже не помешает знать путь к рабочей директории. Удобно будет задавать его через параметр конструктора.

В общем, предлагаю переделать с учетом замечаний.

Автор: nerezus 8.12.2006, 22:25
Угу, спасибо =)
Только я наверно это только завтра смогу сделать =\
По крайней мере в воскресенье надеюсь уделить часов 12 джаве =)

Автор: nerezus 10.12.2006, 12:59
Вот в общем написал, но чую, что плохо.
Пока почитаю про коллекции )
Еще примерно какие области надо знать в первую очередь? Коллекции, потоки, сеть(http, сокеты), файлы....

Код

package ru.hunger.test;

import ru.hunger.Hunger;

public class TestHunger {
    public static void main(String[] args) {
        Hunger hunger = Hunger.getInstance();
        hunger.init("", "hunger.xml");
        System.out.println(hunger.config.getItem("preved"));
    }
}



Код

package ru.hunger;

public class Configuration {
    public String workingDirectory;

    public String getItem(String itemName) {
        return itemName;   // заглушка
    }

    public void setWorkingDirectory(String workingDirectory) {
        this.workingDirectory = workingDirectory;
    }
}



Код

package ru.hunger;

import java.util.logging.*;
import java.io.IOException;


public class Hunger{
    private static Hunger instance = new Hunger();
    public Configuration config;
    private Logger logger;
    private FileHandler fh;
    private String filename;
    private String workingDirectory;

    private Hunger() {
        config = new Configuration();
    }

    public static Hunger getInstance() {
        return instance;
    }

    public void init(String workingDirectory, String filename) {
        config.setWorkingDirectory(workingDirectory);
        this.workingDirectory = workingDirectory;
        this.filename = filename;
        logger = Logger.getLogger("ru.hunger.hunger");
        try {
            fh = new FileHandler(filename);
        } catch (IOException e) {
            System.out.println("Cannot open logfile");
            e.printStackTrace();
        }
        logger.addHandler(fh);
        logger.setLevel(Level.ALL);
    }
}


Автор: Stampede 15.12.2006, 22:57
nerezus, так не пойдет. Или мы делаем проект, или не делаем. Из девяти замечаний, которые я привел, ты отреагировал только на пункты 1, 3, 4, 5, 7, да и то не в лучшем виде.

Зачем сделал публичной переменную config? Там просто геттер нужен. Где метод в Configuration, который возвращает текстовое описание конфига? Можно для этой цели переопределить toString(). Почему по-прежнему явно инициализируешь логгер? Зачем из TestHunger пытаешься вывести значение конфиг параметра? А если будет десять параметров? А если двадцать тестовых классов (хинт: со временем и тех, и других может оказаться намного больше).

И потом, что это за второй параметр в init()? Передаешь в него вроде файл конфига, а используешь для вывода логов. Неряшливость, невнимательность.

В общем, если хочешь продолжать, то давай работать, если нет - нет.

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

Код

log4j.rootLogger=DEBUG, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n


Переменная логгера определяется в одну строчку:

Код

Logger logger = Logger.getLogger(YourClass.class);


Жду новостей.

Автор: Omut 28.12.2006, 21:23
Tак, тема уже не зафиксирована smile . Люди! Спасите сериал! Может batigoal всё же "поработает" учеником smile . Stampede, Вы не против?

Автор: tux 28.12.2006, 21:29
Omut, а сам? Нет желания поучаствовать? Если согласен, верну тему на место. Хотя... Смотря, что скажет Stampede.

Автор: nerezus 29.12.2006, 07:22
Я тоже за то, что batigoal поработает, т.к. у еня сложилось предвзятое мнение к джаве в вебе...
Я конечно извиняюсь за потраченное время, но.... короче еще раз извиняюсь =(

Автор: batigoal 29.12.2006, 11:45
Цитата(nerezus @  29.12.2006,  08:22 Найти цитируемый пост)
Я тоже за то, что batigoal поработает

У меня назрело решение об альтернативном проекте, так что я теперь "вне игры" smile

Автор: Omut 29.12.2006, 12:11
Цитата(batigoal @  29.12.2006,  11:45 Найти цитируемый пост)
Цитата(nerezus @  29.12.2006,  08:22 )
Я тоже за то, что batigoal поработает


У меня назрело решение об альтернативном проекте, так что я теперь "вне игры"  


Блин!!!!!

Автор: Hidrag 30.12.2006, 01:01
А могу я себя выставить в роли ученика?
..правда есть уже небольшой опыт (ссылка в подписи - все на JSP/Servlet сделано) но многое еще не осовоил, например куки, JavaBeen... да многое наверное...

Добавлено @ 01:02 
nerezus, почему сложилось такое мнение?

Автор: Stampede 31.12.2006, 00:05
Цитата(nerezus @  28.12.2006,  21:22 Найти цитируемый пост)
т.к. у еня сложилось предвзятое мнение к джаве в вебе...


nerezus, а ведь мы с тобой к вебу еще даже не притронулись. Похоже, тебя отпугнула именно Java как таковая. Что я могу на это сказать... Да, в изучении Java существует некоторый начальный порог, но если его пройти, дальше все становится не так грустно. Так что может быть когда-нибудь еще вернешься.

Цитата(Hidrag @  29.12.2006,  15:01 Найти цитируемый пост)
А могу я себя выставить в роли ученика?
..правда есть уже небольшой опыт (ссылка в подписи - все на JSP/Servlet сделано) но многое еще не осовоил, например куки, JavaBeen... да многое наверное...


Ну что ж, давай попробуем. Исходные данные сопоставимы. То, что есть опыт с JSP - вообще отлично: значит шишки уже набиты smile

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

Автор: Hidrag 31.12.2006, 00:57
Хочется развить сайт в такой некий опен-сорс ресурс, типа в вкладке проектов будут описания начатых проектов, если посетитель не зареген то он может скачать только бинарникили вообще ничего, если зареген то и исходник, и например обновить файлы проекта (например что то дописал)  - пока только это в голову пришло... 

Да и хостинг там сильно ограничен всего 10 мб и отсутствует возможность средствами сервлета создавать и редактировать файлы, а так бы админку сделал бы для наполнения содержимым, правда есть доступ к БД, можно сделать админку типа, написал текст для главной странице он залился в базу, потом при вызове этой страницы данные из базы считались и вывелись... Еще на хостинге нельзя почтовиком пользоваться, придется функцию "Восстановления пароля" сделать такой чтобы данные о таком запросе вбивались в базу а админ потом уже сам проверял и отсылал в ручную...

Еще была идея сделать гостевую... но не как обычно в виде мини форума, а в виде аплета. Аплет как паинт, т.е посетитель рисует на нем мышкой, жмет - сохранить и этот рисунок отображается на странице, естественно регулировать количество этих "художеств"...

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

Одна из задач такая - для пользователя со статусом админа показывать юзеров. Одно из решений это считать данные из базы в колекшн а потом значения из колекшена выводить в JSP в цикле... может есть более красивое решение? Типа считать данные  в некий датасет, кинуть на страницу таблицу где источником данных взять этот датасэт (влияние долгого программирования на Delphi)?

воть... smile

Всех с наступающим! smile Думаю минимум числа до 3-4 буду "занят" smile а потом можно будет заняться всем этим...

Автор: Hidrag 8.1.2007, 15:55
Итак, первая задача:

1. Сделать страничку админки - слева размещаются ссылки: "Юзеры", "Заявки на восстановление паролей" - пока только эти, таблицы базы будут появляться, будут появляться новые ссылки.  По щелчку на ссылке к БД идет запрос (стандартный селект *) и данные выводятся в таблицу, после чего можно любую запись этой таблицы изменить/удалить соответственно к БД полетит что то типа апдейт или делит - админка состоит из одной веб-странички - слева ссылки, в центра таблица, справа кнопки удалить/изменить/вставить...

Как работать с БД, для меня не проблема, проблема в том что я не знаю как нарисовать таблицу и вывести в нее данные, кроме как запихать результат запроса в коллекцию... и потом как сделать возможность редактирования данных этой таблицы и чтобы потом передать верный sql экзекьюту smile

Автор: Иван Человеков 8.2.2007, 12:34
Ребята хотел от себя добавить. В изучении чего либо существуют пороги, пройдя который ты уже будешь легче двигаться вперёд. Видимо они будут всегда, но самый трудный, видимо, и высокий – это первый, он окутан страхом и сомнением: «Смогу ли я?». 
Может придти и более коварная, потому как незаметная мысль: «Сменить предмет обучения, потому как он мне не по душе, либо не подходит». Например: «Человек взялся за Java (нужное вписать), месяц или два поучив, он говорит не мне не нравится синтаксис, сама её идея, надо знать английский, потому как документация к ней почти вся на английском, возьму-ка я, Delphi (нужное вписать)!»  
Почему мысль коварная? Да потому, что она постоянно будет приходить, и скорее всего Вы будете поступать уже по шаблону – выработавшейся привычке.
Бывают и исключения, человек часто занимается тем, что ему не по душе, а что круто или за это много платят денег smile  Если он у меня спросит совета – скажу, что это не самый лучший выбор, который он делает. Думаю можно всегда найти то, что тебе нравится делать и за это буду платить хорошие деньги smile
Не нравится быть программистом не будь им smile И парикмахеру можно зарабатывать хорошие деньги, просто не надо стандартно подходить к этому вопросу smile Открой свою парикмахерскую, работай там сам. Через года 3-4 ты уже будешь управляющим, вот здесь твой бухгалтер, вот здесь зал и твои сотрудники стригут людей. Вот здесь делают маникюр smile Хочется – становись и делай причёски вместе с сотрудниками, а хочешь просто учи и показывай smile  
Любимое дело будет давать плоды не только в деньгах, а в моральном удовлетворении smile А это – здоровье и счастье человека, то есть конечная цель smile
Во как, а начал с порогов, которые сам преодолеваю smile 

Удачи и здоровья smile

Автор: KaKTyCc 10.2.2007, 23:41
Доброго времени суток!

Я тоже задал себе вопрос, JSP — с чего начать? И думаю я не один.
Хочется заниматься веб разработкой на Java.
Сейчас очень много всевозможных фрэймворков, технологий и т.д. И действительно задаёшься вопросом с чего начать  и что изучать, чтобы максимально быстро разобраться в основном и главном, а остальное уже прирастёт.

Кто подскажет? С Java не знаком. Сейчас работаю Oracle Developer'ом. В будущем хочу заниматься разработкой веб приложений на Java под Oracle.

Автор: Иван Человеков 12.2.2007, 08:03
KaKTyCc, начни с ичучения языка ява smile Потом, можно будет приступить J2EE smile

Автор: Dagger 20.2.2007, 22:10
Народ, могу продолжить сериал. Кто-то тут из джава программеров хочет еще делиться опытом?:-)

Автор: Tony 20.2.2007, 22:27
Цитата(KaKTyCc @ 10.2.2007,  23:41)
Доброго времени суток!

Я тоже задал себе вопрос, JSP — с чего начать? И думаю я не один.
Хочется заниматься веб разработкой на Java.
Сейчас очень много всевозможных фрэймворков, технологий и т.д. И действительно задаёшься вопросом с чего начать  и что изучать, чтобы максимально быстро разобраться в основном и главном, а остальное уже прирастёт.

Кто подскажет? С Java не знаком. Сейчас работаю Oracle Developer'ом. В будущем хочу заниматься разработкой веб приложений на Java под Oracle.

Последнее вреня о4ень много вижу, 4то c++ девелоперы мигрируют на java. Почему?

Автор: powerOn 21.2.2007, 00:37

M
powerOn
Не надо оффтопить.



Автор: Hidrag 21.2.2007, 11:36
ИМХО тему можно откреплять, все что нужно теперь можно найти в факе в разделе студенческого отдела кадров, Антон там все хорошо расписал.

Автор: tux 21.2.2007, 11:48
Цитата(Hidrag @  21.2.2007,  11:36 Найти цитируемый пост)
ИМХО тему можно откреплять, все что нужно теперь можно найти в факе в разделе студенческого отдела кадров, Антон там все хорошо расписал. 

Подождем до весны, может кто созреет. smile

Автор: fixxer 23.5.2007, 13:04
Кадетов еще набираете?

Автор: Stampede 23.5.2007, 16:58
fixxer, рассказывай, чего хочешь - покумекаем smile

Автор: fixxer 24.5.2007, 09:38
Хочется продолжить начатый тур. Цель - научится проектировать грамотную  архитектуру. (Loose coupled и все такое) База java есть, но много  белых пятен. Да и с ЕЕ мало опыта.

Автор: Izabela 24.5.2007, 11:46
Цитата

Конечно ужаснула эта жуткая смесь HTML и Java-кода

nerezus, сли использувать JSTL теги---будет менше смести smile

Добавлено через 1 минуту и 25 секунд


Цитата

Конечно ужаснула эта жуткая смесь HTML и Java-кода

nerezus, сли использувать JSTL теги---будет менше смести smile 

Автор: Izabela 24.5.2007, 12:07
аааааааааааааа....ступила что тема уже на 4 страницах............
 smile  и вопше то я тоже учитса хочу........
Stampede,

Автор: Stampede 24.5.2007, 18:38
Цитата(fixxer @  24.5.2007,  00:38 Найти цитируемый пост)
Цель - научится проектировать грамотную  архитектуру... Да и с ЕЕ мало опыта.


А, понятно. Боюсь, тут я помочь не смогу. Это тебе надо сюда: http://java.sun.com/javaee/5/docs/tutorial/doc/. Там как раз все грамотно и пре EE.

Та архитектура, которую я могу предложить - оне не столько "грамотная", сколько "с позиций здравого смысла". Увы, при нынешнем положении дел в Java-технологиях эти два понятия совпадают далеко не всегда и не везде.

Поэтому еще раз: все, кто преследуют абстрактный интерес - пожалуйста, используйте официальную литературу и общепринятые подходы. Так будет полезнее для вашей карьеры и спокойнее для души. Сюда же прошу обращаться только тех, кто замыслил свой личный онлайновый проект - который планируется разрабатывать, сопровождать и развивать самостоятельно. Вот тут сразу найдется о чем поговорить. Пока что ближе всех к такому определению был автор этой темы nerezus, но он, похоже, разочаровался в Java. А жаль.

Всякие Ruby on Rails, конечно, подкупают легкостью начального вхождения, но в какой-то момент ведь все "домашние заготовки" из серии "convention over configuration", которыми и обусловлена эта легкость, закончатся, и придется таки заняться реальным программированием. И тут Java ой как поборется... smile

Автор: nerezus 24.5.2007, 19:08
Цитата

Пока что ближе всех к такому определению был автор этой темы nerezus, но он, похоже, разочаровался в Java. А жаль.
 В определенной степени верно, просто сейчас основная причина в нехватке времени + я еще не определился, чем хочу заниматься. Ну и конечно лень, но с ней пытаюсь бороться всеми возможными (и невозможными) методами, причем довольно-таки успешно.

Собственно со времени открытия темы успел поковырять J2ME(причем очень успешно) и Python, где впервые увидел РАБОТАЮЩУЮ реализацию ORM(в свое время хотел писать такое под пхп, но потом как-то поугас).

Сейчас заказал себе трехтомник Дейтелов по джаве и кучу книг по теории программирования, но сейчас почему-то считаю, что при программировании опыт набирается гораздо быстрее, чем при чтении ) Тот же xmlpull освоился за 5 минут, в то время, как в книгах уделяют парсингу XML намного больше места и времени.

Сейчас для себя поставил ряд целей, среди которых есть и сайт на джаве(на вашем фреймворке, кстати).

Однако самая главная цель - дописать оставшиеся 90 страниц документации за следующие 15 часов, с чем и удаляюсь ;)

Автор: fixxer 24.5.2007, 21:33
Цитата

Это тебе надо сюда: The Java EE 5 Tutorial.

Да, вот это я наверное и сам как-нибудь освою. Просто мне очень импонирует Ваш подход когда ядро системы проектируется в отрыве от морды. Я сам об этом думал, но наверное опыта маловато. И в начале топика понял что процесс пойдет именно по этому пути. Но nerezus  всех обломал. Поэтому и хотелось продолжить. Ну нет так нет.
Да, если не сложно, поясните, пожалуйста, почему по Вашему мнению грамотная и не противоречащая здравому смыслу архитектура порой различаются. Как по мне так это нонсенс.

Автор: Hidrag 24.5.2007, 22:31
Все правильно, это как автомобиль! Сборка начинается с двигателя и заканчивается уже кузовом - сначала логика и движок приложения, потом уже и гуишник или веб морда ко все этому крепятся.

Автор: Stampede 25.5.2007, 01:42
Привет, nerezus - скока лет, скока зим smile

Цитата(nerezus @  24.5.2007,  10:08 Найти цитируемый пост)
но сейчас почему-то считаю, что при программировании опыт набирается гораздо быстрее, чем при чтении


Правильно считаешь. Книшки, конечно, пригодятся, хотя бы в качестве справочника, но без реальной практики программирования никакого опыта не прибавится. Причем речь не об учебных примерах, а именно о реальных проектах.

Цитата(nerezus @  24.5.2007,  10:08 Найти цитируемый пост)
Сейчас для себя поставил ряд целей, среди которых есть и сайт на джаве(на вашем фреймворке, кстати).


Похвальный выбор smile Когда дойдут руки до реализации - обращайся, помогу всемерно.

Теперь fixxer.

Цитата(fixxer @  24.5.2007,  12:33 Найти цитируемый пост)
Просто мне очень импонирует Ваш подход когда ядро системы проектируется в отрыве от морды.


Я очень рад, что ты выделяешь этот аспект. Для многих это совсем не очевидно, так что комментарий Hidrag'а тут совершенно мимо кассы: и пример неудачный, и тезис не подкрепляется практикой - и мы к этому еще вернемся. Что касается продолжения сериала с твоим участием: понимаешь, нужна задача, цель. Нужно, чтобы ты зачем-то хотел довести дело до конца. Иначе все бросится на полпути. Только впустую потратишь свое и чужое время. Это я тебе как гносеолог со стажем говорю, ибо практика показывает, что эффективность когнитивного процесса напрямую определяется степенью мотивации.

Ну и напоследок разъяснение.

Цитата(fixxer @  24.5.2007,  12:33 Найти цитируемый пост)
Да, если не сложно, поясните, пожалуйста, почему по Вашему мнению грамотная и не противоречащая здравому смыслу архитектура порой различаются. Как по мне так это нонсенс.


Эх, чувствую, начать придется издалека...

Если взглянуть вокруг внимательным глазом, то нетрудно заметить, что нонсенсы такого рода окружают нас буквально повсюду. Вот навскидку пара примеров.

Пример 1. В моей цифровой мыльнице (Кодак) при разряде батареек слетают все настройки. Приходится каждый раз выставлять их заново. Еб@н@фты! У вас в аппарате 32 мега встроенной энергонезависимой памяти - неужели нельзя было отвести кусочек под хранение настроек???

Пример 2. У меня в машине магнитола, часы и розетка под прикуриватель работают только при включенном зажигании. Нахера?! Где блин логика? Получается, если что-то надо (например, элементарно подзарядить сотовый или влючить музыку) - приходится вставлять ключ и включать зажигание. В результате пару раз на кемпинге приходилось заводиться прикуриванием.

Примеров такого рода - миллион. О чем это говорит? О том, что мир не всегда и не во всем устроен в гармонии со здравым смыслом. Например, в случае с техническими девайсами наивный потребитель может думать, что рынок и конкуренция должны заставлять производителей выпускать изделия высочайшей степени функциональности, юзабилити и потребительских характеристик. А на деле выходит, что главное, к чему подталкивает рынок - к регулярному выпуску новых моделей. А какие они при этом получаются, всем ровным счетам наплевать.

Примерно такая же петрушка и с софтом. Sun'у по большому счету пофиг, хорошо ли, удобно ли разрабатывать приложения под их спецификации. У них свой процесс разработки индустрийных стандартов. Достаточно непростой, надо заметить, процесс, и при этом взаимоувязанный со множеством других процессов: документрование, написание туториалов, разработка программ сертификации, работа с партнерами, и многое-многое другое.

Неудивительно, что при таком положении дел многие вещи появляются на свет в виде, достаточно далеком от идеала - особенно учитывая концентрацию теоретиков во всяких комитетах по стандартам. Но раз уж появился - делать уже нечего: стандарт есть стандарт. И начинается "мыши плакали, кололись, но продолжали есть кактусы". Например, счастливо отведать Java DOM.

Когда противоречия между желаемым и действительным достигают особой остроты - ну, приходится как-то реагировать. Как правило, перенимают решения, ставшие стандартом де-факто. Так появился EJB 3 (Hibernate), пакеты concurrent (oswego), logging (log4j) и многое другое. Но до многих вещей руки так и не доходят. Может, со временем вместо неработающего JAAS приспособят Acegi. Может, канонизируют легковесные контейнеры, взяв за основу Spring. Ну а пока - вот так, как есть.

Так что ничего особенно необычного - не более, чем во всем остальном. Но разве кто-то обещал, что будет легко? smile

Автор: fixxer 25.5.2007, 06:37
Stampede, спасибо за объяснение. Просто я пока архитектуру воспринимаю с точки зрения ООП, и привязка (увязка) технологического стека к domain model, это наверное следующий этап моего развития. smile

Автор: vzf 25.5.2007, 09:45
Stampede, можете рассказать в каких случаях не работает JAAS и чем Acegi лучше.

P.S. Просто интересно smile 

Автор: Stampede 25.5.2007, 19:27
Цитата(vzf @  25.5.2007,  00:45 Найти цитируемый пост)
Stampede, можете рассказать в каких случаях не работает JAAS и чем Acegi лучше.


Нет, это будет оффтоп. Можете создать отдельную тему, если есть желание.

Автор: diablero 19.6.2007, 18:03
Пожелал стать новым учеником, и получил на это согласие.
Задача. Основная цель изучения это реализация своего проекта. Сайт не сложный, со стандартными сервисами ,к примеру, http://real-english.ru. 

log4j.properties. Пишет все в консоль и файл.
Код

log4j.logger.Appender = DEBUG, Console, File

log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%d{dd MMM yyyy HH:mm:ss,SS}] SYN %-5p %m %n

log4j.appender.File=org.apache.log4j.RollingFileAppender
log4j.appender.File.file=${infinite.home}/log/application.log
log4j.appender.File.MaxFileSize=1000KB
log4j.appender.File.MaxBackupIndex=9999
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern=[%d{dd MMM yyyy HH:mm:ss,SS}] %-5p %m %n


Код

package ru.selfexpression;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import java.io.File;

public class Infinite {
    private static Infinite instance;
    private Configuration config;
    private Logger logger;
    private String homeDir;

    private Infinite() {
        homeDir = System.getProperty("user.dir");
        System.setProperty("infinite.home", homeDir);

        config = new Configuration(homeDir+"/conf/infinite.properties");

        File propertiesFile = new File(homeDir+"/conf/log4j.properties");
        PropertyConfigurator.configure(propertiesFile.toString());
        logger = Logger.getLogger("Appender");
    }

    public static Infinite getInstance() {
        if (instance == null) {
            instance = new Infinite();
        }
        else return null;
        return instance;
    }

    public void  init(String homeDir) {
        if(homeDir == null) {
            throw new NullPointerException();
        }
        this.homeDir = homeDir;
        System.setProperty("infinite.home", homeDir);
    }
    public void  setLogFileName(String fileName) {
        if(fileName == null) {
            throw new NullPointerException();
        }
        // logger
    }
    public Configuration getConfiguration() {
        if(config == null) {
            throw new NullPointerException();
        }
        return config;
    }

    public void dispose() {
        logger = null;
        config = null;
        instance = null;
    }
}




Код

package ru.selfexpression;

import java.io.File;
import java.io.FileNotFoundException;

public class Configuration {
    private String confFileName;

    public Configuration(String confFileName) {
        this.confFileName = confFileName;
        File file = new File(confFileName);
        if(!file.exists()) {
            try {
                throw new FileNotFoundException("File \""+ confFileName +"\" not found");
            } catch (FileNotFoundException e) {

            }
        }
    }
    public String toString() {
        return "Configuration: "+confFileName+"\n"+
                "key1 = value1; key2 = value2; key3 = value3";
    }
}


Код

package ru.selfexpression.test;

import ru.selfexpression.Infinite;
import java.io.File;

class Test {

    public Test() {
    }
    private static String getHomeDir(String arg) {
        File file = new File(arg);
        if(!file.isDirectory()) {
            throw new IllegalArgumentException("\""+arg+"\" It not directory");
        }
        return arg;
    }
    private static String getLogName(String arg) {
        if(arg.length()==0) {
            throw new IllegalArgumentException("\""+arg+"\" invalid file name");
        }
        return arg;
    }
    public static void main(String arg[]) {

        String homeDir = null, logName = null;
        if(arg.length==2) {
            homeDir = getHomeDir(arg[1]);
        }
        if(arg.length==4) {
            homeDir = getHomeDir(arg[1]);
            logName = getLogName(arg[3]);
        }
        Infinite infinite = Infinite.getInstance();
        if(homeDir!=null) {
            System.setProperty("infinite.home", homeDir);
            infinite.init(homeDir);
        }
        if(logName!=null) {
            //infinite
        }
   }
}

Автор: Stampede 19.6.2007, 18:55
diablero, c почином! smile

Для начала в общем неплохо. Замечания такие.

1. Название пакета. Принятая в Java схема именования пакетов помимо всего прочего служит цели по возможности разнести пространства имен разных производителей, дабы минимизировать риск конфликта имен. Для этого предлагается начинать название пакета с префикса, который хоть как-то гарантирует уникальность - с домена конторы, записанного задом наперед. То есть если у проекта Апачи есть домен apache.org, то пакеты Java, разрабатываемые под его крышей, имеют вид org.apache.*.

Ты сейчас выбрал имя ru.infinite. Но домен infinite.ru уже существует, и если только это не твой домен (в чем я сильно сомневаюсь), то в будущем это может быть чревато ненужными осложнениями. Идеологически более правильно было бы пойти сейчас на сайт любого регистратора (например, http://www.webnames.ru/) и если не зарегистрировать, то хотя бы подобрать себе свободный, незанятий домен.

2. Название проекта. Ты пока что просто подобрал название, доставшееся от nerezus'а, Hunger. Вряд ли это имя отражает суть твоего начинания. Между тем название проекта - это исключительно важный момент. "Как вы яхту назовете, так на ней и поплывете" (с) smile Поэтому предлагается начать именно с поиска хорошего названия. Потом менять на ходу будет достаточно трудно, да и душой уже прикипишь.

У меня еще есть замечания по коду, но давай мы сначала решим эти два вопроса.

Автор: diablero 19.6.2007, 22:28
infinite.ru был свободен.

Исправил...

Автор: Stampede 20.6.2007, 00:04
Цитата(diablero @  19.6.2007,  13:28 Найти цитируемый пост)
infinite.ru был свободен.


Смотрим:

Цитата
domain:     INFINITE.RU
type:       CORPORATE
...
registrar:  RUCENTER-REG-RIPN
created:    2005.11.22
paid-till:  2007.11.22
source:     TC-RIPN


Но да ладно. Значит, говоришь, ru.selfexpression? Ну что ж, нормально. А насчет Tools точно уверен? Больше походит на название класса-утилиты... Впрочем, хозяин - барин.

Теперь следующее. Я бы хотел, чтобы ты создал папку, которая будет домашней директорией всей проги, а в ней такую структуру:
  • tools // или tools-test, или как-нибудь еще - это и будет домашняя директория
    • conf
    • log
Для начала хватит. Это нужно для того, чтобы файлы, которых со временем будет все больше, не валялись вперемешку.

Далее, в командной строке запуска проги предусмотри два необязательных аргумента: (1) путь к домашней директории и (2) имя файла конфига. Дефолтным значением для первого пусть будет текущая (рабочая) директория, а для второго - какое-нибудь фиксированное имя, например, tools.properties.

Содержимое log4j.properties придется подправить, чтобы он открывал лог-файл относительно домашней (а не текущей!) директории. По счастью, log4j понимает переменные окружения, так что это можно указать таким образом:

Код

log4j.appender.File.file=${tools.home}/log/application.log


Соответственно, переменную tools.home нужно будет выставить в коде (через System.setProperty()) до вызова PropertyConfigurator.configure(fileName). То есть не в конструкторе, а в методе init(), потому что конструктору синглтона мы никакие параметры передать не сможем. Сам файл log4j.properties удобно будет засунуть в директорию conf, чтобы все конфиги были у нас в одном месте. Разумеется, при этом надо будет не забыть скорректировать логику формирования полного пути к этому файлу относительно домашней директории.

Теперь по коду:

1. Конструктор класса Configuration пусть принимает имя файла конфига. Хорошо также отображать это имя в выводе описания конфига. Что-то вроде:

Цитата
Configuration: file C:\tools\conf\tools.properties
param1=value1
param2=value2
...



2. В самом конце метода init() надо будет дернуть метод Configuration.toString() и вывести полученную строку в лог. Так у нас будет сохраняться полная история, что, когда и с какими параметрами мы запускали.

3. Класс Tools не должен никому давать свой логгер! Я уже писал об этом. Log4j позволяет как угодно тонко настраивать уровни и форматы вывода, но вся эта логика основана на иерархии пакетов, и если мы предоставим себе возможность пользоваться одним общим логгером на всех, то сами спрвоцируем себя на "насилование" (abuse) полезного инструмента.

4. В классе test (кстати, в Java названия классов принято писать с большой буквы) нужно проанализировать аргументы командной строки, получить экземпляр синглтона, и запустить его инициализацию. Больше там ничего не нужно. Обо все остальном должен позаботиться главный клас приложения.

Вот так вот пока.

Автор: diablero 20.6.2007, 10:12
Отредактировал свой пост выше, в котором я код выкладывал. Потому, что есть парочку вопросов.

1. Чего - то я не пойму как быть с логером. В приватном конструкторе я его инициализирую относительно папки проета. 
    А как быть при вызове метода init() ? Логер же нужно перенастроить. 

2. 
Цитата

3. Класс Tools не должен никому давать свой логгер! Я уже писал об этом. Log4j позволяет как угодно тонко настраивать уровни и форматы вывода, но вся эта логика основана на иерархии пакетов, и если мы предоставим себе возможность пользоваться одним общим логгером на всех, то сами спрвоцируем себя на "насилование" (abuse) полезного инструмента.


Я чего-то не могу понять как будет происходить логирование.  Если не трудно покажи на коде.

3. Как задать имя лог файла? Через переменные окружения или там есть какой-то метод?

Автор: Stampede 20.6.2007, 19:30
Хорошо, смотри:

Код

package ru.selfexpression;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import java.io.File;

public class Infinite {
    private static Infinite instance = new Infinite();
    private Configuration config;
    private Logger logger;
    private String homeDir;

    private Infinite() {
    }

    public static Infinite getInstance() {
       return instance;
    }

    public void  init(String homeDir) {
        this.homeDir = homeDir;
        System.setProperty("infinite.home", homeDir);

        PropertyConfigurator.configure(homeDir+"/conf/log4j.properties");
        logger = Logger.getLogger(Infinite.class);

        config = new Configuration(homeDir+"/conf/infinite.properties");
        logger.info(config);
    }

    public Configuration getConfiguration() {
        return config;
    }

    public void dispose() {
    }
}


Код

package ru.selfexpression.test;

import ru.selfexpression.Infinite;

class Test {
    public static void main(String args[]) {
        String homeDir = (args.length > 0)? args[0] : System.getProperty("user.dir");
        Infinite.getInstance().init(homeDir);
   }
}


Теперь комментирую. Обрати внимание, что код стал намного короче. Это особенно важно для тестового класса. Если тесты делать длинными, то писать их будет в лом. А надо, чтобы было в радость smile Я с этой целью маленько упростил задачу: пускай имя конфиг файла будет зашито в коде. Ничего страшного: потом, если понадобится, добавим еще параметр. Зато, как ты можешь видеть, сам по себе запуск проги из тестового класса пишется в две строчки.

Теперь по главномк классу. Я ведь тебе намекал, что в конструкторе ничего особо полезного сделать не получится, поскольку мы еще не знаем путь к домашней директории. Ну так и незачем тогда вообще его засорять - оставим его пустым. Сам по себе приватный дефолтный конструктор нам нужен, чтобы мы случайно (или кто-то другой по злому умыслу) не унаследовали от главного класса.

Я также убрал лишние проверки и выбрасывания исключений. В том виде, в котором они у тебя были, от них пользы все равно никакой. Когда у нас наберется побольше кода, тогда и обсудим стратегию обработки ошибок, лады?

Еще один важный момент в реализации синглтона: создавать инстанс надо прямо при объявлении статической переменной. Тогда он гарантированно создастся до первого обращения к методу getInstance(), что избавит нас от необходимости делать проверки и объявлять его синхронизированным. Это, в общем, азбука паттерна Синглтон.

Ну вот, а теперь ты расскажи, что у тебя получается, и какие пока ощущения от работы над проектом. Да, укажи, какой IDE пользуешься.

До связи.

Автор: diablero 20.6.2007, 21:14
Цитата

 Когда у нас наберется побольше кода, тогда и обсудим стратегию обработки ошибок, лады?

Договорились. 
Цитата

Ну вот, а теперь ты расскажи, что у тебя получается, и какие пока ощущения от работы над проектом.

Пока как все будет мне не ясно. Но уже из того что есть я извлек много пользы для себя. Одно дело изучать самому, другое, под руководством знающего человека. Ощущения только положительные, интерес не только не пропал, но и обострилсяsmile
Жду заданий...
Цитата

 Да, укажи, какой IDE пользуешься.

IntelliJ IDEA 5.0

Автор: Stampede 20.6.2007, 23:32
Ага, отлично.

Давай теперь сделаем загрузку конфигурации из файла. Нужно также завести несколько переменных в конфиге и определить для них геттеры. Переменные такие:
  • String fileEncoding
  • String encryptionKey
  • int maxConnections

Загрузку лучше вынести в отдельную функцию load(). Обрати внимание, что ключи для пропертей принято писать с маленькой буквы, разделяя слова точкой. Строки ключей можно определить как константы, хотя в общем не обязательно.

Каждую переменную инициализируй дефолтным значением. Это позволит минимизировать объем конфигурирования. Метод toString() пускай выводит реальные данные конфига (непосредственно из переменных).

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

Автор: diablero 21.6.2007, 17:44
Цитата

Каждую переменную инициализируй дефолтным значением.

maxConnections = 90. Потому что посмотрев доки mySQL, обнаружил эту цифру, как максимально число подключений.
Исходя из своего опыта, скажи, какие должны быть значения по умолчанию?

Код

package ru.selfexpression;

import java.util.Properties;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Configuration {
    private String confFileName;
    private Properties properties;

    private String fileEncoding = "Windows-1251";
    private String encryptionKey = "Infinite";
    private int maxConnections = 90;

    public Configuration(String confFileName) {
        this.confFileName = confFileName;
        properties = new Properties();
    }
    public void load() {
        try {
            FileInputStream is = new FileInputStream(confFileName);
            properties.load(is);
            is.close();
        } catch (FileNotFoundException e) {

        } catch (IOException e) {

        }
    }
    public String getfileEncoding() {
        return properties.getProperty("file.Encoding");
    }
    public String encryptionKey() {
        return properties.getProperty("encryption.Key");
    }
    public int maxConnections() {
        return Integer.parseInt(properties.getProperty("max.Connections"));
    }
    public String toString() {
        return "Configuration: "+confFileName+"\n"+
               "File encoding: "+fileEncoding+"\n"+
               "Encryption key: "+encryptionKey+"\n"+
               "Max connections: "+maxConnections+"\n";
    }
}



Автор: Stampede 21.6.2007, 18:44
Так-так, минутку... А что же это ты так смело присваиваешь значения переменным?

Код

            fileEncoding = properties.getProperty("file.Encoding");
            encryptionKey = properties.getProperty("encryption.Key");
            maxConnections = Integer.parseInt(properties.getProperty("max.Connections"));


 А если параметр отсутствует в пропертях? Для чего же мы тогда заводили дефолтные значения? Кроме того, если в конфиге будет пропущен параметр max.Connections, то прога вообще не запустится, так как вылетит по NullPointerException. Так что тут-то как раз проверки нужны.

И по поводу исключений. Вот мы наконец и столкнулись с ситуацией, когда игнорировать их нельзя, поскольку они чекнутые (checked). Совсем-то их молчком проглатывать, как у тебя - это тоже не дело. Давай пока сделаем так: в тех местах, где может выбрасываться чекнутое исключение, будем оборачивать его в RuntimeException и пробрасывать наверх:

Код

try {
    // some IO code here
} catch (IOException ioe {
    throw new RuntimeException(ioe);
}


В общем, надо исправить. Когда сделаешь - потести с пропущенными параметрами конфига, вплоть до полностью пустого файла. Убедись, что в этом случае подхватываются дефолтные значения.

Да, и по поводу параметра maxConnections. Если честно, совершенно пофиг, чему он будет равен. Мы все равно будем использовать пул соединений. Я просто хотел, чтобы у нас был какой-нибудь числовой параметр - чтоб служба медом не казалась smile

Автор: diablero 21.6.2007, 22:36
Подправил.
Код

package ru.selfexpression;

import java.util.Properties;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Configuration {
    private String confFileName;
    private Properties properties;

    private String fileEncoding = "Windows-1251";
    private String encryptionKey = "Infinite";
    private int maxConnections = 90;

    public Configuration(String confFileName) {
        this.confFileName = confFileName;
        properties = new Properties();
    }
    public void load() {
        try {
            FileInputStream fis = new FileInputStream(confFileName);
            properties.load(fis);
            fis.close();
        } catch (FileNotFoundException err) {
            throw new RuntimeException(err);
        } catch (IOException err) {
            throw new RuntimeException(err);
        }
    }
    public String getfileEncoding() {
        return properties.getProperty("file.Encoding", fileEncoding);
    }
    public String getEncryptionKey() {
        return properties.getProperty("encryption.Key", encryptionKey);
    }
    public int getMaxConnections() {
        try {
            return Integer.parseInt(properties.getProperty("max.Connections"));
        } catch(NumberFormatException err) {
            return maxConnections;
        }
    }
    public String toString() {
        return "Configuration: "+confFileName+"\n"+
               "File encoding: "+getfileEncoding()+"\n"+
               "Encryption key: "+getEncryptionKey()+"\n"+
               "Max connections: "+getMaxConnections()+"\n";
    }
}

Автор: Stampede 21.6.2007, 23:55
Вообще-то я немного не такой подход имел в виду. Я хотел бы, чтобы все параметры конфига фиксировались именно в переменных, а не вытаскивались "на лету" из пропертей. Одна из причин заключается в том, что нам может со временем понадобиться перейти на другой формат конфигурации, например XML. А коль скоро у нас доступ к параметрам уже "зашит" в коде геттеров, то придется все менять.

То есть еще раз: идеологически правильнее сделать так, чтобы класс конфигурации мало зависел от формата хранения. Для этого формато-зависимый код надо как можно сильнее локализовать и изолировать. У нас для этого есть функция load(). Тогда с переходом на другой формат мы просто перепишем этот метод - и всех делов. Если сильно захочется, можем даже оформить в виде иерархии классов. Но это в данном случае будет, пожалуй, overkill.

ЗЫ: Еще раз напоминаю: не надо редактировать код в ранее написанных постах. Люди ведь не поймут, о чем мы тут говорим.

Автор: diablero 22.6.2007, 00:14
Подправил. Часть 2
Код

package ru.selfexpression;

import java.util.Properties;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Configuration {
    private String confFileName;
    private Properties properties;

    private String fileEncoding = "Windows-1251";
    private String encryptionKey = "Infinite";
    private int maxConnections = 90;

    public Configuration(String confFileName) {
        this.confFileName = confFileName;
        properties = new Properties();
    }
    public void load() {
        try {
            FileInputStream fis = new FileInputStream(confFileName);
            properties.load(fis);
            fis.close();
        } catch (FileNotFoundException err) {
            throw new RuntimeException(err);
        } catch (IOException err) {
            throw new RuntimeException(err);
        }
        fileEncoding = properties.getProperty("file.Encoding", fileEncoding);
        encryptionKey = properties.getProperty("encryption.Key", encryptionKey);

        try {
            maxConnections = Integer.parseInt(properties.getProperty("max.Connections"));
        } catch(NumberFormatException err) {}
    }
    public String getfileEncoding() {
        return fileEncoding;
    }
    public String getEncryptionKey() {
        return encryptionKey;
    }
    public int getMaxConnections() {
        return maxConnections;
    }
    public String toString() {
        return "Configuration: "+confFileName+"\n"+
               "File encoding: "+getfileEncoding()+"\n"+
               "Encryption key: "+getEncryptionKey()+"\n"+
               "Max connections: "+getMaxConnections()+"\n";
    }
}


Автор: Stampede 22.6.2007, 00:45
Ага, теперь такой момент: зачем нам классовая переменная ptoperties? Смотри, ты объявляешь ее в одном месте, создаешь для нее объект в другом, а используешь - в третьем. У Блоха на этот счет есть даже отдельное правило:

Цитата
Item 29: Minimize the scope of local variables

By minimizing the scope of local variables, you increase the readability and maintainability of your code and reduce the likelihood of error.

... 

The most powerful technique for minimizing the scope of a local variable is to declare it where it is first used.  If a variable is declared before it is used, it is just clutter—one more thing to distract the reader who is trying to figure out what the program does. By the time the variable is used, the reader might not remember the variable's type or initial value. If the program evolves and the variable is no longer used, it is easy to forget to remove the declaration if it's far removed from the point of first use.


А Блох - это товарищ, который знает, о чем говорит smile

Когда ты сделаешь переменную properties локальной для блока try, ты перестанешь ее видеть в том месте, где у тебя извлекаются значения параметров. Пофиксить можно переносом этих строк вовнутрь блока try.

И еще по поводу параметра max.Connections. У тебя сейчас вот так:

Код

        try {
            maxConnections = Integer.parseInt(properties.getProperty("max.Connections"));
        } 


Но тут по-хорошему надо бы различать два случая - когда параметр просто отсутствует, и когда он не является валидным целым. В первом случае надо просто присвоить дефолтное значение, а во втором - это на усмотрение. Я бы так и не ловил его вовсе, благо оно не чекнутое, или ловил и оборачивал в другой RuntimeException, с подробным указанием причины.

Автор: diablero 22.6.2007, 01:07
Код

    public void load() {
        try {
            Properties properties = new Properties();
            FileInputStream fis = new FileInputStream(confFileName);
            properties.load(fis);
            fis.close();

            fileEncoding = properties.getProperty("file.Encoding", fileEncoding);
            encryptionKey = properties.getProperty("encryption.Key", encryptionKey);

            try {
                maxConnections = Integer.parseInt(properties.getProperty("max.Connections"));
            } catch(NumberFormatException err) {
                throw new RuntimeException("Parameter \"maxConnections\" has a wrong format");    
            }
        } catch (FileNotFoundException err) {
            throw new RuntimeException(err);
        } catch (IOException err) {
            throw new RuntimeException(err);
        }
    }

Давай оставим пока так. А позже уже определимся куда и что будем делать со всеми исключениями.

Автор: diablero 22.6.2007, 01:25
Цитата(Stampede @  22.6.2007,  00:45 Найти цитируемый пост)
Но тут по-хорошему надо бы различать два случая - когда параметр просто отсутствует, и когда он не является валидным целым.

Понял всю глубину. Такая задержка в понимание из-за столь позднего времени. smile
Уже скоро на работу, посплю чуток. Днем подправлю.
Может еще одно задание?smile

Автор: Stampede 22.6.2007, 01:30
Цитата(diablero @  21.6.2007,  16:25 Найти цитируемый пост)
Может еще одно задание?smile 


Нет, нам нужно сначала подвести важную черту. Зафиксировать, тыкскыть, достигнутое. Так что до завтра.

Автор: batigoal 22.6.2007, 07:22
Цитата(Stampede @  22.6.2007,  00:55 Найти цитируемый пост)
Вообще-то я немного не такой подход имел в виду. Я хотел бы, чтобы все параметры конфига фиксировались именно в переменных, а не вытаскивались "на лету" из пропертей. Одна из причин заключается в том, что нам может со временем понадобиться перейти на другой формат конфигурации, например XML. А коль скоро у нас доступ к параметрам уже "зашит" в коде геттеров, то придется все менять.

Я обычно решаю эту проблему так: создаю класс Config, а в нем - внутренний класс ConfigReader. В случае изменения формата хранения - будет правиться ConfigReader, который никем, кроме самого Config не используется --> хорошо локализован.

Автор: Stampede 22.6.2007, 09:32
Цитата(batigoal @  21.6.2007,  22:22 Найти цитируемый пост)
Я обычно решаю эту проблему так: создаю класс Config, а в нем - внутренний класс ConfigReader. В случае изменения формата хранения - будет правиться ConfigReader, который никем, кроме самого Config не используется --> хорошо локализован.


Вот, обрати внимание, diablero - batigoal приводит очень хороший пример разбиения компонентов по границе функциональной ответственности, причем имено в технике ООП, специфической для Java - с использованием внутрених классов. Это и есть то, что называется "слабо связанными" (loosely coupled) компонентами.

Тут уже нетрудно увидеть, как можно развить этот подход таким образом, чтобы вообще ничего не править, а просто по мере надобности использовать тот или иной ридер конфига. Например, завести отдельно:
  • PropertiesConfigReader
  • XMLConfigReader
И сделать к ним этакую мини-фабричку, хотя бы просто в форме отдельного метода или куска кода:

Код

protected ConfigReader getConfigReader(String fileName) {
    ConfigReader reader = null;
    if (fileName.endsWith('.properties')) {
        reader = new PropertiesConfigReader();
    } else if (fileName.endsWith(".xml")) {
       reader = new XMLConfigReader();
    } else {
        throw new InvalidArgumentException("Unsupported config file format: " + fileName);
    }

    return reader;
}


Но тут я еще раз напоминаю, что мы не пытаемся написать компонент общего назначения, и раньше времени вводить излишнюю гибкость нам ни к чему. В английском языке есть хорошая поговорка: "we'll cross that bridge when we come to it" (когда дойдем до моста, тогда и будем по нему переходить). Она хорошо передает суть "шустрого программирования"(agile programming) - начинай с самой простой реализации, которая только будет работать. Важно только, чтобы в этой реализации было как можно меньше "закладок", которые впоследствие затруднят нам функциональное дробление компонент, как это имело место с твоей ранней версией получения параметров конфига.

А вообще в борьбе внутреннего Шустрого Программиста и ОО Архитектора внутри себя, программист должен доминировать безоговорочно, и если что - не стесняясь бить архитектора по рукам, чтоб не терял чувства реальности smile

batigoal, спасибо за удачное развитие темы.

Автор: diablero 22.6.2007, 13:10
мини-фабричка
Код

package ru.selfexpression;

public class Configuration {
    private ConfigReader reader;
    private String confFileName;

    public Configuration(String confFileName) {
        this.confFileName = confFileName;
    }
    protected ConfigReader getConfigReader(String confFileName) {
        this.confFileName = confFileName;
        reader = null;
        if (confFileName.endsWith(".properties")) {
            reader = new PropertiesConfigReader(confFileName);
        } else if (confFileName.endsWith(".xml")) {
            reader = new XMLConfigReader(confFileName);
        } else {
            throw new IllegalArgumentException("Unsupported config file format: " + confFileName);
        }
        return reader;
    }
    public void load() {
        getConfigReader(confFileName).load();
    }
    public String getfileEncoding() {
        return reader.getfileEncoding();
    }
    public String getEncryptionKey() {
        return reader.getEncryptionKey();
    }
    public int getMaxConnections() {
        return reader.getMaxConnections();
    }
    public String toString() {
        return reader.toString();
    }
}

Код

package ru.selfexpression;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;

public class PropertiesConfigReader extends ConfigReader {
    private String fileName;
    private String fileEncoding, encryptionKey;
    private int maxConnections;
    public PropertiesConfigReader(String fileName) {
        this.fileName = fileName;
    }
    public void load() {
        try {
            Properties properties = new Properties();
            FileInputStream fis = new FileInputStream(fileName);
            properties.load(fis);
            fis.close();

            fileEncoding = properties.getProperty("file.Encoding", defaultFileEncoding);
            encryptionKey = properties.getProperty("encryption.Key", defaultEncryptionKey);
            try {
                maxConnections = Integer.parseInt(properties.getProperty("max.Connections", String.valueOf(defaultMaxConnections)));
            } catch (NumberFormatException err) {
                throw new RuntimeException("Parameter \"maxConnections\" has a wrong format");
            }

        } catch (FileNotFoundException err) {
            throw new RuntimeException(err);
        } catch (IOException err) {
            throw new RuntimeException(err);
        }
    }
    public String getfileEncoding() {
        return fileEncoding;
    }
    public String getEncryptionKey() {
        return encryptionKey;
    }

    public int getMaxConnections() {
        return maxConnections;
    }
    public String toString() {
        return "Configuration: "+fileName+"\n"+
               "File encoding: "+getfileEncoding()+"\n"+
               "Encryption key: "+getEncryptionKey()+"\n"+
               "Max connections: "+getMaxConnections()+"\n";
    }
}

Код

package ru.selfexpression;

abstract class ConfigReader {

    public static final String defaultFileEncoding = "Windows-1251";
    public static final String defaultEncryptionKey = "Infinite";
    public static final int defaultMaxConnections = 190;

    abstract public void load();
    abstract public String getfileEncoding();
    abstract public String getEncryptionKey();
    abstract public int getMaxConnections();
}

Автор: Stampede 22.6.2007, 13:40
НЕТ, НЕТ, НЕПРАВИЛЬНО!

Я же говорю, НЕ НАДО раньше времени ваять лишний ООП! Пожалуйста, верни все как было, у тебя же уже все было почти хорошо, за исключением чтения maxConnections.

А если уж говорить о разбивке на классы... Зачем ты перенес переменные в ридеры? Переменные конфигурации должны быть именно в классе конфигурации.

Поясняю еще раз. Вот есть класс конфигурации. Он знает (и может выдавать) только те переменные, которые мы в нем пропишем. Как эти переменные инициализируется, откуда берутся значения для них - его не колышет абсолютно, для этого есть вспомогательные классы-ридеры. И ридеры не дублируют переменные конфига - они работают непосредственно с приватными полями класса Configuration. Поэтому-то batigoal и подчеркивал, что классы объявлены как внутренние - чтобы у них был доступ к этим полям.

Но я еще раз повторяю, не надо нам этого пока. Так что вертай все взад.

Автор: diablero 22.6.2007, 13:55
Вернул как было, и исправил ситуацию с maxConnections
Код

package ru.selfexpression;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;

public class Configuration {
    private String confFileName;
    private Properties properties;
    private String fileEncoding = "Windows-1251";
    private String encryptionKey = "Infinite";
    private int maxConnections = 90;

    public Configuration(String confFileName) {
        this.confFileName = confFileName;
        properties = new Properties();
    }
    public void load() {
        try {
            FileInputStream fis = new FileInputStream(confFileName);
            properties.load(fis);
            fis.close();
        } catch (FileNotFoundException err) {
            throw new RuntimeException(err);
        } catch (IOException err) {
            throw new RuntimeException(err);
        }
        fileEncoding = properties.getProperty("file.Encoding", fileEncoding);
        encryptionKey = properties.getProperty("encryption.Key", encryptionKey);
        try {
            maxConnections = Integer.parseInt(properties.getProperty("max.Connections", String.valueOf(maxConnections)));
        } catch (NumberFormatException err) {
            throw new RuntimeException("Parameter \"maxConnections\" has a wrong format");
        }
    }
    public String getfileEncoding() {
        return fileEncoding;
    }
    public String getEncryptionKey() {
        return encryptionKey;
    }
    public int getMaxConnections() {
        return maxConnections;
    }
    public String toString() {
        return "Configuration: " + confFileName + "\n" +
                "File encoding: " + getfileEncoding() + "\n" +
                "Encryption key: " + getEncryptionKey() + "\n" +
                "Max connections: " + getMaxConnections() + "\n";
    }
}
 

Автор: Stampede 22.6.2007, 14:11
И опять неправильно. Правильно (почти) было в том вариенте, который ты запостил перед тем как пойти спать. Вот этот вот:

Код

    public void load() {
        try {
            Properties properties = new Properties();
            FileInputStream fis = new FileInputStream(confFileName);
            properties.load(fis);
            fis.close();
            fileEncoding = properties.getProperty("file.Encoding", fileEncoding);
            encryptionKey = properties.getProperty("encryption.Key", encryptionKey);
            try {
                maxConnections = Integer.parseInt(properties.getProperty("max.Connections"));
            } catch(NumberFormatException err) {
                throw new RuntimeException("Parameter \"maxConnections\" has a wrong format");    
            }
        } catch (FileNotFoundException err) {
            throw new RuntimeException(err);
        } catch (IOException err) {
            throw new RuntimeException(err);
        }
    }


Там все, что нужно доделать - это проверять на null значение properties.getProperty("max.Connections"), и обрабатывать отдельно. Ты же говоришь, что понял этот момент.

diablero, может ты не выспался? Я серьезно.

Добавлено через 3 минуты и 59 секунд
diablero, извини, не заметил вот это: String.valueOf(maxConnections).

Значит, осталась только классовая переменная properties.

Автор: diablero 22.6.2007, 16:12
Цитата(Stampede @  22.6.2007,  14:11 Найти цитируемый пост)
Значит, осталась только классовая переменная properties.

Блин, не с того поста вертал все взадsmile. Это уже я давно исправил. Даже пост уже такой есть.

Автор: Stampede 22.6.2007, 18:13
Хорошо.

Теперь нам нужно сделать важную вещь - подвести жЫрную черту под тем, что мы на данный момент наваяли. Я попрошу тебя сбросить файлы проекта в zip-архив и залить в аттачмент.

Предварительно убедись, что все нужные файлы в наличии, все компилируется и работает. В названии zip файла предусмотри номер версии.

Ждем-с.

Автор: diablero 22.6.2007, 19:00
Готово.

Автор: Stampede 22.6.2007, 20:58
Ага, посмотрел.

В целом нормально, пойдет. Только ты недовложил зависимые библиотеки (log4j). Еще замечание по методологии тестирования: у тебя в infinite.properties прописано max.Connections=90. А как же ты тогда проверял, что оно перебивает дефолтное значение, если и там и там 90? Правильнее использовать разные. Кроме того, нет такой кодировки - KOI-8, есть KOI8-R.

Но это в общем детали. Самое главное: мы заложили фундамент будущей проги!

То, что у нас при этом получилось - уже хорошая основа для дальнейшего наращивания функциональности. У нас имеется:
  • Понятие домашней директории, задаваемой при запуске.
  • Главный класс проги, который будет служить точкой входа во все приложение.
  • Настроенный в первом приближении логгер.
  • Класс конфигурации, в который теперь уже легко добавлять новые параметры.

Да, это заняло какое-то время. Да, не все получалось сразу. Но оно стоило того. Я думаю, ты и сам это заметил.

Прежде чам мы приступим к следующему этапу, я хотел бы услышать твою ответную реакцию: как в целом оцениваешь опыт, что считаешь приобретением для себя, в чем были наибольшие трудности, какие есть пожелания, и т. д.

После этого продолжим, а пока - поздравляю! user posted image

smile

Автор: diablero 23.6.2007, 07:58
Чувствуется нехватка опыта проетирование и необходимость структирировать, изучить и углубить теоритические знание. Я и раньше писал нечто подобное, теперь осознаю, что это было кривавато.

Жду заданий. 

Автор: Stampede 23.6.2007, 23:46
Итак, следующий этап. Нам предстоит создать программную модель предметной области. Но не сразу.

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

Поехали.

Автор: diablero 24.6.2007, 02:10
Это должен быть тематический сайт-форум. 
Авторизированные пользователи, отправляют запрос на размещение своей статьи,  по определенной тематике, после проверки, она размещается на сайте. Что-то вроде персональных страниц, только загнанные в стальные рамки тематикой сайта.  Каждый читатель сайта может проголосовать за статью, поставить оценку, на основании которых выставляется рейтинг. И в зависимости от количества статей и их рейтинга пользователь присуждается группа. Каждый зарегистрированный пользователь имеет учетную запись - профиль пользователя (персональные данные, подписи, аватар, настройки), размещенные статьи, личный ящик, статистика.     

В связи с этим должны быть, на мой взгляд, следующее:
  • Авторизация   
  • Голосования
  • Статистика
  • Новости 
  • Популярные материалы
  • Поиск


Автор: Stampede 24.6.2007, 15:19
В общем примерно понятно. Попробуй теперь, исходя из своего видения будущего вебсайта, набросать перечень бизнес-сущностей. Для каждой сущности укажи набор значимых свойств.

Да, подсистему статистики пока не трогай, это вешь достаточно перпендикулярная всему остальному, и при этом весьма трудоемкая. Придет время - сделаем отдельно.

Автор: diablero 24.6.2007, 17:23
Цитата(Stampede @  24.6.2007,  15:19 Найти цитируемый пост)
В общем примерно понятно. Попробуй теперь, исходя из своего видения будущего вебсайта, набросать перечень бизнес-сущностей. Для каждой сущности укажи набор значимых свойств.

А простым языком скажи, что от меня надо?smile

Автор: Stampede 24.6.2007, 17:30
Цитата(diablero @  24.6.2007,  08:23 Найти цитируемый пост)
А простым языком скажи, что от меня надо?smile


Я думал все и так прозрачно smile Ну хорошо, например что-то вроде такого:
  • User
    • id
    • name
    • password
    • email
    • group
    • ...
  • Post
    • id
    • title
    • text
    • ...

...

Автор: diablero 24.6.2007, 17:34
Понял, как с работы приду, постараюсь все это написать

Автор: diablero 24.6.2007, 23:17
User:
  • id
  • name
  • password
  • group
  • avatars
  • birthday
  • gender
  • city
  • country
  • signature
  • e-mail
  • icq
  • posts
  • date registration
Post:
  • id
  • status
  • user
  • section
  • date
  • title
  • text
  • viewings
  • rating
News:
  • Id
  • section
  • date
  • title
  • text
  • rating
Personal box
  • id
  • inbox
  • outbox
  • sent message
  • trash
Message
  • id
  • status
  • to
  • from
  • date
  • subject
  • text
Здесь есть еще над чем подумать, но я думаю в ходе разработки добавить не трудно будет.

Автор: v2v 25.6.2007, 10:01
Stampede,  какая чудесная инициатива.
Я бы тоже с удовольствием поучаствовал в таком проектике, но за не хваткой времени не смогу вовремя присылать ответы на задания.
Поэтому, Stampede , не мог ли ты направить меня на путь истинный, если основной моей целью является знакомство и изучение JEE.
Что читать? Какие технологии изучать? 

Автор: Stampede 25.6.2007, 16:16
v2v, я не так давно отвечал практически на такой вопрос fixxer'у:

Цитата(Stampede @  24.5.2007,  09:38 Найти цитируемый пост)
А, понятно. Боюсь, тут я помочь не смогу. Это тебе надо сюда: http://java.sun.com/javaee/5/docs/tutorial/doc/. Там как раз все грамотно и про EE.


To diablero:

Отлично, давай теперь запишем это в виде Java классов. Начнем с сущностей User и Post.

В каждом классе должен быть дефолтный конструктор, приватные переменные и публичные геттеры/сеттеры.

Автор: v2v 25.6.2007, 17:54
Stampede, я видел этот ответ и смотрел туториал  - там много, а читать всё подряд потребует очень не мало вреемени. ((
Может быть вы как то обобщите процесс обучения. Тут вы выбрали конкретного человека и пытаетесь с ним реализовать его проект.
Это конечно хорошо, и уверен этот человек очень классно разберётся.
Но было бы не плохо если бы вы не внедрялись в потробности анализа кода и исправления ошибок, а создали шаблон, по которому можно было бы всем пробовать свои возможности.
например : 
1. проектриврание системы. Определить чего хотите ... если выберет то , то  надо будет делать то , а если то, то то и т.д.... 
2. выбор средства разработки .. есть такие то... такие то могут то то, такие то я рекомендую..
3. реализация ... юзайте те то технологии создайте такие то основные файлы без которых жить не сможете, распихать по таким то пакетам..
4. проектирование бд, где будет то то и то ... 
5. и т.д....

Автор: Stampede 25.6.2007, 18:25
v2v, я не позиционирую себя как опытного JEE разработчика в том понимании, которое вы вкладываете в это слово. Поэтому ваши вопросы - это не ко мне. Попробуйте создать отдельную тему, или посмотрите вот этот только что открытый топик: http://forum.vingrad.ru/forum/topic-160671/kw-%D1%81%D1%82%D0%B0%D0%B6%D0%B5%D1%80-2ee-%D0%B7%D0%BD%D0%B0%D0%BD%D0%B8%D1%8F-%D0%BA%D0%BE%D1%80%D0%BF%D0%BE%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5.html.

Автор: diablero 26.6.2007, 18:26
User
Код

package ru.selfexpression;

import javax.swing.*;
import java.util.Date;

public class User {
    private int id;
    private String name;
    private byte []password;
    private String group;
    private Icon avatars;
    private Date birthday;
    private String gender;
    private String city;
    private String country;
    private String signature;
    private String mail;
    private String icq;
    private int posts;
    private Date dateRegistration;

    public static final int GENDER_MALE = 0;
    public static final int GENDER_FEMALE = 1;

    public User() {

    }
    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setPassword(byte password[]) {
        this.password = password;
    }
    public byte[] getPassword() {
        return password;
    }
    public void setGroup(String group) {
        this.group = group;
    }
    public String getGroup() {
        return group;
    }
    public void setAvatars(Icon avatars) {
        this.avatars = avatars;
    }
    public Icon getAvatars() {
        return avatars;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setGender(int gender) {
        if(gender == GENDER_MALE) {
            this.gender = "male";
        }
        if(gender == GENDER_FEMALE) {
            this.gender = "female";
        }
    }
    public String getGender() {
        return gender;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public String getCity() {
        return city;
    }
    public void setCountry(String country) {
        this.country = country;
    }
    public String getCountry() {
        return country;
    }
    public void setSignature(String signature) {
        this.signature = signature;
    }
    public String getSignature() {
        return signature;
    }
    public void setMail(String mail) {
        this.mail = mail;
    }
    public String getMail() {
        return mail;
    }
    public void setICQ(String icq) {
        this.icq = icq;
    }
    public String getICQ() {
        return icq;
    }
    public void setPosts(int posts) {
        this.posts = posts;
    }
    public int getPosts() {
        return posts;
    }
    public void setDateRegistration(Date dateRegistration) {
        this.dateRegistration = dateRegistration;
    }
    public Date getDateRegistration() {
        return dateRegistration;
    }
}

Post
Код

package ru.selfexpression;

import java.util.Date;

public class Post {
    private int id;
    private int status;
    private String user;
    private int section;
    private Date date;
    private String title;
    private String text;
    private int viewings;
    private float rating;

    public static final int AUTHORIZED = 0;
    public static final int NOT_AUTHORIZED = 1;

    public Post() {

    }
    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }
    public void setStatus(int status) {
        this.status = status;
    }
    public int getStatus() {
        return status;
    }
    public void setUser(String user) {
        this.user = user;
    }
    public String getUser() {
        return user;
    }
    public void setSection(int section) {
        this.section = section;
    }
    public int getSection() {
        return section;
    }
    public void setDate(Date date) {
        this.date = date;
    }
    public Date getDate() {
        return date;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getTitle() {
        return title;
    }
    public void setText(String text) {
        this.text = text;
    }
    public String getText() {
        return text;
    }
    public void setViewings(int viewings) {
        this.viewings = viewings;
    }
    public int getViewings() {
        return viewings;
    }
    public void setRating(float rating) {
        this.rating = rating;
    }
    public float getRating() {
        return rating;
    }
}


Добавлено через 1 минуту и 43 секунды
Stampede мы будем с локализацией заморачиваться?

Автор: Stampede 26.6.2007, 19:11
Так, препарируем. Замечания такие:

1. Зачем хранить пароль в виде байтов? Гораздо удобнее в виде строки - hex или base64.

2. Аватар собираешься прямо в базе хранить, блобом? Подумай, удобно ли? В прниципе это возможно, но поддерживать будет трудно (вставлять/обновлять/просматривать). Я бы все-таки предложил хранить в файловой системе. Тогда поле avatars (кстати, почему во множественном числе) будет строкой, содержащей путь к файлу картинки.

3. По поводу пола. У тебя вот такая конструкция:

Код

    public void setGender(int gender) {
        if(gender == GENDER_MALE) {
            this.gender = "male";
        }
        if(gender == GENDER_FEMALE) {
            this.gender = "female";
        }
    }
 


Есть ли в этом смысл? Практически единственное место, где у тебя будет создаваться объект типа User - это при обработке соответствующей веб-формы. Пол там будет скоее всего задаваться радиокнопкой. Ну и зачем нам связываться с преобразованием строки в число, а потом обратно, если мы можем везде работать со строкой? Я бы сделал так:

Код

    public static final String GENDER_MALE = "male";
    public static final String GENDER_FEMALE = "female";

    public void setGender(String gender) {
        this.gender = gender;
    }


Проверками пока не заморачиваемся, потому что для валидации все равно нужен будет какой-то целостный подход.

4. В классе Post ссылка на автора поста идет по имени юзера. Правильнее было бы по ID. Это как бы азбука проектирования реляционных баз данных.

В остальном вроде нормально. Да, вот еще. В классе Post я заметил у тебя переменную int section, а ты такую сущность не описывал. Так что отредактируй классы с учетом замечаний и приведи список свойств для Section.

После этого займемся непосредственным созданием интересующих нас объектов.

Автор: diablero 26.6.2007, 20:08
section это тематические разделы сайта.  Например, с++ java asm ...
Как и если она нам понадобиться мы ее опишем или уберем.

Код

package ru.selfexpression;

import java.util.Date;

public class User {
    private int id;
    private String name;
    private String password;
    private String group;
    private String avatar;
    private Date birthday;
    private String gender;
    private String city;
    private String country;
    private String signature;
    private String mail;
    private String icq;
    private int posts;
    private Date dateRegistration;

    public static final String GENDER_MALE = "male";
    public static final String GENDER_FEMALE = "female";

    public User() {

    }
    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getPassword() {
        return password;
    }
    public void setGroup(String group) {
        this.group = group;
    }
    public String getGroup() {
        return group;
    }
    public void setAvatarFilePath(String filePath) {
        this.avatar = filePath;
    }
    public String getAvatarFilePath() {
        return avatar;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public String getGender() {
        return gender;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public String getCity() {
        return city;
    }
    public void setCountry(String country) {
        this.country = country;
    }
    public String getCountry() {
        return country;
    }
    public void setSignature(String signature) {
        this.signature = signature;
    }
    public String getSignature() {
        return signature;
    }
    public void setMail(String mail) {
        this.mail = mail;
    }
    public String getMail() {
        return mail;
    }
    public void setICQ(String icq) {
        this.icq = icq;
    }
    public String getICQ() {
        return icq;
    }
    public void setPosts(int posts) {
        this.posts = posts;
    }
    public int getPosts() {
        return posts;
    }
    public void setDateRegistration(Date dateRegistration) {
        this.dateRegistration = dateRegistration;
    }
    public Date getDateRegistration() {
        return dateRegistration;
    }
}


Код

package ru.selfexpression;

import java.util.Date;

public class Post {
    private int id;
    private int status;
    private int userId;
    private int section;
    private Date date;
    private String title;
    private String text;
    private int viewings;
    private float rating;

    public static final int AUTHORIZED = 0;
    public static final int NOT_AUTHORIZED = 1;

    public Post() {

    }
    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }
    public void setStatus(int status) {
        this.status = status;
    }
    public int getStatus() {
        return status;
    }
    public void setUserId(int userId) {
        this.userId = userId;
    }
    public int getUserId() {
        return userId;
    }
    public void setSection(int section) {
        this.section = section;
    }
    public int getSection() {
        return section;
    }
    public void setDate(Date date) {
        this.date = date;
    }
    public Date getDate() {
        return date;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getTitle() {
        return title;
    }
    public void setText(String text) {
        this.text = text;
    }
    public String getText() {
        return text;
    }
    public void setViewings(int viewings) {
        this.viewings = viewings;
    }
    public int getViewings() {
        return viewings;
    }
    public void setRating(float rating) {
        this.rating = rating;
    }
    public float getRating() {
        return rating;
    }
}


Автор: batigoal 26.6.2007, 20:45
Код

    public static final String GENDER_MALE = "male";
    public static final String GENDER_FEMALE = "female";

А почему не enum? Вроде у нас Джава не четвертая...

Автор: Stampede 26.6.2007, 23:00
Ага, годидзе.

Теперь очень важный момент. Нам нужно будет насоздавать экземпляров определенных нами сущностей. В этом месте многие начинающие программисты сразу приступают к программированию датабазного слоя, с тем чтобы далее юзать данные из набитой вручную базы данных. Это не есть хорошо.

При таком подходе вектор зависимости компонентов проги неизбежно отклоняется в сторону датабазной логики. При еще менее удачной архитектуре, когда начинают плясать от вебного слоя, этот вектор тяготеет также в сторону Servlet API, что в сочетании дает совершенно невообразимую мешанину, в которой практически невозможно разобраться.

Поэтому крайне важно смотреть на базу данных лишь как на один из возможных способов персистенции, и соответственно экранировать реализацию низкоуровневого доступа к базе неким более общим интерфейсом, который знать ничего не знает о таких вещах как Statement, ResultSet и SQLException.

Но вернемся к исходному вопросу. Как же все-таки создавать объекты для тестирования? Выход состоит в использовании т. н. mock objects - болванок, имитирующих реальные сущности (mock == имитация). Для их генерации существует несколько подходов:
  • Вручную через форму ввода

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

  • Вручную программно

    То есть пишем тестовый класс, в котором создаем, например, объект типа User, и сеттерами заполняем его поля некими константами. Потом копируем код и редактируем в нем константы, и так до посинения. Понятно, что это путь в никуда.

  • Вручную через файл-описатель

    Примерно то же самое, но описание каждой сущности зашиваем не в коде, а виде текстового описателя, например в формате CSV или XML. Потом этот файл как-то парсится, на основе извлеченных значений создаются объекты. Вариант не самый плохой, но для массовой генерации слишком трудемкий.

  • Посредством динамических моков

    Есть специальные библиотеки, типа jMock, EasyMock и пр., которые создают моки на лету, используя рефлексию. Вещь, конечно, хорошая, но с ними есть одна трудность: они не понимают семантики прикладных данных. А нам надо, чтобы объекты получались более-менее похожими на реальными. Чтоб если текст - то связный, если имя - то не просто набор букв, если дата - то в определенном диапазоне, 

  • Посредством самодельного генератора моков

    Пишется специальная утилита, которая, используя генератор случайных чисел и кастомную логику, создает объекты с осмысленными значениями полей.
diablero, выбор за тобой. Если интересует мое мнение, я бы наверно выбрал через файл-описатель. Тем более что там есть возможность организовать парсинг на халяву, штатными средствами.

To batigoal:
Цитата(batigoal @  26.6.2007,  11:45 Найти цитируемый пост)
А почему не enum? Вроде у нас Джава не четвертая.


Ну, для поля gender с двумя возможными значениями это был бы все-таки overkill. Тем более что ведение enum не решало бы вопрос о преобразовании из строкового значения в enum'ное.

Автор: diablero 26.6.2007, 23:24
Не знал как это называется, но всегда для тестов использовал сгенириный файлик.smile
Так что я склоняюсь к файлу описателю.
Т.е. моя задача в какую-нибудь структуру нагенерить кучу User и Post.

Автор: Stampede 27.6.2007, 00:08
Цитата(diablero @  26.6.2007,  14:24 Найти цитируемый пост)
Т.е. моя задача в какую-нибудь структуру нагенерить кучу User и Post.


Типа того. Тут можно еще сделать финт ушами и реализовать это дело малой кровью. Фокус заключается в том, чтобы задействовать один из имеющихся средств сериализации Java объектов в какой-нибудь текстовый формат. Для этого подойдет например класс http://java.sun.com/j2se/1.5.0/docs/api/java/beans/XMLEncoder.html, или библиотека Burlap (вот тут рассказывается, как в две строчки за- и рассериализовать объект: http://www.caucho.com/resin-3.0/protocols/burlap.xtp), или XStream (вот пример сериализации в строку: http://xstream.codehaus.org/tutorial.html), или дюбой другой (погугли по java xml serialization). Я бы от себя порекомендовал XStream - очень простой, четкий и внятный.

Сделать надо вот что. Пишешь новый тестовый класс, скажем TestUser, и в нем (можно прямо в main()) создаешь экземпляр User, заполняешь поля значениями, и печатаешь его XML представление. Да, предварительно засовываешь объект в какую-нибудь коллекцию, чтобы потом загружать сразу пачкой. Примерно вот так:

Код

User user = new User();
user.setId(123);
user.setName("joe");
// ...
ArrayList list = new ArrayList();
list.add(user);

XStream xstream = new XStream();
String xml = xstream.toXml(list);

System.out.println(xml);


После этого копируешь вывод в буфер, заносишь в файл, скажем, conf/users.xml, и, творчески применяя излюбленный метод китайских программистов под названием copy-paste, создаешь столько юзеров, сколько тебе надо.

Фсе! Телемаркет! smile

Как потом прочитать - объяснять, я думаю, не надо.

Автор: diablero 27.6.2007, 01:00
Я сделал проще. Взял сворй старый генератор csv файла и создаю сколь угодно User  вида:
Код

0;user_0;password_0;group_0;avatar_0;118273399709;gender_0;city_0;country_0;signature_0;mail_0;icq_0;posts_0;118273399709
1;user_1;password_1;group_1;avatar_1;121436999718;gender_1;city_1;country_1;signature_1;mail_1;icq_1;posts_1;149908399808

Post аналогично.

И не в две, а чуть больше строчек, с помощью StringTokenizer создаю n-е количество User и Post 
Код

class Mock {

    public Vector<User> getUsers(String filePath) {
        Vector<User> data = new Vector<User>();
        ...
        return data;
    }
    public Vector<Post> getPosts(String filePath) {
        Vector<Post> data = new Vector<Post>();
        ...
        return data;
    }
}

Автор: Stampede 28.6.2007, 01:15
Рад, что тебе понравился XStream. Поехали дальше.

Давай теперь напишем два класса-менеджера, которые реализуют простенький интерфейс:

Код

public interface Manager {
    public void init();
    public void destroy();
}


Менеджерами будет заведовать класс Infinite: создавать, инициализировать и выдавать через геттеры.

В самих менеджерах надо будет при инициализации загрузить из файла моки соответственно юзеров и постов, и сохранить их в мапе по ID. Выдавать методами getUser(int id) и getPost(int id).

В классе UserManager дополнительно определи методы
  • Vector getUsers()
  • User login(String name, String password) throws Exception

Попробуй все это потестить отдельным тестовым классом.

Цитата(diablero @  26.6.2007,  16:44 Найти цитируемый пост)
Stampede у меня есть еще такой вопрос, какой подход используют, если в файле конфигурации много параметров? С точки быстродействия и оптимальности.


Не совсекм понял вопрос. А вообще, чтение конфига - это не та задача, где надо выжимать максимум быстродействия.

Автор: diablero 28.6.2007, 12:37
UserManager
Код

public class UserManager implements Manager{
    private HashMap <Integer, User> storage;
    private Vector<User> data;
    public void init() {
        try {
            XStream xstream = new XStream(new DomDriver());
            data = (Vector) xstream.fromXML(new FileInputStream("conf/user.xml"));
            int size = data.size();
            storage = new HashMap<Integer, User>(size);
            for(User user : data) {
                storage.put(user.getId(), user);
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
    public User login(String name, String password) throws Exception {
        for(User user : data) {
            if(user.getName().equals(name) && user.getPassword().equals(password)) {
                return user;
            }
        }
        throw new Exception("Invalid user name or password");
    }
    public User getUser(int id) {
        return  storage.get(id);
    }
    public Vector <User> getUsers() {
        return data;
    }
    public void destroy() {
        storage = null;
        data = null;
    }
}

PostManager
Код

public class PostManager implements Manager{
    private HashMap<Integer, Post> storage;

    public void init() {
        try {
            XStream xstream = new XStream(new DomDriver());
            Vector<Post> data = (Vector) xstream.fromXML(new FileInputStream("conf/post.xml"));
            int size = data.size();
            storage = new HashMap<Integer,Post>(size);
            for(Post post : data) {
                storage.put(post.getId(), post);
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
    public Post getPost(int id) {
        return  storage.get(id);
    }
    public void destroy() {
        storage = null;
    }
}

Потестил, работает.

Автор: Stampede 28.6.2007, 17:48
Отлично!

Теперь проведем вторую жЫрную черту. Выкладывай zip-архив всего, что наваял. Не забудь про сторонние библиотеки и файлы для моков.

После этого приступим к обсуждению персистенции.

ЗЫ. Я бы все-таки поменял Vector на ArrayList. Vector и Hashtable - это тяжелаое наследие проклятого царизма, когда еще не было фреймворка Collections, а коллекции на всякий случай делались полностью потокобезопасными (естественно, в ущерб производительности).

Автор: diablero 28.6.2007, 19:41
Сменить vector на arraylist я захотел после того как посмотрел соотношение производительности между ними.
А какой струтурой заменить hashmap?
Архив с библиотеками запостить не получиться. Лимит всего 120кб, а со всеми библиотеками архив будет пол мегабайта.
Как сменюсь с дежурства все сделаю.

Автор: Stampede 28.6.2007, 20:13
Цитата(diablero @  28.6.2007,  10:41 Найти цитируемый пост)
Архив с библиотеками запостить не получиться. Лимит всего 120кб, а со всеми библиотеками архив будет пол мегабайта.


Хм, жалко... Модераторы, есть ли какая-нибудь возможность разместить полный архив? Может, дать доступ к FTP или еще-как-то. Спасибо.


Цитата(diablero @  28.6.2007,  10:41 Найти цитируемый пост)
А какой струтурой заменить hashmap


Читай внимательнее. HashMap как раз в полном порядке.

И еще. Я сегодня уезжаю на пять дней. Доступа в инет скорее всего не будет. Так что если не хочешь, чтобы работа на это время застопорилась, давай попытаемся успеть обсудить персистенцию.

Основные кандидаты - голый JDBC, Hibernate, iBatis, Castor. Выбирай с учетом предыдущего опыта и/или желания освоить какую-то технологию.

ЗЫ. Я сам с ORM не работал, так что если что - придется кого-то просить в помощники. Или разбираться вместе.

Автор: diablero 28.6.2007, 20:39
Из перечисленного я имел дело только с jdbc. Заметил что часто требуется работадателями и много народ обсуждает hibernate. Давай будем работать с ним. Или есть аргументы в другую сторону?

Автор: nerezus 28.6.2007, 20:43
Цитата

Заметил что часто требуется работадателями и много народ обсуждает hibernate.
 +1, ORM в разы упростит работу )
попробовал однажды - теперь отвращение к другим способам.

Автор: batigoal 28.6.2007, 21:09
Есть ведь еще Java Persistence API. Я с ним сейчас пытаюсь заморачиваться. Как я понимаю, он во многом базируется (или тупо повторяет) Hibernate.

Автор: Stampede 28.6.2007, 22:22
Говоришь, Hibernate? Ну что ж, хороший выбор.

Попробуй сначала разобраться самостоятельно. Для двух простых таблиц, думаю, сделать самому вполне реально. Ну а если что - проси помощи клуба. В принципе тут грамотных людей хватает. Особенно хорошо будет, если tux согласится помочь.

Но сначала выложи зип.

Добавлено через 2 минуты и 3 секунды
Цитата(batigoal @  28.6.2007,  12:09 Найти цитируемый пост)
Есть ведь еще Java Persistence API.


Я его тоже посмотрел. Но для него, как я понял, нужен полновесный JEE сервер. А мы все-таки делаем обычный вебсайт.

Автор: w1nd 28.6.2007, 22:28
Цитата(Stampede @  28.6.2007,  22:22 Найти цитируемый пост)
Я его тоже посмотрел. Но для него, как я понял, нужен полновесный JEE сервер. А мы все-таки делаем обычный вебсайт.

Не обязательно, возможны (и вроде есть) легковесные реализации.

Автор: Stampede 28.6.2007, 23:32
У JPA (Java Persistence API) есть все предпосылки стать той технологией работы с данными, которая вот уже много лет напрашивалась к реализации и которую так долго ждали разработчики. Думаю, уже в самом скором времени производители ORM-средств адаптируют свои продукты таким образом, чтобы они соответствовали JPA. А когда вслед за этим JPA включат в состав Java SE, на всей планете наступит большое щястье.

Но до этого светлого времени еще нужно дожить. Поэтому давайте пока все-таки придерживаться проверенных технологий.

Автор: fixxer 29.6.2007, 10:20
Хочу заметить, что JPA вполне можно использовать в Java SE. http://hibernate.org/397.html Для этого необходимы пакеты:
http://sourceforge.net/project/showfiles.php?group_id=40712&package_id=127784&release_id=509407, он скорее всего уже есть
http://sourceforge.net/project/showfiles.php?group_id=40712&package_id=139933
http://sourceforge.net/project/showfiles.php?group_id=40712&package_id=156160

Также, если нужно, могу запостить книжку Java Persistence with Hibernate

Автор: diablero 29.6.2007, 20:10
Подвожу вторую жЫрную черту

Автор: Entry_N3 30.6.2007, 11:57
Цитата(Stampede @ 28.6.2007,  23:32)
У JPA (Java Persistence API) есть все предпосылки стать той технологией работы с данными, которая вот уже много лет напрашивалась к реализации и которую так долго ждали разработчики. Думаю, уже в самом скором времени производители ORM-средств адаптируют свои продукты таким образом, чтобы они соответствовали JPA. А когда вслед за этим JPA включат в состав Java SE, на всей планете наступит большое щястье.

Но до этого светлого времени еще нужно дожить. Поэтому давайте пока все-таки придерживаться проверенных технологий.

JSR 220 Java Persistence Architecture – в Java SE 7 планируется включить разработанный группой экспертов по EJB persistence API, значительно облегчающий работу с реляционными БД из объектно-ориентированного кода.

Автор: tux 1.7.2007, 15:27
Цитата(Stampede @  28.6.2007,  22:22 Найти цитируемый пост)
Особенно хорошо будет, если tux согласится помочь.

tux согласится. smile

Автор: powerOn 1.7.2007, 18:40
Цитата(Stampede @  28.6.2007,  23:22 Найти цитируемый пост)
Но для него, как я понял, нужен полновесный JEE сервер. А мы все-таки делаем обычный вебсайт.


Не нужен никакой контейнер. Как правильно заметил fixxer, JPA можно легко использовать в stand-alone программах. JPA - это спецификация описывающая единый интерфейс для ORM фрейвёрков. JPA описывает интерфейсы, которые должен реализовать  ORM фрейвёрк, что бы его можно было использовать в качестве провайдера данных. 
Т.е. если раньше цепочка доступа к данным шла как:
Business Logic -- ORM Framework -- Database.

то теперь она будет выглядеть так:
Business Logic -- JPA -- ORM Framework -- Database.

т.е. Business Logic для доступа к данным должна знать только интерфейс JPA, а не конкретного ORM Framework-а. Что позволяет этот самый ORM Framework легко сменить на другой, не трогая уровень бизнес логики.
В частном случае могут быть такие цепочки:
Business Logic -- JPA -- Hibernate -- Database.
Business Logic -- JPA -- TopLink -- Database.

Цитата(fixxer @  29.6.2007,  11:20 Найти цитируемый пост)
Hibernate Annotations
Hibernate EntityManager


Вот эти библиотеки как раз и реализуют интерфейсы JPA для Hibernate, что позволяет подключить Hibernate в качестве провайдера к JPA. TopLink так же имеет аналогичные реализации.

Так что, ИМХО, JPA - это будущее. (И фиг его почему его ассоциируют с EJB3.0... наверное из-за аннотаций)

Автор: diablero 1.7.2007, 23:05
Цитата(tux @  1.7.2007,  15:27 Найти цитируемый пост)
tux согласится.

Тогда вопросsmile

Не видет файл user.hbm.xml.
Код

try {
            Configuration conf = new Configuration();
            conf.addResource(homeDir+"/conf/user.hbm.xml");
            conf.configure(new File(homeDir+"/conf/hibernate.cfg.xml"));
            SessionFactory sessionFactory = conf.buildSessionFactory();

            Session session = sessionFactory.getCurrentSession();

            sessionFactory.close();

        } catch (Throwable ex) {
            throw new ExceptionInInitializerError(ex);
 }

Хоть так.
Код

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost/infinite</property>
        <property name="connection.username">root</property>
        <property name="connection.password"></property>
        ...
        <mapping resource="conf/user.hbm.xml"/>       
    </session-factory>

</hibernate-configuration>


Причем если у Configuration вызывать configure передавая в качестве параметра путь к hibernate.cfg.xml а не файл, то тоже получаем file not found



Автор: tux 2.7.2007, 05:30
Давай разбираться как конфигурируется Hibernate. Всю свою конфигурацию он может читать либо из файловой системы либо из ресурсов. С файловой системой, я думаю, все понятно, а вот про ресурсы тебе наверное нужно почитать. Грубо говоря, поиск ресурсов выполняется так же, как и поиск классов. То есть, если ты пытаешься прочитать ресурс "/conf/user.hbm.xml", то это означает, что у тебя в CLASSPATH должен быть каталог conf, в котором лежит файл user.hbm.xml. 

Если посмотреть в javadoc от Hibernate, то там видно, что метод addResource добавляет файл именно из ресурсов. Простейший способ решить проблему - положить файл /conf/user.hbm.xml туда, где у тебя лежат скомпилированные классы (либо если используешь, например, Eclipse, то вместе с исходниками, он сам все сделает). Если все же все-таки хочется грузить конфигурацию из файловой системы, то используй метод addFile() вместо addResource(). Еще раз замечу, что при использовании последнего путь нужно задавать относительно корня CLASSPATH. 

С использованием методов configure(String resource) и configure(File configFile) та же самая ситуация - в первом случае конфигурация грузится из ресурса, во втором - из файла, потому и такой несовпадающий результат.

Еще пара замечаний. Использование метода addResource() и наличие в hibernate.cfg.xml тэга mapping дублируют друг друга, поэтому лучше оставить что-то одно. Вместо метода getCurrentSession() лучше использовать openSession(). Первый метод предназначен для использования в серверах приложений и используется JTA для управления транзакциями. Обычно это нужно при работе нескольких приложений с одним источником данных. Метод openSession() явно открывает сессию, а не пытается получить уже существующую. Хотя в текущих версиях Hibernate в методе getCurrentSession() явно вызывается openSession() если не удалось сконфигурироавть JTA (в версиях 3.0 и ниже он просто не работал бы), лучше явно сказать, что мы открываем сессию.

Автор: diablero 2.7.2007, 12:42
Какая-то не понятная ошибка закралась. Никак не могу User'а добавить в базу.
Код

log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: insert into user (id, name, password, group, avatar, birthday, gender, city, country, signature, mail, posts, dateRegistration) values (null, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Exception in thread "main" java.lang.ExceptionInInitializerError
    at ru.selfexpression.test.Test.main(Test.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:86)
Caused by: org.hibernate.exception.SQLGrammarException: could not insert: [ru.selfexpression.User]
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:67)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
    at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:40)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2158)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2638)
    at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:48)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:298)
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:181)
    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:107)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:187)
    at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:33)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:172)
    at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:27)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
    at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:535)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:523)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:519)
    at ru.selfexpression.test.Test.main(Test.java:32)
    ... 5 more
Caused by: java.sql.SQLException: You have an error in your SQL syntax near 'group, avatar, birthday, gender, city, country, signature, mail, posts, dateRegi' at line 1
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2926)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1571)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1666)
    at com.mysql.jdbc.Connection.execSQL(Connection.java:2978)
    at com.mysql.jdbc.Connection.execSQL(Connection.java:2902)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:933)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1162)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1079)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1064)
    at org.hibernate.id.IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(IdentityGenerator.java:73)
    at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:33)
    ... 21 more

[code=xml]
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="ru.selfexpression">

    <class name="User" table="user">
        <id name="id" column="id">
            <generator class="native"/>
        </id>
        <property name="name" type="java.lang.String">
            <column name="name" not-null="true" sql-type="VARCHAR" />
        </property>
        <property name="password" type="java.lang.String">
            <column name="password" not-null="true" sql-type="VARCHAR" />
        </property>
        <property name="group" type="java.lang.String">
            <column name="group" not-null="true" sql-type="VARCHAR" />
        </property>
        <property name="avatar" type="java.lang.String">
            <column name="avatar" not-null="false" sql-type="VARCHAR" />
        </property>
        <property name="birthday" type="java.util.Date">
            <column name="birthday" not-null="false" sql-type="DATE" />
        </property>
        <property name="gender" type="java.lang.String">
            <column name="gender" not-null="false" sql-type="VARCHAR" />
        </property>
        <property name="city" type="java.lang.String">
            <column name="city" not-null="false" sql-type="VARCHAR" />
        </property>
        <property name="country" type="java.lan

Автор: tux 3.7.2007, 04:35
Сразу замечание по базе данных. В именах полей следует избегать зарезервированных слов. В частности, 'group' используется в конструкциях group by. Похоже ошибка связана с этим. Во-первых, в hibernate.cfg.xml нужно указать диалект SQL, с которым предполагается работать. Делается это добавлением еще одной проперти в hibernate.cfg.xml:
Код

<property name="dialect">org.hibernate.dialect.MySQLDialect</property>

Если это не поможет, поменяй в базе данных имена полей 'password' и 'group' на что-то другое.

Однако, исправив эту ошибку, вероятно получим следующую. Вот посмотри на запрос, который сгенерировал Hibernate:
Цитата

insert into user (id, name, password, group, avatar, birthday, gender, city, country, signature, mail, posts, dateRegistration) values (null, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

В поле id вставляется значение null, хотя это видимо первичный ключ и таких значений иметь не может. У тебя используется генератор native, который зависит от используемого диалекта. Явное указание диалекта позволит Hibernate определить что именно использовать. Не помню что используется в MySQL, если это будет тип sequence, то для его использования нужно будет предпринять специальные действия. Если Hibernate будет выдавать ошибки, пока можно будет поменять генератор на простейший - increment. 

Автор: diablero 3.7.2007, 22:58
Изменил имена полей в базе данных и  генератор на increment.
Все заработало.

Автор: Stampede 3.7.2007, 23:54
Всем привет, я вернулся!

Очень рад, что дело не стояло на месте. Теперь вот какой важный момент.

Цитата(fixxer @  29.6.2007,  01:20 Найти цитируемый пост)
Хочу заметить, что JPA вполне можно использовать в Java SE.


Цитата(powerOn @  1.7.2007,  09:40 Найти цитируемый пост)
Как правильно заметил fixxer, JPA можно легко использовать в stand-alone программах.


Это исключительно хорошая новость! Очень жаль, что в документации по JPA этот момент не акцентруется. Я ведь говорю: я заходил, читал материалы, в том числе http://java.sun.com/javaee/overview/faq/persistence.jsp. Везде разговор о JPA ведется в контексте EJB и EE. А вот тут так и вообще открытым текстом:

Цитата

QWill the Java Persistence API become part of Java SE?

A: There are no current plans to include the Java Persistence API in Java SE. As the Java Persistence API evolves, however, it is likely that this issue will be considered by the Java SE expert group in a future Java SE release. 


Дальше читать резко расхотелось. И только сейчас, уже зная правильный ответ, прочел более внимательно, и нашел упоминания о возможности использования JPA в standalone приложениях:

Цитата

In addition, the Java Persistence API is usable both within Java SE environments as well as within Java EE, allowing many more developers to take advantage of a standard persistence API.


Так вот, в свете этой новости ситуация меняется самым радикальным образом. Тем более что у Hibernate уже имеется адаптер для JPA (fixxer ловит плюса за отличную новость). Категорически предлагаю diablero переделать персистенцию в вендорно-независимом виде, то есть через JPA и с аннотациями. Пока еще не слишком поздно.

ЗЫ. Заодно вопрос: а ты точно уверен, что хочешь мускуль? Есть для такого выбора какие-то веские основания?

Автор: y3u 4.7.2007, 06:18
Цитата(Stampede @  4.7.2007,  00:54 Найти цитируемый пост)

Цитата

In addition, the Java Persistence API is usable both within Java SE environments as well as within Java EE, allowing many more developers to take advantage of a standard persistence API.

Так вот, в свете этой новости ситуация меняется самым радикальным образом. Тем более что у Hibernate уже имеется адаптер для JPA 


"вам хочется песен? их есть у меня!"... покажите рабочий примерчик с JPA в стендэлоне smile Пожалуйста, пожалуйста smile

Автор: powerOn 4.7.2007, 07:21
Цитата(y3u @  4.7.2007,  07:18 Найти цитируемый пост)
"вам хочется песен? их есть у меня!"... покажите рабочий примерчик с JPA в стендэлоне smile Пожалуйста, пожалуйста smile




Вы хотели пати? нати  smile :

Что потребуется: Библиотеки для Hibernate Core, Hibernate Entity Manager (они есть на сайте hibernate), JDBC драйвер для MySQL. 

В нашем простом проекте будет всего 3 файла:
1) persistence.xml - файл конфигурации JPA.
2) jpatest.MyEntity - простая сущность. будем её сохранять в БД.
3) jpatest.Main - это класс содержит логику сохранения. 

persistence.xml
Здесь описываем persistence-unit - параметры соединения с БД, диалекты, транзакции, подключаем Entity классы и прочее. Данный файл лежит в каталоге META-INF. Видимо по умолчанию так принято.
Код

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="JpaTestPU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>jpatest.MyEntity</class>
    <properties>
      <property name="hibernate.connection.username" value="root"/>
      <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
      <property name="hibernate.connection.password" value="12345"/>
      <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/testdb"/>
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
      <property name="hibernate.hbm2ddl.auto" value="update"/>
    </properties>
  </persistence-unit>
</persistence>


jpatest.MyEntity
Это класс-сущность. Замапим его на таблицу MyTable используя аннотацию @Table(name="MyTable"). Имеется у данного класа 2 поля - id (главный ключ с автогенерацией) и someData. Опишем их свойства так же с помощью аннотаций.
Код

package jpatest;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Table;

@javax.persistence.Entity
@Table(name="MyTable")
public class MyEntity implements Serializable
{
    @javax.persistence.Id
    @Column(name="id")
    @javax.persistence.GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
    private Long id;
    
    @Column(name="someData")
    private String someData;
    
    /** Creates a new instance of MyEntity */
    public MyEntity()
    {
    }

    public Long getId()
    {
        return this.id;
    }

    public void setId(Long id)
    {
        this.id = id;
    }

    public String getSomeData()
    {
        return someData;
    }

    public void setSomeData(String someData)
    {
        this.someData = someData;
    }
    
    @Override
    public int hashCode()
    {
        int hash = 0;
        hash += (this.id != null ? this.id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object)
    {
        if (!(object instanceof MyEntity)) {
            return false;
        }
        MyEntity other = (MyEntity) object;
        if (this.id != other.id && (this.id == null || !this.id.equals(other.id))) return false;
        return true;
    }

    @Override
    public String toString()
    {
        return "jpatest.MyEntity[id=" + id + "]";
    }
}


jpatest.Main
Данный код сохраняет объект типа MyEntity в БД.
Сначало создаем фабрику для EntityManager-а используя описание нашего persistence-unit-а. Это можно сделать по имени.
Далее создаем сам EntityManager и используем его для сохранения объекта.
Код

package jpatest;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Main
{
    private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("JpaTestPU");
    
    /** Creates a new instance of Main */
    public Main()
    {
    }
    
    public static void main(String[] args)
    {
        EntityManager em = emf.createEntityManager();

        MyEntity myEntity = new MyEntity();
        myEntity.setSomeData("some data");
        
        em.getTransaction().begin();
        try 
        {
            em.persist(myEntity);
            em.getTransaction().commit();
        } 
        catch (Exception e) 
        {
            e.printStackTrace();
            em.getTransaction().rollback();
        } 
        finally {
            em.close();
        }
    }
}



Не трудно заметить, что работа с JPA весьма похожа на работу с Hibernate.

Автор: batigoal 4.7.2007, 07:46
Stampedediablero и остальные: не будете возражать, если дискуссия о выборе персистенса перекочует в отдельный топик?

Автор: diablero 4.7.2007, 17:06
Цитата(Stampede @  3.7.2007,  23:54 Найти цитируемый пост)
 Категорически предлагаю diablero переделать персистенцию в вендорно-независимом виде, то есть через JPA и с аннотациями. Пока еще не слишком поздно.

Категорически согласен.
Цитата(Stampede @  3.7.2007,  23:54 Найти цитируемый пост)
ЗЫ. Заодно вопрос: а ты точно уверен, что хочешь мускуль? Есть для такого выбора какие-то веские основания?

Основание только одно, это единственная имеющаяся у меня база данных. Если есть какие-то основания перейти на что-то другое, то скажи на что.

Stampede давай определим план действий. Что мне сейчас в итоге нужно сделать и как это оформим. Я имею ввиду структуру классов для работы с базой данных. Будем ли реарганизовывать менеджеры или их пока оставим для генерирования исходных данных.

Добавлено через 3 минуты и 17 секунд
Цитата(batigoal @  4.7.2007,  07:46 Найти цитируемый пост)
 diablero и остальные: не будете возражать, если дискуссия о выборе персистенса перекочует в отдельный топик?

Конечно нет

Автор: Stampede 4.7.2007, 19:42
Цитата(batigoal @  3.7.2007,  22:46 Найти цитируемый пост)
Stampede, diablero и остальные: не будете возражать, если дискуссия о выборе персистенса перекочует в отдельный топик?


Я бы все-таки оставил здесь - для удобства всех заинтересованных, кто будет читать эту ветку в будущем. Как ни крути, выбор способа персистенции - это немаловажный момент в разработке сайта, так что высказанные здесь соображения должны помочь людям сориентироваться.

Теперь о реализации. powerOn, спасибо за пример.

Цитата(diablero @  4.7.2007,  08:06 Найти цитируемый пост)
Stampede давай определим план действий. Что мне сейчас в итоге нужно сделать и как это оформим. Я имею ввиду структуру классов для работы с базой данных. Будем ли реарганизовывать менеджеры или их пока оставим для генерирования исходных данных.


Одно из ключевых преимуществ ORM нового поколения (и JPA в том числе) заключается в том, что они позволяют проектировать DAO как обычные классы, или POJO (Plain Old Java Objects), без необходимости выводить их родословную из каких-то специфических классов или интерфейсов. Поэтому User и Post остаются у нас как были, только добавятся необходимые аннотации.

Экземпляр EntityManager у нас будет один на все приложение. В методе Infinite.init() нужно предусмотреть его инициализацию, и потом выдавать всем желающим через геттер. Преимущество от использования единственного менеджера энтитей в том, что тогда он сможет отслеживать и разруливать обновления объектов простым и ненакладным образом. Ну и понятно, не надо будет для каждой транзакции заставлять фабрику заниматься инициализацией нового экземпляра менеджера.

В UserManager и PostManager убираем  работу с моками, при инициализации получаем (от Infinite) и сохраняем локально экземпляр EntityManager. Методы getPost(int id), getUser(int id), getUsers() и login(String name, String password) переписываем для работы с базой.

Все действия по созданию/управлению транзакциями кодируем вне менеджеров. Пока что будем делать это прямо в тесте:

Код

EntityManager em = Infinite.getEntityManager();
UserManager userman = Infinite.getUserManager();
Transaction tx = null;
try {
    tx = em.getTransaction();
    tx.begin();
    User user = userman.getUser(1234);
    logger.info(user);
    tx.commit();
} catch (Exception e) {
    if (tx != null) {
        tx.rollback();
    }
    throw new RuntimeException(e);
}


Все, пока этого достаточно.

Цитата(diablero @  4.7.2007,  08:06 Найти цитируемый пост)
Основание только одно, это единственная имеющаяся у меня база данных. Если есть какие-то основания перейти на что-то другое, то скажи на что.


Бесплатных СУБД много, так что на мускуле свет клином не сошелся. В принципе в последних версиях он уже вроде стал пригодным для работы, но раньше, когда в нем не было поддержки транзакций, вложенных подзапросов и много чего еще, его вообще нельзя было считать за нормальную базу.

В общем, советую поспрошать в подфоруме СУБД, послушать разные мнения. От себя порекомендовал бы Postgres - вполне взрослая база, в меру быстрая и надежная, есть на большинстве Java хостингов.

Но дело, конечно, хозяйское.

Автор: diablero 4.7.2007, 22:18
to fixxer 
Ты говорил, что можешь запостить книжку Java Persistence with Hibernate.
Нужно, если есть возможность.

У меня пока проблемы с пониманием сути и важности Hibernate JPA. Вроде как оно долно упростить нам архитектуру приложения и избавить от множества настроек. А сама работа с базой данных аналогична. Т.е. все настраивается в файле persistence.xml.

В связи с этим у меня сразу вопрос, с чем я столкнулся, это как задать путь к файлу?

Автор: powerOn 4.7.2007, 22:39
Цитата(diablero @  4.7.2007,  23:18 Найти цитируемый пост)
В связи с этим у меня сразу вопрос, с чем я столкнулся, это как задать путь к файлу? 

К какому? К persistence.xml? если про него речь, то просто положи его в папку META-INF, которая будет в корне исходников.

Автор: Stampede 4.7.2007, 22:51
Цитата(diablero @  4.7.2007,  13:18 Найти цитируемый пост)
У меня пока проблемы с пониманием сути и важности Hibernate JPA. Вроде как оно долно упростить нам архитектуру приложения и избавить от множества настроек.


Нет, не столько упростить, сколько избавить от привязки к конкретному продукту.

Понимаешь, когда ты работаешь с Hibernate напрямую, у тебя в проекте появляется множество зависимостей: формат конфига, названия аннотаций, имена классов фабрик и менеджеров, и т. д. В принципе ничего особенно страшного, но вот на другую ORM так запросто уже не перейдешь. И дело тут не в том, что ты будешь десять раз переводить прогу с Hibernate на TopLink, а с TopLink на iBatis, а в том, что при работе через единый API ты нарабатываешь базу для переиспользования в других проектах и приобретаешь "конвертируемый" опыт. И можешь быть уверен, в самом скором будущем работодатели вместо Hibernate будут требовать от кандидатов знание JPA. Потому что так будет намного проще и удобнее для всех.

Цитата(diablero @  4.7.2007,  13:18 Найти цитируемый пост)
В связи с этим у меня сразу вопрос, с чем я столкнулся, это как задать путь к файлу? 


Уточни, о каком файле идет речь. persistence.xml? Не знаю, почитай доку. Должен быть какой-то способ. Может, переменную окружения можно как-то задействовать.

Автор: diablero 4.7.2007, 23:21
Цитата(powerOn @  4.7.2007,  22:39 Найти цитируемый пост)
просто положи его в папку META-INF, которая будет в корне исходников.

спасибо, так работает. А по другому ни как?

У меня вот такое исключение валиться, не знаю почему. Сделал все как в примере у  powerOn
Код

java.lang.IllegalArgumentException: Unknown entity: ru.selfexpression.User
    at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:216)
    at ru.selfexpression.test.Test.main(Test.java:30)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:86)




Автор: powerOn 4.7.2007, 23:25
скорее всего ты забыл у класса сущности указать аннотацию @javax.persistence.Entity. Выложи пожалуйста этот класс сюда.

Автор: diablero 4.7.2007, 23:38
Код

package ru.selfexpression;

import javax.persistence.Table;
import javax.persistence.Column;
import java.util.Date;

@javax.persistence.Entity
@Table(name="user")
public class User {
    @javax.persistence.Id
    @Column(name="user_id")
    @javax.persistence.GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
    private int id;
    @Column(name="user_name")
    private String name;
    @Column(name="user_password")
    private String password;
    @Column(name="user_group")
    private String group;
    @Column(name="user_avatar")
    private String avatar;
    @Column(name="user_birthday")
    private Date birthday;
    @Column(name="user_gender")
    private String gender;
    @Column(name="user_city")
    private String city;
    @Column(name="user_country")
    private String country;
    @Column(name="user_signature")
    private String signature;
    @Column(name="user_mail")
    private String mail;
    @Column(name="user_icq")
    private String icq;
    @Column(name="user_posts")
    private int posts;
    @Column(name="user_dateRegistration")
    private Date dateRegistration;

    public static final String GENDER_MALE = "male";
    public static final String GENDER_FEMALE = "female";

    public User() {

    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getPassword() {
        return password;
    }
    public void setGroup(String group) {
        this.group = group;
    }
    public String getGroup() {
        return group;
    }
    public void setAvatar(String filePath) {
        this.avatar = filePath;
    }
    public String getAvatar() {
        return avatar;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public String getGender() {
        return gender;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public String getCity() {
        return city;
    }
    public void setCountry(String country) {
        this.country = country;
    }
    public String getCountry() {
        return country;
    }
    public void setSignature(String signature) {
        this.signature = signature;
    }
    public String getSignature() {
        return signature;
    }
    public void setMail(String mail) {
        this.mail = mail;
    }
    public String getMail() {
        return mail;
    }
    public void setICQ(String icq) {
        this.icq = icq;
    }
    public String getICQ() {
        return icq;
    }
    public void setPosts(int posts) {
        this.posts = posts;
    }
    public int getPosts() {
        return posts;
    }
    public void setDateRegistration(Date dateRegistration) {
        this.dateRegistration = dateRegistration;
    }
    public Date getDateRegistration() {
        return dateRegistration;
    }
}



Код

<?xml version="1.0" encoding="UTF-8"?>    
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">    
  <persistence-unit name="Infinite" transaction-type="RESOURCE_LOCAL">    
    <provider>org.hibernate.ejb.HibernatePersistence</provider>    
    <class>ru.selfexpression.User</class>    
    <properties>    
      <property name="hibernate.connection.username" value="root"/>    
      <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>    
      <property name="hibernate.connection.password" value=""/>    
      <property name="hibernate.connection.url" value="jdbc:mysql://diablero:3306/infinite"/>    
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>    
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>    
      <property name="hibernate.hbm2ddl.auto" value="update"/>    
    </properties>    
  </persistence-unit>    
</persistence>

Автор: Stampede 4.7.2007, 23:40
Вот еще что вычитал:

Цитата

persistence.xml files define EntityManagerFactories. The createEntityManagerFactory  methods search for persistence.xml files within the META-INF directory of any CLASSPATH  element. For example, if your CLASSPATH contains the conf directory, you could place an EntityManagerFactory definition in conf/META-INF/persistence.xml.

Источник: http://openjpa.apache.org/docs/latest/manual/manual.html


Давай действительно так и сделаем, и conf добавим к classpath. Заодно можно будет убрать ручное задание пути к конфигу log4j - сам будет находиться.

Добавлено через 7 минут и 50 секунд
Дак ты же в объявлени классса не указал implements Serializable!

Автор: diablero 5.7.2007, 00:00
Цитата(Stampede @  4.7.2007,  23:40 Найти цитируемый пост)
Дак ты же в объявлени классса не указал implements Serializable!

Увидел когда постилsmile
Exception только изменился 
Код

javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: ru.selfexpression.User


Автор: diablero 5.7.2007, 00:18
Проблемку решил.
Заменив метод persist на merge

По базе. Я сейчас сижу через GPRS. Попрошу, мне скачают Postgres. Только это будет не скоро.
Если не трудно дай прямой линк на закачку базы и драйвера к ней. 

Вечером приступлю к 
Цитата(Stampede @  4.7.2007,  19:42 Найти цитируемый пост)
В UserManager и PostManager убираем  работу с моками, при инициализации получаем (от Infinite) и сохраняем локально экземпляр EntityManager. Методы getPost(int id), getUser(int id), getUsers() и login(String name, String password) переписываем для работы с базой.


Долг зовет, а еще не спалsmile

Автор: Stampede 5.7.2007, 00:24
А покажи код, чего и как делаешь. Есть подозрение, что нужно правильно объявить scope контекста персистенции. Попробуй такое объявление фабрики:

Код

@PersistenceUnit(unitName="Infinite") EntityManagerFactory emf;


И вообще, почитай вот это вот: https://blueprints.dev.java.net/bpcatalog/ee5/persistence/webonlyapp.html.

Автор: fixxer 5.7.2007, 09:31
Цитата(diablero @ 4.7.2007,  22:18)
to fixxer 
Ты говорил, что можешь запостить книжку Java Persistence with Hibernate.
Нужно, если есть возможность.


http://rapidshare.com/files/41105083/Java_Persistence_with_Hibernate.rar.html

По поводу выбора БД, могу еще предложить FireBird. Простая, бесплатная база данный. Имеет, в отличие от мускуля, мощный диалект SQL, причем довольно чистый, избавленный от болезней роста многих "больших" баз. Практически полностью соответствует стандарту SQL-92 (не уверен по поводу SQL-99)  Довольно шустрая и может работать с большими объемами.

http://ru.wikipedia.org/wiki/Firebird
http://www.firebirdsql.org/

Автор: diablero 5.7.2007, 20:37
Код

public class Infinite {
    private static Infinite instance = new Infinite();
    private static EntityManager entityManager;

    ....
    public void  init(String homeDir) {
    ....
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("Infinite");
    entityManager = emf.createEntityManager();
    ...
    }
    public static EntityManager getEntityManager() {
        return entityManager;
    }

Код

class Test {
    public static void main(String args[]) {
        String homeDir = (args.length > 0) ? args[0] : System.getProperty("user.dir");
        Infinite infinite = Infinite.getInstance();
        infinite.init(homeDir);

        try {
            XStream xstream = new XStream(new DomDriver());
            ArrayList<User> data = (ArrayList) xstream.fromXML(new FileInputStream("conf/user.xml"));
            User user = data.get(4);


            EntityManager em = Infinite.getEntityManager();
            em.getTransaction().begin();
            try
            {
                em.merge(user);
                em.getTransaction().commit();
            }
            catch (Exception e)
            {
                e.printStackTrace();
                em.getTransaction().rollback();
            }
            finally {
                em.close();
            }

        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        }
    }
}



Код

@PersistenceUnit(unitName="Infinite") EntityManagerFactory emf;

Такое объявление ничиго не изменило

Автор: diablero 5.7.2007, 21:25
Что-то с запросами у меня вообще загвостка. Погуглил и только больше запутался. 
Если бы кто-нибудь объяснил по русски, доступно. Было бы здорово

Автор: Stampede 5.7.2007, 22:07
fixxer, спасибо за отличную книжку! Плюс без разговоров smile

Сейчас читаю 9-ю главу (там же, кстати, освещается работа через JPA) - это как раз про контекст персистенции и состояния жизненного цикла персистируемых объектов. Там в числе прочего говорится, что изначально энтити, созданные по new (или как в нашем случае сгенерированные XStream), неизвестны механизму персистенции. Операция merge() как раз и вводит энтитю в соответствующий контекст. Так что я так понимаю, для сохранения объекта в базе после merge() еще нужно выполнить persist().

У тебя в базу что-нибудь попадает?

А что именно не выходит с запросами? Выкладывай, будем разбираться. А то сам понимаешь: лето, пора отпусков - все ясновидящие на курортах smile

Автор: diablero 5.7.2007, 22:20
Цитата(Stampede @  5.7.2007,  22:07 Найти цитируемый пост)
У тебя в базу что-нибудь попадает?

Да, все работает. Загнал в базу всех User и Post.

Я сейчас хотел потренироваться с запросами.  Повыбирать User и Post. 
И пока не додумал, как получить всех User или Post.

Времени уже нет разбираться, я сегодня уеду по работе до воскресенья, в лутшем случае. Доступ в интернет будет, правда только к форуму, через телефон. 

Цитата(Stampede @  5.7.2007,  22:07 Найти цитируемый пост)
fixxer, спасибо за отличную книжку!

завтра я ее скачаю, буду разбираться

Автор: fixxer 6.7.2007, 09:29
Цитата(Stampede @ 5.7.2007,  22:07)
Сейчас читаю 9-ю главу (там же, кстати, освещается работа через JPA) - это как раз про контекст персистенции и состояния жизненного цикла персистируемых объектов. Там в числе прочего говорится, что изначально энтити, созданные по new (или как в нашем случае сгенерированные XStream), неизвестны механизму персистенции. Операция merge() как раз и вводит энтитю в соответствующий контекст. Так что я так понимаю, для сохранения объекта в базе после merge() еще нужно выполнить persist().

Подозреваю, что для введения нового объекта достаточно persist(). А merge() это для введения detached объекта (вышедшего из контекста сессии) в контекст другой сессии. Но не уверен, пусть знающие поправят.

Автор: diablero 8.7.2007, 06:35
Мне нужна помощь, потому что я уже зарылся, и клавиатуре остался один удар до кончиныsmile
Если простые запросы работают, как:
Код

User user = em.find(User.class, 105);
...
Query query = em.createNativeQuery("select * from user", User.class);
query.getResultList();

А вот с запросами с параметрами проблема.

Автор: diablero 8.7.2007, 17:28
Все, разобрался. smile

Автор: diablero 8.7.2007, 20:12
Готово.
Код

package ru.selfexpression;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;


@Entity
@Table(name = "user")
@NamedQueries({
@NamedQuery(
        name="loginUser",
        query="select u from User u where u.name =:name and u.password =:password"),
@NamedQuery(
        name="getAllUsers",
        query="select u from User u")
})

public class User implements Serializable {
    @Id
    @Column(name = "user_id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    @Column(name = "user_name", nullable = false)
    private String name;
    @Column(name = "user_password", nullable = false)
    private String password;
    @Column(name = "user_group", nullable = false)
    private String group;
    @Column(name = "user_avatar")
    private String avatar;
    @Column(name = "user_birthday")
    private Date birthday;
    @Column(name = "user_gender")
    private String gender;
    @Column(name = "user_city")
    private String city;
    @Column(name = "user_country")
    private String country;
    @Column(name = "user_signature")
    private String signature;
    @Column(name = "user_mail")
    private String mail;
    @Column(name = "user_icq")
    private String icq;
    @Column(name = "user_posts")
    private int posts;
    @Column(name = "user_dateRegistration", nullable = false)
    private Date dateRegistration;

    public static final String GENDER_MALE = "male";
    public static final String GENDER_FEMALE = "female";

    public User() {

    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPassword() {
        return password;
    }

    public void setGroup(String group) {
        this.group = group;
    }

    public String getGroup() {
        return group;
    }

    public void setAvatar(String filePath) {
        this.avatar = filePath;
    }

    public String getAvatar() {
        return avatar;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getGender() {
        return gender;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getCity() {
        return city;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCountry() {
        return country;
    }

    public void setSignature(String signature) {
        this.signature = signature;
    }

    public String getSignature() {
        return signature;
    }

    public void setMail(String mail) {
        this.mail = mail;
    }

    public String getMail() {
        return mail;
    }

    public void setICQ(String icq) {
        this.icq = icq;
    }

    public String getICQ() {
        return icq;
    }

    public void setPosts(int posts) {
        this.posts = posts;
    }

    public int getPosts() {
        return posts;
    }

    public void setDateRegistration(Date dateRegistration) {
        this.dateRegistration = dateRegistration;
    }

    public Date getDateRegistration() {
        return dateRegistration;
    }
}

Код

package ru.selfexpression;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.persistence.PersistenceUnit;
import java.util.ArrayList;


public class UserManager implements Manager{
    @PersistenceUnit(unitName="Infinite") private EntityManager entityManager;

    public void init(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
    public User login(String name, String password) throws Exception {
        User user;
        try {
            entityManager.getTransaction().begin();
            Query query = entityManager.createNamedQuery("loginUser");
            query.setParameter("name", name);
            query.setParameter("password", password);
            user = (User) query.getSingleResult();
        } catch(NoResultException err) {
            user = null;
            throw new Exception("Invalid user name or password");
        } finally {
            entityManager.getTransaction().commit();
        }
        return user;
    }
    public User getUser(int id) {               
        User user;
        try{
            entityManager.getTransaction().begin();
            user = entityManager.find(User.class, id);
        } catch (NoResultException err) {
            user = null;
        } finally {
            entityManager.getTransaction().commit();
        }
        return  user;
    }
    public ArrayList<User> getUsers() {
        ArrayList<User> data;
        try {
            entityManager.getTransaction().begin();
            Query query = entityManager.createNamedQuery("getAllUsers");
            data = (ArrayList<User>) query.getResultList();
        } catch(Exception err) {
            data = null;
        } finally {
            entityManager.getTransaction().commit();
        }
        return data;
    }
    public void destroy() {
        entityManager.clear();
    }
}


Автор: diablero 10.7.2007, 09:11
И Post.
Код

@javax.persistence.Entity
@Table(name="post")

public class Post implements Serializable {
    @javax.persistence.Id
    @Column(name="post_id")
    @javax.persistence.GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
    private int id;
    @Column(name="post_status")
    private int status;
    @Column(name="post_userId")
    private int userId;
    @Column(name="post_section")
    private int section;
    @Column(name="post_date")
    private Date date;
    @Column(name="post_title")
    private String title;
    @Column(name="post_text")
    private String text;
    @Column(name="post_viewings")
    private int viewings;
    @Column(name="post_rating")
    private float rating;

    public static final int AUTHORIZED = 0;
    public static final int NOT_AUTHORIZED = 1;

    public Post() {

    }
    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }
    public void setStatus(int status) {
        this.status = status;
    }
    public int getStatus() {
        return status;
    }
    public void setUserId(int userId) {
        this.userId = userId;
    }
    public int getUserId() {
        return userId;
    }
    public void setSection(int section) {
        this.section = section;
    }
    public int getSection() {
        return section;
    }
    public void setDate(Date date) {
        this.date = date;
    }
    public Date getDate() {
        return date;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getTitle() {
        return title;
    }
    public void setText(String text) {
        this.text = text;
    }
    public String getText() {
        return text;
    }
    public void setViewings(int viewings) {
        this.viewings = viewings;
    }
    public int getViewings() {
        return viewings;
    }
    public void setRating(float rating) {
        this.rating = rating;
    }
    public float getRating() {
        return rating;
    }
}

Код

public class PostManager implements Manager{
    @PersistenceUnit(unitName="Infinite") private EntityManager entityManager;

    public void init(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
    public Post getPost(int id) {
        Post post;
        try{
            entityManager.getTransaction().begin();
            post = entityManager.find(Post.class, id);
        } catch (NoResultException err) {
            post = null;
        } finally {
            entityManager.getTransaction().commit();
        }
        return  post;
    }
    public void destroy() {
        entityManager.clear();
    }
}

Что делаем дальше?

Автор: Stampede 10.7.2007, 21:22
Все-таки маленько не так. Помнишь, я писал:

Цитата(Stampede @  4.7.2007,  10:42 Найти цитируемый пост)
Все действия по созданию/управлению транзакциями кодируем вне менеджеров. Пока что будем делать это прямо в тесте:


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

И еще пара замечаний.
  • В блоке finally ты делаешь коммит. Это как бы семантически не совсем верно. Коммит лучше поставить последним стейтментом в блоке try, а по исключению (в catch) делать роллбак.

    Конечно, если бы был некий универсальный метод закрытия, типа как Connection.close() в JDBC, то мы бы его поместили в finally. Но блин недодумали маленько эксперты. Так что придется быть очень аккуратными в оформлении транзакционного кода, а не то у нас будут накапливаться незакрытые соединения.
  • Ты передаешь экземпляр entityManager через параметр метода init(). Зачем, когда мы можем получить его напрямую из Infinite?

Вообще персистенцию следовало бы хорошенько потестить на предмет разных возможных ситуаций, включая сбойные. Например, сделать вставку, а потом в этой же транзакции выполнить некорректную операцию, чтобы выскочило исключение. Посмотреть, что получится. Так можно заранее выявить и предотвратить многие неприятные сюрпризы. Это намного проще, чем потом пытаться воссоздать сбойный сценарий, тыкая в вебный интерфейс.

В общем, рано пока двигаться дальше.

[оффтоп]Товарищи модеры, вы не в курсе, куда делся мой аватар?[/оффтоп]

Автор: Maksym 11.7.2007, 12:44
Цитата(Stampede @  10.7.2007,  21:22 Найти цитируемый пост)
куда делся мой аватар?

сбой базы, http://forum.vingrad.ru/forum/topic-162789/0.html

Автор: diablero 11.7.2007, 18:35
Цитата

В UserManager и PostManager убираем  работу с моками, при инициализации получаем (от Infinite) и сохраняем локально экземпляр EntityManager. Методы getPost(int id), getUser(int id), getUsers() и login(String name, String password) переписываем для работы с базой.

Поэтому и передаю экземпляр entityManager через параметр метода init().

Цитата(Stampede @  10.7.2007,  21:22 Найти цитируемый пост)
Пока что будем делать это прямо в тесте

ОК, персистенцию потестил, прямо в Test'е. Всю работу с базой в менеджерах убрал.

Цитата(Stampede @  10.7.2007,  21:22 Найти цитируемый пост)
 Как же мы тогда обеспечим целостность всей совокупности обращений, например, в рамках обработки одного запроса?

Какую организацию мы будем использовать для решения этой задачи?

Автор: Stampede 11.7.2007, 21:33
Цитата(diablero @  11.7.2007,  09:35 Найти цитируемый пост)
Поэтому и передаю экземпляр entityManager через параметр метода init().


У нас же есть специальный метод, Infinite.getEntityManager() - вот им и нужно пользоваться. Тут идея вот в чем. Мы неспроста завели интерфейс Manager. Это такая удобная конструкция, про которую известно, что ее можно создать, инициализировать, и в конце работы прихлопнуть.

А ты введением в сигнатуру метода Manager.init() параметра EntityManager ломаешь эту конструкцию. Во-первых, JPA - это всего лишь одна из технологий персистенции, хоть и весьма перспективная. Но это конкретная технология и конкретный API, а ты ее зашиваешь в такую абстрактную вещь как Manager.

Во-вторых, не все менеджеры обязательно будуть работать с базой. Например, почтовый менеджер, который нам рано или поздно понадобится, может вообще ничего не знать про какой-то там EntityManager.

В общем, надеюсь, это достаточно понятно.

Цитата(diablero @  11.7.2007,  09:35 Найти цитируемый пост)
ОК, персистенцию потестил, прямо в Test'е. Всю работу с базой в менеджерах убрал.


Там не работу с базой надо убрать, а работу с транзакциями! Чтобы в менеджере осталось только вот это:

Код

public Post getPost (int id) {
    return entityManager.find(Post.class, id);
}


Ты это и имел в виду?

Цитата(diablero @  11.7.2007,  09:35 Найти цитируемый пост)
Цитата(Stampede @  10.7.2007,  21:22 Найти цитируемый пост)
 Как же мы тогда обеспечим целостность всей совокупности обращений, например, в рамках обработки одного запроса?

Какую организацию мы будем использовать для решения этой задачи?


Как я уже говорил, в тестах - прямо в коде теста, а когда будем делать вебный слой, то в коде обработки запроса.

Чтобы в каждом тесте не дублировать один и тот же код работы с транзакциями, можно воспользоваться преимуществами объектного подхода и написать базовый класс для тестирования датабазного слоя. Что-нибудь типа такого:

Код

public abstract class DataTest {
    protected abstract void doTest();

// ...

    public void run() {
        EntityManager em = Infinite.getEntityManager();
        EntityTransaction tx = null;
        try {
            tx = em.getTransaction
            tx.begin();
            doTest();
            tx.commit();
        } catch (Exception e) {
            try {
                tx.rollback();
            } catch (Exception e1 {
                logger.error("Rollback failed: " + e1.getMessage());
            }
            logger.printStackTrace(e);
        }
    }
}


Туда же, кстати, можно перенести и метод main() и все действия по инициализацию проги. В результате написание тестов сведется всего-навсего к определению метода doTest(), без лишней мутоты.

Собственно, в JUnit примерно так и организовано: ты наследуюшь от TestCase, и все методы, что имеют имя вида testXXX, выполняются за тебя автоматически.

Короче, переделай с учетом замечаний и покажи. Возможно, на этом и проведем третью жЫрную черту. После этого сразу приступим к долгожданному вебу smile

ЗЫ. 2 Maksym: ага, понятно, спасибо.

Автор: diablero 11.7.2007, 22:22
Да, я убрал работу с транзакциями
Код

package ru.selfexpression;

import javax.persistence.PersistenceUnit;
import javax.persistence.EntityManager;

public class PostManager implements Manager{
    @PersistenceUnit(unitName="Infinite") private EntityManager entityManager;
    public void init() {
         entityManager = Infinite.getEntityManager();
    }
    public Post getPost(int id) {
        return entityManager.find(Post.class, id);
    }
    public void destroy() {
        entityManager.clear();
    }
}

Код

package ru.selfexpression;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceUnit;
import javax.persistence.Query;
import java.util.ArrayList;    
public class UserManager implements Manager{    
    @PersistenceUnit(unitName="Infinite") private EntityManager entityManager;    
    public void init() {    
           entityManager = Infinite.getEntityManager();
    }    
    public User login(String name, String password) throws Exception {
        Query query = entityManager.createNamedQuery("loginUser");    
        query.setParameter("name", name);    
        query.setParameter("password", password);    
        return (User) query.getSingleResult();    
    }    
    public User getUser(int id) {                
        return entityManager.find(User.class, id);    
    }    
    public ArrayList<User> getUsers() {    
        Query query = entityManager.createNamedQuery("getAllUsers");    
        return (ArrayList<User>) query.getResultList();    
    }    
    public void destroy() {    
        entityManager.clear();    
    }    
}

Цитата(Stampede @  11.7.2007,  21:33 Найти цитируемый пост)
Тут идея вот в чем. Мы неспроста завели интерфейс Manager. Это такая удобная конструкция, про которую известно, что ее можно создать, инициализировать, и в конце работы прихлопнуть.

Это понятно, но чтобы проинициализировать в менеджере entityManager, нам нужно либо передать ссылку на объект через конструктор, какой-то метод, либо унаследовать от Infinite, либо сделать  статиком entityManager и геттер. Ведь так? 

Автор: Stampede 11.7.2007, 22:36
Цитата(diablero @  11.7.2007,  13:22 Найти цитируемый пост)
Это понятно, но чтобы проинициализировать в менеджере entityManager, нам нужно либо передать ссылку на объект через конструктор, какой-то метод, либо унаследовать от Infinite. Ведь так? 


Дак а мы же специально для этого сделали Infinite синглтоном - именно для простоты лукапа. Чтобы в любом место проги написал Infinite.getInstance() - и фсе, доступайся к чему хочешь.

Более того, я ранее отмечал, что использовать синглтон чисто ради лукапа - это не совсем хорошая идея. Есть там кое-какие подводные грабли. Но для нас пока сойдет. А где-то ближе к концу проекта отдельно поговорим про грабли и альтернативные решения.

Как все доделаешь, выкладывай зип. Тесты только перепиши в ООП-ним виде, по типу как я показывал.

Автор: diablero 11.7.2007, 23:33
Готово.

Я вот только так и не понял фишку с логером, геттера мы для него не делаем. Он у нас объявлен как приватный. 
Сделать его пабликом?

Автор: Stampede 11.7.2007, 23:40
Цитата(diablero @  11.7.2007,  14:33 Найти цитируемый пост)
Я вот только так и не понял фишку с логером, геттерамы для него не делаем. Он у нас объявлен как приватный. 
Сделать его пабликом?


Нет, просто в каждом классе, где понадобится логгер, прописываем свой собственный:

Код

private Logger logger = Logger.getLogger(YourClass.class);


Сырцы щас посмотрю.

Да, вот еще что. Я бы на твоем месте пошел и зерегил домен selfexpression.ru. От киберсквоттеров гадости можно ждать в любой момент.

Автор: diablero 11.7.2007, 23:52
Подправил, архив изменил

Автор: diablero 13.7.2007, 18:33
Что делаем дальше?

Автор: Stampede 13.7.2007, 18:35
Хорошо бы еще скрипт создания базы выложить. И вообще, положи его в проект, пусть и в зипах тоже будет.

Итак, вебный интерфейс. Расскажи как можно более подробно, что будет на сайте и какие ты предполагаешь для всего этого УРЛы.

Автор: diablero 14.7.2007, 02:40
Цитата(Stampede @  13.7.2007,  18:35 Найти цитируемый пост)
Расскажи как можно более подробно, что будет на сайте

Это же обсуждалось, или подробно, это до структуры страниц?

Цитата(Stampede @  13.7.2007,  18:35 Найти цитируемый пост)
и какие ты предполагаешь для всего этого УРЛы

Я эту часть предложения не понял

Автор: Stampede 14.7.2007, 03:08
Цитата(diablero @  13.7.2007,  17:40 Найти цитируемый пост)
Это же обсуждалось, или подробно, это до структуры страниц?


Знач смотри. У тебя будет несколько типов страниц: главная, раздел, статья, личная страница участнега, и т. д. Надо, чтобы у тебя было представление, что будет на каждой странице (это сейчас не важно), и по какому шаблону у них будет задаваться УРЛ.

ЗЫ. А ты чего там полуночничаешь? smile

Автор: diablero 14.7.2007, 03:21
Цитата(Stampede @  13.7.2007,  18:35 Найти цитируемый пост)
Хорошо бы еще скрипт создания базы выложить. И вообще, положи его в проект, пусть и в зипах тоже будет.

Добавил скрипт создания базы и положил в папку lib, readme файл, со списком необходимых библиотек.

Цитата(Stampede @  14.7.2007,  03:08 Найти цитируемый пост)
ЗЫ. А ты чего там полуночничаешь?

Такая у меня дурная работа, ушел в четверг, пришел в субботу. И чтобы дело не стояло на месте, читаю форум перед сномsmile

Автор: Stampede 14.7.2007, 03:33
Цитата(diablero @  13.7.2007,  18:21 Найти цитируемый пост)
И чтобы дело не стояло на месте, читаю форум перед сном


Во, вот это правильно - загружать подкорку перед сном. Пускай подсознание молотит, пока спишь smile

Автор: diablero 14.7.2007, 03:53
Я сейчас структурно все опишу, завтра на свежую голову подкорректирую, и выложу вордовский файл.

Автор: diablero 14.7.2007, 18:34
Формат страниц готов, для наглядности сделан в вордовском файле, на таблицах.
Жду критики и предложений...

УРЛ предлагаю формировать по полям id

Автор: niasilil 16.7.2007, 06:30
Цитата(Stampede @ 22.6.2007,  09:32)
...
А вообще в борьбе внутреннего Шустрого Программиста и ОО Архитектора внутри себя, программист должен доминировать безоговорочно, и если что - не стесняясь бить архитектора по рукам, чтоб не терял чувства реальности smile
...

Удивительно. А как же принцип "program to an interface"? 
Так как офттопик, то открыл новую http://forum.vingrad.ru/forum/topic-163606.html. 

Автор: Stampede 16.7.2007, 18:16
Цитата(diablero @  14.7.2007,  09:34 Найти цитируемый пост)
УРЛ предлагаю формировать по полям id


Значит, ЧПУ* не хочешь?

* http://ru.wikipedia.org/wiki/%D0%A7%D0%9F%D0%A3_(%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%BD%D0%B5%D1%82) (Человеко-понятный УРЛ) - концепт, ввведенный в широкий обиход известным флеймером всея Рунета, автором сайтного движка Register http://nudnik.ru/.

Автор: diablero 16.7.2007, 19:20
Цитата(Stampede @  16.7.2007,  18:16 Найти цитируемый пост)
Значит, ЧПУ* не хочешь?

Можно и ЧПУ, я за то как нам удобней.
На мой взгляд по id проще запросы будут. Хоть это и относительная простота, но все же.

Автор: Stampede 16.7.2007, 19:39
Цитата(diablero @  16.7.2007,  10:20 Найти цитируемый пост)
На мой взгляд по id проще запросы будут.


Хорошо, договорились. Итак, предлагай УРЛы для следующих страниц: раздел, статья и профиль. Я со своей стороны предлагаю использовать имя в формате [имя.расширение], причем расширение - не типичное для сервлетных книжек .do, а такое, которое указывало бы на характер ссылаемого документа. Например, я считаю, что .shtml было бы вполне уместно - так традиционно помечают динамические странички, генерируемые с использованием технологии SSI (Server-Side Include). Но в принципе выбор твой.

Автор: diablero 16.7.2007, 19:55
Цитата(Stampede @  16.7.2007,  19:39 Найти цитируемый пост)
Например, я считаю, что .shtml было бы вполне уместно - так традиционно помечают динамические странички, генерируемые с использованием технологии SSI (Server-Side Include).

Я согласен. 

news.shtml
section.shtml
article.shtml
account.shtml

Автор: Stampede 16.7.2007, 20:26
Отлично.

Итак, приступаем к вебу. Для начала нам нужно прикрутить к проекту шаблонный движок. Особой разницы между ними нет, так что возьмем хорошо всем знакомый Velocity, легкий и шустрый.

Надо будет написать класс TemplateManager такого примерно содержания:

Код

public class TemplateManager implements Manager {
    private VelocityEngine engine;

    public void init() {
        // создаем инстанс движка и инициализируем его пропертями из conf/velocity.properties
    }

    public String merge(String templateName, Context context) {
        // через движок получаем объект Template, сливаем с контекстом, выдаем строку
    }

    public void destroy() {
        // чего-нибудь высвобождаем/закрываем
    }
}


Как мы видим, в таком виде менеджер шаблонов получается у нас практически независимым от Velocity, то есть при желании его можно будет переделать под любую технологию рендеринга, включая XSLT и JSP. Опять же, можно запросто оформить это дело как интерфейс, а для Velocity написать реализацию VelocityTemplateManager. Но я напоминаю, мы не делаем библиотеку общего назначения, мы делаем просто сайт. Подчеркиваю это специально для niasilil, который, похоже, несколько излишне загрузился вопросом:

Цитата(niasilil @  15.7.2007,  21:30 Найти цитируемый пост)
Удивительно. А как же принцип "program to an interface"? 


niasilil, я там отписался в твоей теме.

Шаблоны предлагаю хранить в отдельной папке {home_dir}/template.

Автор: diablero 16.7.2007, 21:31
Цитата(Stampede @  16.7.2007,  20:26 Найти цитируемый пост)
 Особой разницы между ними нет, так что возьмем хорошо всем знакомый Velocity, легкий и шустрый.

Я всеми руками за него.

Автор: diablero 16.7.2007, 22:41
Код

public class TemplateManager implements EngineManager{
    private VelocityEngine engine;
    private Logger logger = Logger.getLogger(Infinite.class);

    public void init() {
        Infinite infinite = Infinite.getInstance();
        String homeDir = infinite.getHomeDir();
        try {
            engine = new VelocityEngine(homeDir+"/conf/velocity.properties");
        } catch (Exception e) {
            logger.error(e);
        }
    }

    public String merge(String templateName, Context context) {

        return null;
    }

    public void destroy() {
        engine = null;
    }
}

Цитата

через движок получаем объект Template, сливаем с контекстом, выдаем строку

А строку мы из Writer'а получаем?

Автор: Stampede 16.7.2007, 23:13
Цитата(diablero @  16.7.2007,  13:41 Найти цитируемый пост)
А строку мы из Writer'а получаем? 


Используй StringWriter.

 >  private Logger logger = Logger.getLogger(Infinite.class);

Вообще-то идея была в каждом классе заводить логгер, используя свой собственный класс (сорри за тавтологию). То есть передавать TemplateManager.class.

Далее: обработка исключений. Везде, где встречаем библиотеко-зависимые чекнутые исключения - ловим, заворачиваем в рантаймное и пробрасываем дальше. А не глотаем, как у тебя.

Хорошо было бы еще на содержимое velocity.properties посмотреть.

Ну и тест какой-нить прогони и расскажи как получается.

Автор: diablero 16.7.2007, 23:18
Пока так.
Код

input.encoding = UTF-8
output.encoding = UTF-8
file.resource.loader.path = templates

Автор: Stampede 16.7.2007, 23:47
> file.resource.loader.path = templates

Тут есть одна тонкость. Это у тебя работает до тех пор, пока ты запускаешь тест из корня проекта (который у тебя совпадает с домашней директорией). При запуске из контейнера это сломается. К сожалени, Velocity не понимает переменные окружения. Один из выходов заключается в том, чтобы вручную загрузить проперти, получить значение file.resource.loader.path, подменить в нем переменную домашней директории (по шаблону) реальным значением, и засунуть обратно в проперти. Потом инициализировать движок методом VelocityEngine.init(Properties props).

То есть в конфиге будет примерно такое:

file.resource.loader.path = {home_dir}/templates

Так как там с тестом?

Автор: diablero 17.7.2007, 00:29
Готово.
Код

public class TemplateManager implements EngineManager{
    private VelocityEngine engine;

    public void init() {
        Infinite infinite = Infinite.getInstance();
        String homeDir = infinite.getHomeDir();
        try {
            String tml = "{home_dir}";
            Properties properties = new Properties();
            properties.load(new FileInputStream(homeDir+"/conf/velocity.properties"));
            String value = properties.getProperty("file.resource.loader.path");
            value = value.substring(tml.length(), value.length());
            properties.put("file.resource.loader.path", homeDir+value);
            engine = new VelocityEngine(properties);
        } catch (Exception err) {
            throw new RuntimeException(err);
        }
    }
    public String merge(String templateName, Context context) {
        try {
            StringWriter sw = new StringWriter();
            engine.mergeTemplate(templateName, context, sw);
            return sw.getBuffer().toString();
        } catch (Exception err) {
            throw new RuntimeException(err);
        }
    }

    public void destroy() {
        engine = null;
    }
}

test.shtml
Код

<html>
<head>
<title>$article_title</title>
</head>
<body>
<div class="user">    
  User name: $user.getName()    
</div>

</body>
</html>

Код

public class UserDataTest extends DataTest{
    protected void doTest() {
        Infinite infinite = Infinite.getInstance();
        UserManager manager = infinite.getUserManger();

        try {
            //manager.login("bobr", "54321");

        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        User user = manager.getUser(105);
        TemplateManager templateManager = infinite.getTemplateManager();

        Context context = new VelocityContext();
        context.put("article_title", "ПРЕВЕД");
        context.put("user", user);
        templateManager.merge("test.shtml", context);
    }
}

Автор: Stampede 17.7.2007, 01:15
Ништяк! smile

Теперь ты видишь, к чему мы клоним? Практически сайт у тебя уже готов. Ну, не совсем конечно...

Нам щас нужно сделать одну важную вешь: запустить все это дело из-под контейнера. Для этого придется реорганизовать структуру директорий. Все, что у тебя в корне (кроме сырцов), нужно поместить в ROOT/WEB-INF. Убедись, что скомпилированные классы лежат в WEB-INF/classes, а либы - в WEB-INF/lib. Если нужно, настрой проект как веб-приложение.

После этого пропиши в server.xml:

Код

    <Host name="localhost" appBase="c:/infinite">
        <Context path="" reloadable="true" docBase="ROOT" />
    </Host>


Можешь для удобства добавить в etc/hosts запись:

127.0.0.1       infinite

А если еще сменишь порт томката с 8080 на стандартный 80, то вообще сможешь ходить по адресу http://infinite/

Изобрази какой-нибудь web.xml по аналогии с примерами, чтоб *.jsp мапилось на JspServlet. Положи в ROOT простенькую JSPшку и попробуй вызвать ее из браузера.

Саму прогу пока не дергай.

Автор: diablero 17.7.2007, 01:41
Все сделал. Запускаю и работает простой сервлет.

P.S. ушел спать...

Автор: Stampede 17.7.2007, 16:43
Так, продолжаем.

Начнем потихоньку слздавать наш класс-контроллер, букву C в MVC (хотя, если кто помнит, термин  CLMV представляется мне гораздо более адекватным). Пускай он наследует от HttpServlet и дополнительно реализует ServletContextListener. Можно, конечно, сделать это и в разных классах, но в одном будет компактнее.

Update: Внимание! Как по ходу выяснилось, из-за того что листенер и сервлет имеют свой собственный жизненный цикл, объединить их в одном классе не получается. То есть делаем отдельно класс-листенер, отдельно класс-сервлет.

По contextInitialized() надо поднять Infinite. Для этого надо знать путь к домашней директории. Получаем так: ServletContext.getRealPath("/WEB-INF");

В обработке запроса, метод service(), можно для начала проанализировать УРЛ запрашиваемого ресурса, и если он соответствует одному из (раздел, статья, аккаунт), то выдать содержимое соответствующего шаблона.

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

Заодно ты увидишь, что задавая разные адреса, ты можешь получать разные странички.

Да, маппинг к сервлету-контроллеру в web.xml, естественно, удобнее всего прописать по расширению .shtml


Автор: diablero 17.7.2007, 21:21
При инициализации класса Infinite вываливается эксепшен. Никак home dir получить не могу, все время он null.
В чем может быть ошибка?
Код

public class MainController extends HttpServlet implements ServletContextListener {
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        String homeDir = servletContextEvent.getServletContext().getRealPath("/WEB-INF");
        Infinite infinite = Infinite.getInstance();
        infinite.init(servletContext.getRealPath(homeDir));
    }

    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }

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

    }
}

Автор: diablero 19.7.2007, 19:06
Пролопатил пол инета. У многих проблема с getRealPath().
Пока есть только такое решение:
Код

ServletContext sc = request.getSession().getServletContext();
String str= sc.getRealPath("/");

Автор: Stampede 19.7.2007, 20:34
Цитата(diablero @  17.7.2007,  12:21 Найти цитируемый пост)
Никак home dir получить не могу, все время он null.
В чем может быть ошибка?


Чет какие-то новости. Никогда про такое не слышал. Какой контейнер используешь и какой версии?

Вариант получать корень тоже, конечно, прокатит, но хотелось бы разобраться.

Автор: diablero 19.7.2007, 21:09
Tomcat 5.5

servletContextEvent равен null, может я чего-то не так делаю?

Причем если реализовывать интерфейс Servlet, то тогда в методе  public void init(ServletConfig servletConfig), можно получить домашнюю директорию. 

Автор: Stampede 19.7.2007, 21:58
Знаешь, это, по-видимому, из-за различия жизненных циклов у листенера и сервлета. Об этом я как-то не подумал. Попробуй сделать их как два разных класса.

Автор: diablero 19.7.2007, 22:44
Цитата(Stampede @  19.7.2007,  21:58 Найти цитируемый пост)
 Попробуй сделать их как два разных класса.

Так работаетsmile 
Infinite поднялся. 

Два дня напрягался, а оказалось как обычно. Все гениальное просто.smile

Автор: Stampede 19.7.2007, 23:03
Цитата(diablero @  19.7.2007,  13:44 Найти цитируемый пост)
Два дня напрягался, а оказалось как обычно. Все гениальное просто.


Мда, это я сам себя попытался перехитрить... Ведь никогда же так не делал раньше. В общем, век живи - век учись... (дальше не надо smile)

Ну хорошо, давай теперь сервлет сделай.

Автор: diablero 20.7.2007, 16:02
Цитата(Stampede @  19.7.2007,  23:03 Найти цитируемый пост)
Ну хорошо, давай теперь сервлет сделай

Тут у меня загвоздка, я чего-то не могу сообразить как программно это реализовать. В теории вроде все понятно. Точка входа у нас будет одна на все приложение, которая будет выступать в роли диспетчера. И набор классов, которые будут обрабатывать запросы.



Автор: Stampede 20.7.2007, 17:47
А что именно непонятно? Ну скопируй для начала код из последнего теста (с Velocity) внутрь service(), а в конце выведи получившийся текст через response.getWriter().write().

Убедимся, что все работает, и пойдем дальше.

Автор: diablero 20.7.2007, 18:28
Цитата(Stampede @  20.7.2007,  17:47 Найти цитируемый пост)
А что именно непонятно? Ну скопируй для начала код из последнего теста (с Velocity) внутрь service(), а в конце выведи получившийся текст через response.getWriter().write().

Так это я давно сделал. Все работает.
Давай мой вопрос отложим, пойдем дальше. А там может из твоих указаний будет мне все понятно.

Автор: Stampede 20.7.2007, 19:32
Хорошо, двинем дальше.

Следующим шагом мы реализуем букву L (Logic) в идиоме CLMV. Для этого я предлагаю ввести понятие воркера.

Что такое воркер? Это компонент, который отвечает за построение программного представления страницы определенного типа. Например, воркер раздела, или воркер главной страницы.

Возьмем, к примеру, воркер статьи. Мы как разработчики сайта имеем вполне четкое представление о том, что должно присутствовать на этой странице: это инфа об авторе плюс инфа о статье плюс сам текст статьи. Фсе! Как получить всю эту инфу по id статьи мы знаем. Возникает вопрос: а куда складывать все эти данные? Ответ: так прямо в контекст Velocity!

Представим себе такой интерфейс:

Код

public interface Worker {
    public VelocityContext execute(HttpServletRequest request, HttpServletResponse response);
    public Template getTemplate();
}


Отсюда уже нетрудно представить, как будет выглядеть реализация ArticleWorker.

Если мы теперь соорудим нечто вроде внутреннего диспетчера, который по УРЛу будет выдавать нам экземпляр воркера, то все становится вообще тривиально. На первых порах логику разрешения УРЛов в воркеры можно засунуть просто в отдельный метод в нашем сервлете-контроллере:

Код

private static Map mappings;
static {
    mappings = new HashMap<String, Class<Worker>>();
    mappings.put("home.shtml", HomeWorker.class);
    mappings.put("section.shtml", SectionWorker.class);
    mappings.put("article.shtml", ArticleWorker.class);
}
protected Class<Worker> getWorkerClass(String url) {
    Class<Worker> cls = mappings.get(url);
    return (cls == null)? ErrorWorker.class : cls;
}


Понятно, что со временем все это оформится в отдельный конфигурируемый компонент, ну а пока нам нужно просто проверить идею.

Просю.

ЗЫ. Расширения шаблонов лучше поменять на стандартное .vm Это поможет избежать путаницы на последующих этапах.

Автор: diablero 20.7.2007, 23:56
Я чего-то не могу сообразить. Как у нас взаимодействуют между собой MainController, воркеры и TemplateManager. 

Автор: Stampede 22.7.2007, 05:49
Хорошо, смотри:

Код

public void service(HttpServletRequest request, HttpServletResponse response)
                throws ServletException {
    Infinite infinite = Infinite.getInstance();
    EntityTransaction tx;
    try {
        tx = infinite.getEntityManagager.getTransaction();
        tx.begin();
        String url = request.getRequestURL();
        Class<Worker> cls = getWorkerClass(url);
        Worker worker = cls.newInstance();
        VelocityContext ctx = worker.execute(request, response);
        Template template = worker.getTemplate();
        String html = infinite.merge(template, ctx);
        Writer writer = response.getWriter();
        writer.write(html);
        writer.flush();
        tx.commit();
    } catch (Exception e) {
        if (tx != null) {
            tx.rollback();
        }
        throw new ServletException (e);
    }
}


Вот так вот примерно.

Автор: diablero 22.7.2007, 15:55
Готово. 

Автор: Stampede 22.7.2007, 18:09
Цитата(diablero @  22.7.2007,  06:55 Найти цитируемый пост)
Готово. 


Дык и?

Что получается? Сколько и каких страниц нааял? Все ли отрабатывает как должно? Надо же хоть что-то рассказать. Или ты такой этот, брат краткости? smile

Вот еще какую штуку можешь сделать. Чтобы ходить на главную страницу по http://infinite/, нужно прописать соответствующий маппинг. К сожалению, в синтаксисе web.xml такое не предусмотрено. Поэтому приходится делать финт ушами - по счастью, совсем несложный. В корень кладется файл index.jsp такого содержания:

Код

<%@ page contentType="text/html; charset=utf-8" %>

<jsp:forward page="/home.shtml" />


Надо только не забыть в web.xml прописать маппинг для JspServlet и определить welcome-file-list.

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

Автор: diablero 22.7.2007, 18:39
Все работает, по УРЛу выдаются запрашиваемые страницы. 
Повыводил статьи, пользователей, в различных комбинациях.
Полностью страницы я не доделал. Я еще нахожусь в стадии обдумывания и подбора вариантов. Кучу времени потратил на поиски готового шаблона, и как итог, делаю самsmile
Все бы это протекало быстрее, если бы у меня не было врожденного отвращения к html и скриптам. Это в скором времени преодолею. 

Сделаю простенький как три копейки шаблон, чтобы мое обучение не стояло на месте. А по ходу дела, буду его дорабатывать.

Цитата(Stampede @  22.7.2007,  18:09 Найти цитируемый пост)
финт ушами

сделал.

Автор: Ulysses4j 22.7.2007, 19:09
Цитата(Stampede @  22.7.2007,  18:09 Найти цитируемый пост)
приходится делать финт ушами

А так не пойдет:
Код
<web-app ...>
  <!-- .... -->
  <welcome-file-list>
    <welcome-file>home.shtml</welcome-file>
    <!-- .... -->
  </welcome-file-list>
  <!-- .... -->
</web-app>

web.xml
?

Автор: Stampede 23.7.2007, 03:04
Цитата(Ulysses4j @  22.7.2007,  10:09 Найти цитируемый пост)
А так не пойдет:


Увы, нет. При обращении по УРЛу, который заканчивается на слэш (т. е. представляет директорию), контейнер таки пытается физически залезть в эту директорию и найти один из файлов, прописанных в welcome-file-list.

diablero: Предлагаю для простоты обойтись для начала шаблоном, в котором будут постоянные шапка, левое меню навигации и подвал. Чтобы сильно не страдать, можешь сверстать это таблицей.

И это, я надеюсь, твой HTML содержит только структурную разметку, а внешний вид ты задаешь через CSS.

Автор: Ulysses4j 23.7.2007, 13:36
Скажите, а i18n не будет? Очень бы хотелось посмотреть.

Автор: diablero 23.7.2007, 17:44
Цитата

Чтобы сильно не страдать, можешь сверстать это таблицей.

Так и сделал. 

Автор: diablero 23.7.2007, 18:46
И то это не главная страница. Нам нужно выделить в отдельный класс создание каркаса. Т.е. как бы статическое содержимое для всех страниц, кроме тех которые касаются учетной записи.
Т.е. два каркаса. И нужно еще создать таблицу в базе section

Автор: Maksym 23.7.2007, 19:30
diablero
Скажи пожалуйста, куда ты положил persistence.xml, так чтобы при инициализации из сервлет-контейнера после деплоймента EntityManagerFactory его нашла?
И, если несложно, всопроизведи структуру каталогов, которая сейчас получилась в проекте.. 

Автор: Stampede 23.7.2007, 19:33
Так, отлично! (на самом деле есть замечания, но пока ладно) Теперь внимание: сейчас мы резко упростим код и уберем повторяющиеся элементы в шаблонах. Для этого мы воспользуемся конструкцией Velocity #parse. Там, где у тебя стоит элемент <td> для основного контента, пишем:

Код

    <td id="content">#parse("$content")</td>


Теперь обязанность каждого воркера - положить в контекст Velocity имя соответствующего шаблона под ключом "content". Например, в ArticleWorker:

Код

context.put("content", "article.vm")


Соответственно, из article.vm убираем все лишнее и оставляем только то, что составляет информационный блок страницы.

Сам шаблон страницы (который ты запостил под именем home.vm), переименовываем в, скажем, template.vm - это будет наш единый шаблон для всех страниц сайта.

Также поменяем сигнатуру метода Template getTemplate() в интерфейсе Worker на вот такое:

Код

public String getTemplateName();


Поскольку, как мы только что решили, шаблон у нас будет один и тот же, есть смысл реализовать этот метод раз и навсегда для всех воркеров. Для этого создаем базовый абстрактный класс:

Код

public abstract class AbstractWorker implements Worker {
    public String getTemplateName() {
        return "template.vm";
    }
}


И тогда в остальных воркерах (наследуемых от AbstractWorker), нам останется только реализовать метод execute().

При этом работу по получению экземпляра Template по имени придется, естественно, перенести в метод service() сервлета-контроллера, но это как раз хорошо: нечего воркерам иметь дело с TemplateManager.

Все, мы уже почти готовы подвести четвертую жЫрную черту. Таблицу для секций пока не создавай. Определи простой бин Section, напиши SectionManager, а в его методе инит зашей прямо в коде создание нескольких экземпляров Section.

Да, вот еще: во всех бинах определи метод getUrl(), примерно такого содержания:

Код

public String getUrl() {
    return "/article.shtml?id=" + id;
}


После всего этого у тебя получится практически действующий сайт (правда, пока только в режиме просмотра).

Ulysses4j:

Цитата(Ulysses4j @  23.7.2007,  04:36 Найти цитируемый пост)
Скажите, а i18n не будет? Очень бы хотелось посмотреть.


Тут вот какое дело. Необходимость заморачиваться i18n в обычных, десктопных программах вызвана тем, что там все гуевые компоненты отрисовываются программно, и надо каким-то образом хранить строки для них в языко-зависимых хранилищах - например, в ресурсах. В вебном же приложении нетрудно сделать так, чтобы Java код вообще не содержал никаких текстовых констант, а вся текстовка содержалась в шаблонах. Ну а уж сделать так, чтобы в зависимости от профиля пользователя брались шаблоны на том или ином языке - это вопрос проще пареной репы.

Автор: diablero 23.7.2007, 19:53
to Maksym
Цитата(Maksym @  23.7.2007,  19:30 Найти цитируемый пост)
Скажи пожалуйста, куда ты положил persistence.xml

В META-INF, посмотри во вложении структуру

Автор: Maksym 23.7.2007, 20:06
diablero
Цитата(diablero @  23.7.2007,  19:53 Найти цитируемый пост)
В META-INF, посмотри во вложении структуру 

Спасибо, все понятно, все работает.

Автор: Stampede 23.7.2007, 20:17
Цитата(Maksym @  23.7.2007,  11:06 Найти цитируемый пост)
Спасибо, все понятно, все работает.


Maksym, а ты, выходит, тоже активно следишь за сериалом? Интересно было бы услышать твое мнение - с учетом предыдущего (if any) опыта веб разработки и в свете твоих вебных планов.

Автор: Maksym 23.7.2007, 20:24
Stampede
Когда ты говорил о http://forum.vingrad.ru/index.php?showtopic=124877&view=findpost&p=1197482 ты держал в уме некоторый механизм, который позволил бы реализовать его внутри нашего приложения? Если да, то опиши его, пожалуйста, в двух словах.

Добавлено @ 20:25
Цитата(Stampede @  23.7.2007,  20:17 Найти цитируемый пост)
Интересно было бы услышать твое мнение - с учетом предыдущего (if any) опыта веб разработки и в свете твоих вебных планов.

Слежу, можно сказать дышу diablero в затылок. Мнение пока формируется, как только созреет -- обязательно поделюсь.

Автор: Stampede 23.7.2007, 20:46
Цитата(Maksym @  23.7.2007,  11:24 Найти цитируемый пост)
Когда ты говорил о ЧПУ ты держал в уме некоторый механизм, который позволил бы реализовать его внутри нашего приложения? Если да, то опиши его, пожалуйста, в двух словах.


Там на самом деле все достаточно просто. Представим, как могли бы выглядеть некоторые УРЛы:
  • /articles/c++/, /articles/java/, /articles/python/ - разделы;
  • /articles/java/Using-Regular-Expressions-In-XML.shtml - статья;
  • /accounts/diabler.shtml - профиль юзера;
  • и т. д.

Нагрузка тут ложится на два места: 
  • В реализации метода getUrl() нужно закодировать, как по имени/заглавию сформировать соответствующий УРЛ. Можно в принципе делать это однократно, при создании объекта, и потом хранить как поле в базе.
  • В реализации диспетчера, который по УРЛу выдает класс воркера. Там нужно предусмотреть какие-то конфигурируемые маппинги, желательно в формате, который предусматривает wildcards. Например, /articles/*/ - для всех страниц-разделов.

В остальном логика останется практически без изменений.

Но это все можно сделать факультативно. А для целей нашего проекта принятая нами простая схема подходит как нельзя лучше.

Автор: Maksym 23.7.2007, 21:03
Stampede
Спасибо. Исходя из своего понимания того что ты сказал, только что попробовал на примитивном уровне, все красиво ложиться. Интересно, что с использованием, например, jsf подобный механизм так прозрачно не встроить.
Но мы еще не видели контроллера (буква C)...  smile  smile

Добавлено через 9 минут и 15 секунд
Цитата(Stampede @  23.7.2007,  20:46 Найти цитируемый пост)
/articles/c++/, /articles/java/, /articles/python/ - разделы;

Но для этого прийдется прописать такой мэпинг:
Код

    <servlet-mapping>
        <servlet-name>MainController</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

Как тогда быть с картинками, css-ами и прочими ресурсами, которые обычно отдаются напрямую.. тоже анализировать url..?

Автор: Stampede 23.7.2007, 21:46
Цитата(Maksym @  23.7.2007,  12:03 Найти цитируемый пост)
Как тогда быть с картинками, css-ами и прочими ресурсами, которые обычно отдаются напрямую.. тоже анализировать url..? 


Молодец, верно подметил.

Один из выходов - в web.xml явно замапить картинки, css, js и пр. статические ресурсы на дефолтный контроллер, например, по расширениям.

Другой выход - забить вообще на встроенные средства и отдавать всю статику самому. Правда, для этого придется конфигурировать content-type. Зато появляются плюсы, такие как возможность при желании логировать, в рамках единого подхода, запросы к статике, тонко управлять кэшированием на клиенте, гзиповать вывод, и пр.

Цитата(Maksym @  23.7.2007,  12:03 Найти цитируемый пост)
Но мы еще не видели контроллера (буква C)


Да в общем-то, можно сказать, уже и видели: класс сервлета-контроллера и метод getWorkerClass(String url).

Автор: diablero 23.7.2007, 21:51
Цитата(Stampede @  23.7.2007,  19:33 Найти цитируемый пост)
При этом работу по получению экземпляра Template по имени придется, естественно, перенести в метод service() сервлета-контроллера, но это как раз хорошо: нечего воркерам иметь дело с TemplateManager.

Вот тут мне не понятно. 
Наш алгорим действий:
1. В service() определяем имя шаблона, от воркера получаем контекст. И сливаем их вместе.
2. Полученный Template мы должны слить с template.vm.

Как выполнить второй пункт?

Автор: Stampede 23.7.2007, 22:08
Цитата(diablero @  23.7.2007,  12:51 Найти цитируемый пост)
2. Полученный Template мы должны слить с template.vm.

Как выполнить второй пункт?


Нет, там все проще, чем ты думаешь:

Код

public void service(...) throws ... {
    VelocityContext context = worker.execute(request, response);
    String templateName = worker.getTemplateName();
    String html = infinite.getTemplateManager().merge(templateName, context);
}


А воркер нам как раз и вернет "template.vm".

Автор: diablero 23.7.2007, 22:17
Не, я не это имел ввиду.
template.vm это наш базовый шаблон. Где есть такая строчка
Код

<td id="content">#parse("$content")</td>

Мы запрашиваем статью, в воркере статьи, мы заполняем контескт и сливаем его с post.vm.
Теперь нам нужно слить post.vm с template.vm. Вот я не пойму как это сделать

Автор: Stampede 23.7.2007, 22:27
Так а это делается не сливанием в смысле merge. Просто когда движок Velocity дойдет до строчки #parse("$content"), он полезет в контекст, найдет там переменную "content", и использует ее как имя шаблона, который нужно включить в данном месте.

Как я уже говорил ранее, это забота воркера - заранее положить в контекст имя нужного шаблона:

Код

public class ArticleWorker extends AbstractWorker {
    public VelocityContext execute(HttpServletRequest request, HtpServletResponse response) {
        VelocityContext context = new VelocityContext();
        context.put("content", "article.vm");
        // ...
    }
}

Автор: diablero 23.7.2007, 22:27
Вопрос снят. smile

Добавлено @ 22:28
А... Ту уже ответилsmile 
Сейчас все выложу



Релиз кандидат четвертой жЫрной черты

Автор: Stampede 23.7.2007, 22:48
Поздравляю!

Разбор полетов - завтра. А пока - всем отдыхать smile

Да и мне бы еще по работе чуток поработать smile

Автор: Stampede 24.7.2007, 16:26
Ну знач поехали.

1. Пора бы нам уже начать группировать классы по пакетам, а то куча уже большая получается. Напрмер, явно напрашиваются к выделению менеджеры, воркеры и data бины.

2. Есть несколько не совсем удачных имен, которые привносят путаницу.
  • Например, почему Configuration превратилось в Settings?
  • Или, например, интерфейс EngineManager. Я бы его вообще убрал, потому что он у нас недостаточно общий, поскольку имеет библиотечную зависимость, VelocityContext. Тут надо или вводить дополнительный слой абстракции для контекста, или отказываться от потуг сделать все в общем виде.
  • Имя MainController не отражает того факта, что это все-таки в первую очередь сервлет. ControllerServlet было бы лучше.
  • В web.xml для главного сервлета используется имя index.shtml. Это вводит в заблуждение, поскольку наводит на мысли о каких-то физических структурах.

3. Использование переменных в шаблоне.

Вот смотри, как у тебя сделан post.vm:

Код

<table>
<tr>
    <td>$user_name / $article_date  / <u>версия для печати</u></td>
</tr>
<tr>
    <td>$article_title</td>
</tr>
<tr>
    <td>$article_text</td>
</tr>
</table>



А ведь Velocity-то позволяет обращаться к полям бинов (имеющим соответствующие геттеры) напрямую! И получается, что нам всего-то нужно засунуть в контекст две переменные: user и article. Это резко сокращает вероятность ошибок типа недосмотров, опечаток и наложения имен. Шаблон будет выглядеть вот так:

Код

<table>
<tr>
    <td>$user.name / $article.date</td>
</tr>
<tr>
    <td>$article.title</td>
</tr>
<tr>
    <td>$article.text</td>
</tr>
</table>


Я убрал "версию для печати", потому что в наше время это делается гораздо проще, средствами CSS.

Ну и по-хорошему $user.name надо бы сделать ссылкой:

Код

<td><a href="$user.url">$user.name</a> / $article.date</td>


4. Ну и по мелочам:
  • В infinite.sql отсутствует часть для Section.
  • Код CSS надо все-таки как-то планировать, но это ладно, потом.
  • При выставлении contentType в методе service() кодировку лучше брать все-таки из конфига - мы ж его для этого и предусматривали.

Может, еще что замечу/вспомню. Предлагаю подкорректировать по своему усмотрению и перезалить.

Автор: diablero 25.7.2007, 20:30
Создал бин News. И что бы не было путанницы, везде теперь фигурирует Post, а не Article. Классы по пакетам сгруппировал, может быть имя не совсем удачное выбрал?
Интерфейс EngineManager убрал, не думаю что откажусь от Velocity.
Еще посмотрю и проанализирую...
Перезалил.

Теперь уже вырисовыватся вполне ясная картина, и структура сайта. Рано или поздно я бы дошел до этого, или бы забил на пол путиsmile Т.к. не встречал ни одной книги, где разъяснялась последовательность разработки, даже простого сайта. Как закончим, нужно будет это все оформить, но это все потом.

З.Ы. Попутно научился засыпать раньше чем закроются глазаsmile

Автор: Stampede 25.7.2007, 21:53
Цитата(diablero @  25.7.2007,  11:30 Найти цитируемый пост)
Теперь уже вырисовыватся вполне ясная картина, и структура сайта.


Да, и это заметно - например, по тому, как ты ловко добавил сущности Section и News, со всеми сопутствующими компонентами.

В целом структура кода уже почти окончательная. Единственное, мы немного изменим сигнатуру метода Worker.execute() и введем дополнительно несколько новых понятий - для пущего удобства. А сейчас приступаем к следующему этапу - аутентификации и авторизации доступа.

Как мы уже многократно убеждались, "книжные" подходы для нас, простых сайтостроителей, не годятся. Поэтому мы пойдем другим путем. А именно, возьмем на вооружение простую технику, веками используемую в мире ПХП - технику, основанную на куках. Для этого важно понимать, что такое куки, кем и когда они создаются, и как участвуют в коммуникации по HTTP. Чтобы иметь полную картину происходящего, рекомендуется скачать и установить http://www.getfirebug.com/. Тогда на закладке Net будет видно, с точностью до буквы, что поылал браузер, и что приходило в ответ.

Итак, первым делом нам нужно залогиниться. Для этого где-нибудь в шапке страницы пропиши форму для ввода с полями login, password и кнопкой "Войти", метод POST. Атрибут action пусть указывает на такой УРЛ:

/submit/login.do

Здесь .do оправданно, поскольку это будет у нас невизуальный ресурс. Соответственно, для него надо будет прописать маппинг в web.xml (на ControllerServlet) и в диспетчере воркеров (на LoginWorker.class).

В воркере надо прочитать параметры запроса login и password, пробить их через UserManager, и положить в VelocityContext объект типа User. ПДалее показать факт успешного логина (или ошибки) через вставной подшаблон confirmation.vm.

Пока этого будет достаточно.

Автор: diablero 25.7.2007, 23:17
Цитата(Stampede @  25.7.2007,  21:53 Найти цитируемый пост)
вставной подшаблон confirmation.vm

Как его воткнуть грамотно вв шапку страницы, подумаю завтра. А так проверил работоспособность, работает.
Код

public class LoginWorker extends AbstractWorker implements Worker{
    public VelocityContext execute(HttpServletRequest request, HttpServletResponse response) {
        VelocityContext context = new VelocityContext();
        String userName = request.getParameter("userName");
        String password = request.getParameter("password");
        Infinite infinite = Infinite.getInstance();
        UserManager manager = infinite.getUserManger();
        try {
            manager.login(userName,password);
            context.put("login_state", userName+" is login");
        } catch (Exception err) {
            context.put("login_state", err.getMessage());
        }
        context.put("content", "confirmation.vm");
        return context;
    }
}

template.vm
Код

...
<td align="left"><form action="/submit/login.do" method="post">User:&nbsp;<input type="Text" name="userName">&nbsp;Password: <input type="Password" name="password">&nbsp;<input type="Submit" value="Войти"></form></td>
...

web.xml
Код

<servlet-mapping>
        <servlet-name>ControllerServlet</servlet-name>
        <url-pattern>/submit/login.do</url-pattern>
    </servlet-mapping>


Автор: Stampede 26.7.2007, 01:02
Цитата(diablero @  25.7.2007,  11:30 Найти цитируемый пост)
Классы по пакетам сгруппировал, может быть имя не совсем удачное выбрал?


Ну, я бы точно по другому сделал. У тебя там бины данных, менеджеры и воркеры в одной куче оказались. Но дело твое.


Цитата(diablero @  25.7.2007,  14:17 Найти цитируемый пост)

<servlet-mapping>
        <servlet-name>ControllerServlet</servlet-name>
        <url-pattern>/submit/login.do</url-pattern>
</servlet-mapping>


У нас еще много будет невизуальных обработчиков всяких сабмитов, и все они будут проходить через тот же сервлет-контроллер, так что имеет смысл задать маппинг по расширению *.do.

С формой более-менее все в порядке, с обработчиком тоже. Так что перейдем к следующей задаче: запоминанию. Но сначала обещанный рефакторинг.

Речь идет о возвращаемом значении метода execute(). Он имеет тип VelocityContext. Это не совсем удобно, ибо это весьма общий тип. Коль скоро мы имеем дело с представлением вебной страницы, хорошо было бы, чтобы оно предоставляло нам прямой доступ к ряду существенных вебных штучек. Поэтому я предлагаю завести класс (назовем его Page), наследующий от VelocityContext, примерно такого содержания:

Код

public class Page extends VelocityContext {
    private String redirectUrl;

    public Page(HttpServletRequest request, HttpServletResponse response) {
        put("request", request);
        put("response", response);
        put("url", request.getRequestURI());
    }

    // а также геттер и сеттер для redirectUrl
}


Соответственным образом изменится и сигнатура метода execute(). И вот что это нам сразу дает: мы сейчас легко и просто реализуем паттерн PRG (Post-Redirect-Get).

Пару слов о том, что это за паттерн и для чего он нужен. Если в ответ на сабмит формы возвращать отображаемый текст, то это чревато многими путаницами: например, если юзер решит походить кнопками браузера Взад/Вперед, или нажмет рефреш. Чтобы избежать этих неприятностей, есть достаточно простое решение: по сабмиту всегда возвращать HTTP код статуса 302 (ресурс временно перемещен), заодно в заголовке ответа Location указывается адрес для переадресации. В Servlet API для этой цели есть удобное сокращение: response.sendRedirect(String url);

Так вот, я предлагаю для всех обработчиков сабмитов выставлять переменную page.redirectUrl, а в коде сервлета ее анализировать и соответственно что-то делать. Возникает вопрос: а почему не вызывать sendRedirect() непосредственно в коде воркера? А потому, что в этом случае ответ сразу уйдет браузеру, а нам бы хотелось поманипулировать заголовками ответа, в том числе кукисами.

Итак, в LoginWorker пишем примерно так:

Код

protected Page page;
public Page execute(HttpServletRequest request, HttpServletResponse response) {
    public static final AGE_NINETY_DAYS = 3600 * 24 * 90; // 90 days in seconds
    page = new Page(request, response);
    // ...
    User user = userManager.login(userName, pasword);
    if (user != null) {
        page.put("user", user);
        Cookie cookie = new Cookie("userName", user.getName());
        cookie.setPath("/");
        cookie.setMaxAge(AGE_NINETY_DAYS);
        response.addCookie(cookie);
        cookie = new Cookie("password", user.getPassword());
        cookie.setPath("/");
        cookie.setMaxAge(AGE_NINETY_DAYS);
        response.addCookie(cookie);
        page.setRedirectUrl("/confirmation.shtml");
    } else {
        // строка внизу закомментрована, поскольку она нам ничего не дает:
        // после редиректа от нее ничего не останется. На самом деле
        // такие вещи надо делать через сессию, но мы пока об этом не будем.
        // page.put("message", "Invalid user name or pasword.");

        // тут по-хорошему нужно на всякий случай сбросить кукисы userName и password
        // на клиенте, но мы щас не будем загромождать код
        page.setRedirectUrl("/login.shtml");
    }
}


Вообще надо сказать, работа с куками в Servlet API сделана очень неудачно - приходится делать много лишних телодвижений для реализации самых простых вещей. Выходом может быть написание собственных удобных функций. Например, мы могли бы в Page предусмотреть очень простой метод setLongLivedCookie(String name, String value), и то, что мы тут делали в четыре строки, делалось бы в одну. Аналогичным образом можно было бы написать метод resetCookie(String name). Впрочем, все это со времени мы и сделаем.

Не забываем, что в сервлете-контроллере мы должны проверить значение переменной redirectUrl:

Код

public void service(...) throws ... {
    // ...
    Page page = worker.execute(request, response);
    if (page.getRedirectUrl() != null) {
        response.sendRedirect(page.getRedirectUrl());
    } else {
        Writer writer = response.getWriter();
        // ...
    }
}


Теперь смотри, diablero: мы сейчас опять применим объектный подход и снова разом упростим структуру кода.

Юзера нам надо определять при каждом входящем запросе, потому что от этого зависит, что показывать, что не показывать, и показывать ли вообще. Чтобы не дублировать код чтения кукисов во всех воркерах, мы в базовом AbstractWorker можем написать такое:

Код

public abstract class AbstractWorker implements Worker {
    protected HttpServletRequest request;
    protected HttpServletResponse response;
    protected Page page;
    protected User user;
    // ...
    public Page execute(HttpServletRequest request, HttpServletResponse response) {
        this.request = request;
        this.response = response;
        page = new Page(request, response);

        Cookie[] cookies = request.getCookies();
        // тут мы каким-то образом получаем интересующие нас кукисы,
        // и если все совпадает, присваиваем значение переменной user,
        // которая будет видна в экземпляре производного воркера.
    }
}


Теперь, когда общая для всех воркеров часть работы закодирована, мы в производных классах пишем так:

Код

@Override
public Page execute(HttpServletRequest request, HttpServletResponse response) {
    super.execute(request, response);

    // а тут уже делаем все специфическое для данного воркера
}


Из предыдущего фрагмента особенно хорошо заметно, насколько убоги штатные средства работы с куками. Например, Cookie[] request.getCookies() еще нужно не забыть проверит на null, не говоря уж о выуживании кук по имени. Поэтому, diablero, будет хорошо, если ты реализуешь удобные методы работы с куками в классе Page.

После этого у тебя не должно возникнуть затруднений, чтобы сделать в шапке избирательное приветствие: либо "Привет, diablero! | Выход", либо "Имя: | Пароль: | Войти".

Автор: diablero 26.7.2007, 09:31
Цитата(Stampede @  26.7.2007,  01:02 Найти цитируемый пост)
Ну, я бы точно по другому сделал. У тебя там бины данных, менеджеры и воркеры в одной куче оказались.

А как бы ты сделал?

Автор: Maksym 26.7.2007, 13:16
Тем, кто, возможно, захочет пройти путем diablero:
  • Коротенькая статейка по JPА, все что нужно, чтобы начать работу и набросать простую базу  (Entitiy, One-to-One, One-to-Many, Many-to-Many Relationships): http://www.devx.com/Java/Article/33650/0/page/1, http://www.devx.com/Java/Article/33906/0/page/2.
  • Руководство по Apache'вскому OpenJPA,включает в себя обзор JPA стандарта, можно подсмотреть более тонкие вещи: http://openjpa.apache.org/docs/latest/manual/manual.html#jpa_overview_meta_onetomany
  • Пару слов про cookies http://ru.wikipedia.org/wiki/HTTP-Cookies. 
  • Все, что нужно знать про Velocity: http://velocity.apache.org/engine/releases/velocity-1.5/user-guide.html#references.

Автор: Stampede 26.7.2007, 16:13
Цитата(diablero @  26.7.2007,  00:31 Найти цитируемый пост)
Цитата(Stampede @  26.7.2007,  01:02 Найти цитируемый пост)
Ну, я бы точно по другому сделал. У тебя там бины данных, менеджеры и воркеры в одной куче оказались.

А как бы ты сделал? 



А ты угадай smile


Цитата(Maksym @  26.7.2007,  04:16 Найти цитируемый пост)
Тем, кто, возможно, захочет пройти путем diablero:


Maksym, спасибо за ссылки.

Автор: diablero 26.7.2007, 18:57
Цитата(Stampede @  26.7.2007,  16:13 Найти цитируемый пост)
А ты угадай

Думаю угадаюsmile
Мне осталось с Cookie разобраться. Завтра доделаю, а сейчас арбайтен по стахановски

Автор: Maksym 28.7.2007, 13:33
По поводу Cookies. Чтобы не писать лишнего, а сосредоточится на архитектуре, можно взять вот такой утилитный класс (спионерено http://www.nextapp.com/platform/echo1/echo/doc/api/1.0/private/server/nextapp/echoservlet/CookieManager.html):
Код

/**
 * A utility class used to cookies set by an application on the client browser.
 */
public class CookieManager 
implements Serializable {
    
    /**
     * Contains the cookies that were most recently sent in by the client's
     * most recent HTTP request.  If no cookies are present on the client,
     * clientCookies may be null.
     */
    private transient Cookie[] clientCookies = null;
    
    /**
     * Contains the Echo applications "working copy" of the cookies.  This
     * map references cookies by their names, and may contain cookies that the
     * Echo application has requested to be set but that have not yet been set
     * on the client.
     */
    private Map localCookieMap = null;

    /**
     * A set of cookies that have changed.  Cookies are stored in this set when
     * addCookie() is called, such that they will be updated in the HttpResponse
     * generated by the Connection.  Deleted cookies are also stored in this 
     * set, with there expiration set to zero, such that the client will delete
     * them.  The contents of this set are sent to the client at the next 
     * possible opportunity.
     */    
    private Set updatedCookies = new HashSet();

    /**
     * Adds a new cookie or updates an existing one.  In the case of updating an
     * existing cookie, be certain to reset its maximum age with setMaxAge() if
     * necessary.  (Cookies on the client browser do not report their maximum 
     * age to the server, only their value.)
     *
     * @param cookie The cookie to add or update.
     */
    public void add(Cookie cookie) {
        getLocalCookieMap().put(cookie.getName(), cookie);
        
        updatedCookies.add(cookie);
    }
    
    /**
     * Clears the locally held information about cookies.
     */
    private void clearLocalCookieMap() {
        localCookieMap = null;
    }
    
    /**
     * Returns a cookie with the given name if it is exists or null if it
     * does not.
     *
     * @param name The name of the cookie to return.
     * @return The cookie identified by <code>name</code>.
     */
    public Cookie get(String name) {
        return (Cookie) getLocalCookieMap().get(name);
    }

    /**
     * Creates a new local cookie set, copying the client cookies array into it.
     * This method is lazily called by other methods that need to work with the
     * local cookie map.
     *
     * @return A map containing the client's cookies.
     */
    private Map getLocalCookieMap() {
        if (localCookieMap == null) {
            localCookieMap = new TreeMap();

            if (clientCookies != null) {
                for (int index = 0; index < clientCookies.length; ++index) {
                    localCookieMap.put(clientCookies[index].getName(), clientCookies[index]);
                }
            }
        }
        
        return localCookieMap;
    }
    
    /**
     * Returns an iterator over all present cookies.
     *
     * @return An iterator over all present cookies.
     */
    public Iterator iterator() {
        return getLocalCookieMap().values().iterator();
    }
    
    /**
     * Removes the specified cookie.  This is accomplished by setting
     * the cookie on the client with a maximum age of 0.
     *
     * @param name The name of the cookie to remove.
     */
    public void remove(String name) {
        Cookie cookie = get(name);
        
        cookie.setMaxAge(0);
        
        if (localCookieMap != null) {
            localCookieMap.remove(name);
        }

        updatedCookies.add(cookie);
    }

    /**
     * Retrieves cookies from a client HTTP request and stores them in the 
     * CookieManager.  
     * When this method is called, all "working information" about cookies 
     * is destroyed.
     *
     * @param request An <code>HttpServletRequest</code> from which to 
     *        extract cookies.
     */
    void retrieveCookies(HttpServletRequest request) {
        clientCookies = request.getCookies();
        clearLocalCookieMap();
    }
    
    /** 
     * Returns the number of cookies present.  This is the number of cookies
     * that the Echo application is tracking at the current time.  Calls to the
     * add and remove methods will increase or decrease this number even though
     * the cookie has not yet been added or removed on the client.
     *
     * @return The number of cookies present.
     */
    public int size() {
        return getLocalCookieMap().size();
    }
    
    /**
     * Stores cookies on the client by adding them to the HTTP response.
     * When this method is called, all "working information" about cookies 
     * is destroyed.
     *
     * @param response An HttpServletResponse to feed the cookies to.
     */
    void storeCookies(HttpServletResponse response) {
        if (updatedCookies != null) {
            Iterator it = updatedCookies.iterator();

            while (it.hasNext()) {
                response.addCookie((Cookie) it.next());
                it.remove();
            }
        }
    }
}

Автор: diablero 28.7.2007, 21:10
Цитата(Stampede @  26.7.2007,  01:02 Найти цитируемый пост)
После этого у тебя не должно возникнуть затруднений, чтобы сделать в шапке избирательное приветствие

Всетаки возникло. Я чего-то никак не доганю как выход сделать.
Код

public class Page extends VelocityContext {
    private HttpServletRequest request;
    private HttpServletResponse response;
    private String redirectUrl;

    public static final int AGE_NINETY_DAYS = 3600 * 24 * 90;

    public Page(HttpServletRequest request, HttpServletResponse response) {
        this.request = request;
        this.response = response;
        put("request", request);
        put("response", response);
        put("url", request.getRequestURI());
    }
    public void setRedirectUrl(String redirectUrl) {
        this.redirectUrl = redirectUrl;
    }
    public String getRedirectUrl() {
        return redirectUrl;
    }
    public void setLongLivedCookie(String name, String value) {
        Cookie cookie = new Cookie(name, value);
        cookie.setPath("/");
        cookie.setMaxAge(AGE_NINETY_DAYS);
        response.addCookie(cookie);
    }
    public void resetCookie(String name){
        Cookie[] cookies = request.getCookies();
        if(cookies!=null) {
            int size = cookies.length;
            for(int i=0; i<size; i++){
                if(cookies[i].getName().equals(name)) {
                    cookies[i].setMaxAge(0);
                    response.addCookie(cookies[i]);
                    break;
                }
            }
        }
    }
    public String getCookieValue(String name){
        Cookie[] cookies = request.getCookies();
        if(cookies!=null) {
            int size = cookies.length;
            for(int i=0; i<size; i++) {
                if(cookies[i].getName().equals(name)) return cookies[i].getValue();
            }
        }
        return null;
    }
}

Код

public class LoginWorker extends AbstractWorker implements Worker {
    private String templateName;
    protected Page page;
    private Logger logger = Logger.getLogger(Infinite.class);
    public Page execute(HttpServletRequest request, HttpServletResponse response) {
        page = new Page(request, response);
        String url = request.getRequestURI();

        if(url.equals("/submit/login.do")) {
            logger.info("login");
            String userName = request.getParameter("userName");
            String password = request.getParameter("password");
            Infinite infinite = Infinite.getInstance();
            UserManager manager = infinite.getUserManger();
            User user;
            try {
                user = manager.login(userName, password);
                page.setLongLivedCookie("userName", user.getName());
                page.setLongLivedCookie("password", user.getPassword());
                templateName = "template.vm";
                page.setRedirectUrl("/home.shtml");
            } catch (Exception e) {
                page.put("flag", "login");
                templateName = "confirmation.vm";
            }
        }
        if(url.equals("/submit/unlogin.do")) {
            logger.info("unlogin");
            page.resetCookie("userName");
            page.resetCookie("password");
            page.setRedirectUrl("/home.shtml");
            templateName = "template.vm";
        }
        return page;
    }
    public String getTemplateName() {
        return templateName;
    }
}

Во втором if'е надо плюшки сбросить и перегрузить главную страницу. Если даже сбросить вручную, то это ничего не дает. Потому, что при редиректе на home.shtml ничего не происходит.

Код

public class HomeWorker extends AbstractWorker implements Worker {
    protected Page page;
    public Page execute(HttpServletRequest request, HttpServletResponse response) {
        super.execute(request, response);
        page = new Page(request, response);
        if(user!=null) {
            page.put("user", user);
            page.put("flag", "unlogin");
        }
        page.put("logincontent", "confirmation.vm");
        page.put("content", "home.vm");
        return page;
    }
}

Код

#if($flag=="login")
<html>
<head>
    <title>Login</title>
</head>

<body>
<center>
<br><br><br><h2>Login Page</h2><br>
Please enter your user name and password.<br><br>
#if($error==true)
<font color="#FF0000">Invalid user name or password</font><br>
#end
<form action="/submit/login.do" method="post">
<table>
    <tr>
        <td>User name: </td>
        <td><input type="Text" name="userName"></td>
    </tr>
    <tr>
        <td>Password: </td>
        <td><input type="Password" name="password"></td>
    </tr>
    <tr>
        <td align="right" colspan="2"><input type="Submit" value="Войти"></td>
    </tr>
</table>
</form>
</center>
</body>
</html>
#elseif($flag=="unlogin")
Привет, $user.name ( <a href="/submit/unlogin.do">Выход </a> )
#else
Привет, Гость ( <a href="/submit/login.do">Войти </a> )
#end

Автор: Stampede 29.7.2007, 01:24
Ага, посмотрю попозже. Щас только с пейнтбола, указательный палец все еще спусковой крючок пытается на мышке нащупать smile

Автор: diablero 29.7.2007, 11:48
Цитата(diablero @  28.7.2007,  21:10 Найти цитируемый пост)
при редиректе на home.shtml ничего не происходит

Я понял почему такая проблема. Кукисы не удалялись.
Все работает только при одном условии, если после того как "выйти", нажать обновить.

Автор: batigoal 29.7.2007, 12:04
OFF: смотрю на вас, и скриплю зубами от бессильной ярости. Тоже хочется параллельным курсом идти, но еще целый месяц не смогу с вами быть.

Автор: Stampede 30.7.2007, 19:27
Цитата(diablero @  29.7.2007,  02:48 Найти цитируемый пост)
Все работает только при одном условии, если после того как "выйти", нажать обновить.


Вот, вот это очень важный момент. Почему я и категорически настаиваю, чтобы по POST обязательно шел редирект именно на страницу подтверждения. Дело в том, что у разных браузеров - различная умолчальная политика кэширования страниц, и если мы хотим предсказуемого поведения, то должны явно управлять кэшированием на клиенте. Делается это выставлением заголовков ответа HTTP. Нам нужно, чтобы страница подтверждения никогда не кэшировалась. Для этого в соответствующем воркере (да-да, нам понадобится специальный воркер) нужно будет выставить такие заголовки:

Код

response.addHeader("Pragma", "no-cache");
response.addHeader("Cache-control", "no-cache, no-store");
response.addDateHeader("Expires", 0);


Страницу подтверждения следует сделать макимально простой, чтобы с нее никуда нельзя было перейти, кроме как на тот линк, который на ней будет указан. Сейчас для простоты пропиши там статический линк на главную страницу. Потом, когда научимся работать с сессией, сделаем как надо.

Для выхода лучше написать отдельный LogoutWorker, потому что логика там совсем другая, и не нужно мешатьь ее в одну кучу с логикой для логина.

Да, и вот еще. Метод Worker.getTemplateName() был введен нами в интерфейс Worker вынужденно, за неимением лучшего места. Но теперь, с введением класса Page, будет намного логичнее переместить этот метод имено туда.

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

В общем, потерпи - обещаю, просветление уже близко smile

batigoal:

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

Автор: Maksym 30.7.2007, 20:17
Цитата(Stampede @  30.7.2007,  19:27 Найти цитируемый пост)
Но зато ты сейчас работаешь фактически напрямую с HTTP, а не прячешься от него за ширмой туманных директив JSP

Я, наблюдая за сериалом, получаю огромное удовольствие именно от этого smile вопрос в том насколько удастся, не теряя этой супер гибкости, зафреймвочить все рутинные вещи.. уверен что все будет ок smile но интересно куда ты нас заведешь..
и насколько результирующая архитектура будет компонентной.. вот, например, захочу я внедрить в страницы элемент с ajax-поведением.. но, боюсь, я забегаю вперед..
Цитата(Stampede @  30.7.2007,  19:27 Найти цитируемый пост)
обещаю, просветление уже близко

 smile 

Автор: diablero 30.7.2007, 20:56
Цитата(Stampede @  30.7.2007,  19:27 Найти цитируемый пост)
 Для этого в соответствующем воркере (да-да, нам понадобится специальный воркер) нужно будет выставить такие заголовки:

Тут я не понял кудаsmile Вставил пока для проверки в HomeWorker, все тип-топ.
Цитата(Stampede @  30.7.2007,  19:27 Найти цитируемый пост)
Для выхода лучше написать отдельный LogoutWorker

Сделал.
Цитата(Stampede @  30.7.2007,  19:27 Найти цитируемый пост)
Да, и вот еще. Метод Worker.getTemplateName() был введен нами в интерфейс Worker вынужденно, за неимением лучшего места. Но теперь, с введением класса Page, будет намного логичнее переместить этот метод имено туда.

Сделал, добавил сеттер. Чтобы можно было подменять шаблон.
Цитата(Stampede @  30.7.2007,  19:27 Найти цитируемый пост)
В общем, потерпи - обещаю, просветление уже близко

Терпения у меня вагон и маленькая тележкаsmile

Добавлено через 7 минут и 36 секунд
to модераторам
Какая-то ерунда иногда происходит при загрузке страницы форума, она иногда грузиться, грузиться, закачивается примерно пол метра и все. Нажимаешь обновить и она грузиться нормально.  

Автор: Stampede 30.7.2007, 22:03
Цитата(diablero @  30.7.2007,  11:56 Найти цитируемый пост)
Цитата(Stampede @  30.7.2007,  19:27 Найти цитируемый пост)
 Для этого в соответствующем воркере (да-да, нам понадобится специальный воркер) нужно будет выставить такие заголовки:

Тут я не понял кудаsmile Вставил пока для проверки в HomeWorker, все тип-топ.


Знач объясняю. Там на самом деле все достаточно просто. У нас будет специальная страница, доступная по адресу /confirmation.shtml. Это не совсем обычная страница, так как она будет использоваться во всех сценариях обработки форм (то, что называется web flow). Основное ее назначение - свести к минимуму сюрпризы, связанные с особенностями вебного взаимодействия: причуды браузеров, причуды пользователей, перемещения по кнопкам взад-вперед, работа из нескольких окон/закладок, и пр.

Так вот, чтобы все это исключить (или хотя бы свести к минимуму), нужно сделать так, чтобы содержимое /confirmation.shtml не кэшировалось в браузере, а каждый раз запрашивалось с сервера. С этой целью мы заводим специальный ConfirmationWorker, который делает две вещи: переопределяет templateName на confirmation.vm, и выставляет заголовки управления кэшированием.

Пару слов о содержимом confirmation.vm. Понятно, что текст подтверждения будет зависеть от выполненного юзером действия. Например:
  • Вход: Здравствуйте, diablero! Рады видеть вас вновь на нашем сайте.
    Перейти на страницу профиля.
  • Выход: До свидания. Надеемся увидеть вас снова.
    Перейти на главную страницу.
  • Пост: Спасибо, diablero, ваша статья "Технико-тактические характеристики вооружения армии Тамерлана" успешно сохранена.
    Перейти на страницу статьи.

Но я повторяю, мы это сделаем чуть позже. А пока что пропишем статически:

Действие выполнено.
Перейти на главную страницу.

Далее, еще один момент. Я пытался сэкономить тебе один набор УРЛ-маппинг-воркер-шаблон, но сейчас вижу, что получилось в итоге хуже. Поэтому давай поправим, пока не поздно.

Идея в том, чтобы сделать форму для логина отдельной страницей, а не помещать ее в шапку основного шаблона. Предлагаю сделать так:

УРЛ: /form/login.shtml
Воркер: ru.selfexpression.workers.LoginFormWorker
Шаблон: templates/form/login.vm

Логики в воркере практически никакой не будет, кроме задания шаблона.

Действия по сабмиту (LoginWorker) остаются в основном без изменений, но в случае неуспешного логина выставляется redirectionUrl равный /form/login.shtml. То есть юзеру предлагается повторить ввод.

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


Цитата(Maksym @  30.7.2007,  11:17 Найти цитируемый пост)
вот, например, захочу я внедрить в страницы элемент с ajax-поведением.. но, боюсь, я забегаю вперед.


Будет, все будет. Но предупреждаю: разговор об Ajax предстоит очень долгий, и скорее всего потянет на отдельный сериал. Ибо пространства для маневра в этом деле даже еще больше, чем в традиционном вебе.

Автор: diablero 30.7.2007, 23:05
Цитата(Stampede @  30.7.2007,  22:03 Найти цитируемый пост)
Идея в том, чтобы сделать форму для логина отдельной страницей, а не помещать ее в шапку основного шаблона.

А у меня сделанно отдельной страницей.
Код

public class LoginWorker extends AbstractWorker implements Worker {
    protected Page page;

    public Page execute(HttpServletRequest request, HttpServletResponse response) {
        page = new Page(request, response);

        String userName = request.getParameter("userName");
        String password = request.getParameter("password");
        Infinite infinite = Infinite.getInstance();
        UserManager manager = infinite.getUserManger();
        User user;
        try {
            user = manager.login(userName, password);
            page.setLongLivedCookie("userName", user.getName());
            page.setLongLivedCookie("password", user.getPassword());
            page.setTemplateName("template.vm");
            page.setRedirectUrl("/home.shtml");
        } catch (Exception e) {
            page.put("flag", "login");
            page.put("error", "true");
            page.setTemplateName("login.vm");
        }
        return page;
    }
}

Мы же договаривались:
Цитата(Stampede)

чтобы сделать в шапке избирательное приветствие: либо "Привет, diablero! | Выход", либо "Имя: | Пароль: | Войти".

Поэтому в HomeWorker'е:
Код

        super.execute(request, response);
        page = new Page(request, response);
        if (user != null) {
            page.put("user", user);
            page.put("flag", "unlogin");
        }
        page.put("logincontent", "confirmation.vm");
        page.put("content", "home.vm");
        return page;


Полный текст на предыдущей странице. В связи с последними изменениями confirmation.vm теперь у меня login.vm

Цитата(Stampede @  30.7.2007,  22:03 Найти цитируемый пост)
Так вот, чтобы все это исключить (или хотя бы свести к минимуму), нужно сделать так, чтобы содержимое /confirmation.shtml не кэшировалось в браузере, а каждый раз запрашивалось с сервера. С этой целью мы заводим специальный ConfirmationWorker, который делает две вещи: переопределяет templateName на confirmation.vm, и выставляет заголовки управления кэшированием.

Т.е. после того как юзер ввел имя и пароль или вышел его отправляем к ConfirmationWorker. Но это дело не меняет, на главной странице нужно жмакать обновить.

Автор: Stampede 31.7.2007, 00:32
Цитата(diablero @  30.7.2007,  14:05 Найти цитируемый пост)
Т.е. после того как юзер ввел имя и пароль или вышел его отправляем к ConfirmationWorker. Но это дело не меняет, на главной странице нужно жмакать обновить.


Посмотрел сейчас, как у меня сделано в разных проектах. Тут дело в том, что смысл разных заголовков, http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9, остается довольно туманным. Поэтому многие вещи приходится постигать методом проб и ошибок, а также путем долгого лазания в инете. Так вот, я пытался найти такую комбинацию заголовков, чтобы обычные страницы всегда брались с сервера при обращении к ним по адресу, но при навигации кнопками вперед-назад - брались таки из кэша.

В результате множества экспериментов с разными браузерами получилось так, что искомое поведение реализует такой заголовок:

Cache-Control: no-cache

Соответственно, предлагаю в методе ControllerServlet.service(), непосредственно перед началом вывода текста HTML добавить:

Код

response.addHeader("Cache-Control", "no-cache");


А в ConfirmationWorker исправить на:

Код

response.addHeader("Pragma", "no-cache");
response.addHeader("Cache-Сontrol", "no-store");
response.addDateHeader("Expires", 0);


Таким образом у нас все без исключения страницы будут динамическими, но страница подтверждения кроме того будет перезапрашиваться при навигации кнопками вперед-назад.

LoginWorker, как ты его привел в последнем посте, сделан не по заданию. Я ведь говорил, что это невизуальный ресурс: по нему не должно возвращаться никакого ХТМЛа, а должен только идти редирект: или на страницу подтверждения, или обратно на форму логина. Перечитай пожалуйста внимательно мой предыдущий пост.

Автор: diablero 31.7.2007, 15:58
Цитата(Stampede @  31.7.2007,  00:32 Найти цитируемый пост)
В результате множества экспериментов с разными браузерами получилось так, что искомое поведение реализует такой заголовок:
Cache-Control: no-cache


Сделал как ты предложил, IE адекватно и правильно все обрабатывает, в отличии Opera'ы 8.5. Если открывать оперой, то как и раньше нужно при выходе обновлять главную страницу.

LoginWorker сделал по заданию, как оказалось, так код логичнее и правильнееsmile

Автор: Stampede 31.7.2007, 16:41
Цитата(diablero @  31.7.2007,  06:58 Найти цитируемый пост)
IE адекватно и правильно все обрабатывает, в отличии Opera'ы 8.5.


А попробуй, в порядке эксперимента, заменить Cache-Control: no-cache на Pragma: no-cache. Есть у меня одно подозрение.

Как только с этим разберемся, добавим еще немного косметики и будем подводить пятую жЫрную черту.

Добавлено через 3 минуты и 23 секунды
Так, минуточку, а ты в Firefox'е тестируешь? Firebug поставил?

Если нет, то надо обязательно. Все будешь видеть как на ладони.

Автор: diablero 31.7.2007, 19:14
Цитата(Stampede @  31.7.2007,  16:41 Найти цитируемый пост)
А попробуй, в порядке эксперимента, заменить Cache-Control: no-cache на Pragma: no-cache. Есть у меня одно подозрение.

Нет, так не работает, даже IE.
Цитата(Stampede @  31.7.2007,  16:41 Найти цитируемый пост)
Так, минуточку, а ты в Firefox'е тестируешь?

да
Цитата(Stampede @  31.7.2007,  16:41 Найти цитируемый пост)
Firebug поставил?

нет

Автор: Stampede 31.7.2007, 19:53
Цитата(diablero @  31.7.2007,  10:14 Найти цитируемый пост)
Firebug поставил?

нет


Ну так поставь. Делов-то - один гугл-сёрч и один тык мышкой.

Заодно проверишь, как выглядит ответ сервера, и в частности, меня интересует заголовок Date. Он вообще присутствует? Если нет, попробуй выставить его вручную (например, в самом начале метода service()).

Автор: Stampede 31.7.2007, 21:30
Вот, кажЫсь нашел:

Цитата

Well, I guess I'll update it a bit, then. Here's how Opera works, as far as I could understand the implementor:

1) Cache directives in META elements are ignored
2) The Cache-Control directives have the following meanings:
2a) if there's no "Cache-Control" header, cache the page to disk and use internal heuristics:
2a1) if it's a query URL (contains a "?"), treat as 'Cache-Control: no-cache', otherwise
2ab) use user settings
2b) no-cache -> revalidate, expires according to user settings
2b1) private, no-cache -> expire immediately
2c) no-store -> cache to RAM only (no effect on revalidation), expires according to user settings or when closing the program
2d) max-age -> expire page after set age
2e) must-revalidate -> treated as if no "Cache-Control" header was sent*
2f) everything else -> treated as if no "Cache-Control" header was sent

Источник: http://lists.drupal.org/archives/development/2005-04/msg00604.html



Так что попробуй вот это: Cache-Control: private, no-cache

Но убедись, что страница не перезагружается по кнопкам вперед/назад. Если перезагружается, будем искать другие комбинации.

ЗЫ. Терпеть не могу оставлять взведенные грабли за спиной. Никогда не знаешь, когда рванут smile

Update:

Вот еще похоже в чем дело. Досмотрел до конца статью по ссылке: там чувак упоминает заголовок Last-Modified. Ну точно! Я щас глянул на свой сайт из Оперы: он возвращает Cache-Control: no-cache. НО! У меня кроме того выставляется также Last-Modified: <текущее время>!

Короче, попробуй в разных вариантах и под разными браузерами. Как чего хорошего получится - пиши.

Автор: diablero 1.8.2007, 01:07
Не помню уже откуда взял, но кто-то писал что даже такая комбинация из под оперы не работает.
Код

response.addHeader("Date", "\""+ java.util.Calendar.getInstance().getTime().getTime()+"\"");
response.addHeader("Last-Modified", "\""+ java.util.Calendar.getInstance().getTime().getTime()+"\"");
response.setIntHeader("max-age", 0); //IE only;
response.addHeader("Cache-Control","no-store");
response.addHeader("Cache-Control","must-revalidate");
response.addHeader("Pragma", "no-cache"); //HTTP 1.0
response.addHeader ("Expires", "0"); //prevents caching at the proxy server
response.addHeader("cache-Control", "private"); //IE5.x only;

Я перебрал уже кучу вариантов. Пока все дохло.
Цитата(Stampede @  31.7.2007,  21:30 Найти цитируемый пост)
 Я щас глянул на свой сайт из Оперы: он возвращает Cache-Control: no-cache. НО! У меня кроме того выставляется также Last-Modified: <текущее время>!

Может еще что есть?


Днем на свежую голову продолжу...

Автор: diablero 1.8.2007, 22:31
День и свежая голова результатов не дали. Нашел кучу тем с аналогичной проблемой. И те решения которые в них предлагали у меня не работают. 

Автор: Stampede 2.8.2007, 00:13
Я сейчас тоже поэкспериментировал с разными вариантами, выявил туеву хучу новых нюансов по сравнению с временами, когда делал предыдущие проекты.

Короче, предлагаю такое окончательное решение. Для всех страниц:

Код

public void service(...) throws ... {
    // ...
    Page page = worker.execute(request, response);
    if (page.getRedirectUrl() != null) {
        response.sendRedirect(page.getRedirectUrl());
    } else {
        response.addHeader("Cache-Control","private, no-cache");
        response.setDateHeader("Last-Modified", page.getStartDate().getTime()); // просто для порядка
        Writer writer = response.getWriter();
        // ...
    }
}


В воркере ConfirmationWorker дополнительно:

Код

response.addHeader("Cache-Control","no-store"); // for FF
response.setDateHeader("Expires", 0); // просто на всякий случай


У меня в таком варианте получается так:
  • Firefox 2.0.0.5 - работает в точности как задумывалось:
    • при переходе по ссылке или по вводу адреса всегда забирает страницу с сервера;
    • по кнопкам взад-вперед отображает страницы из кэша;
    • страницу подтверждения всегда забирает с сервера:
     
  • Internet Explorer 7 (ох и тупорылый же интерфейс у него) - слишком близко к сердцу воспринимает директиву Cache-Control: no-cache. Ее одной достаточно, чтобы браузер каждый раз брал страницу с сервера, включая навигацию по кнопкам вперед-назад. Побороть не удалось, в сети тоже ничего не нарыл.

  • Opera 9.10 - обычные страницы отрабатывает как положено, но страницу подтверждения при навигации по кнопкам вперед-назад категорически берет из кэша. Гуголь также ничем не помог.
Как видим, универсального решения для всех браузеров найти пока не удалось. И ведь это только три браузера в трех отдельных версиях! А ведь есть еще браузеры постарее. И версии для других платформ. И просто другие браузеры. А сколько их еще будет в будущем?

Короче, вердикт: ну их всех нахер. Мы не собираемся гробить еще кучу времени на поиск идеального решения, которого, по всему выдать, все равно не существует. Будем ориентироваться на Firefox, как на наиболее стандартопослушный браузер. А кто не спрятался - мы не виноваты. На всех все равно не угодить.

В том виде, в котором я привел, работать худо-бедно будет во всех трех испробованных. Вот и ладушки.

Возражений нет? Тогда прошу зип в студию.

Автор: diablero 2.8.2007, 14:20
А у меня получаеться так:
Firefox 1.0 RC1 (такой вот древний) работает как и опера. Все берут из кэша.
IE 6.0 работает также как и твой 7.0

Я вообще всегда думал, что по части веба, все равняются на IE... Как самый массовый и правильный по отображению кода браузер.

Автор: batigoal 2.8.2007, 15:11
Цитата(diablero @  2.8.2007,  15:20 Найти цитируемый пост)
Я вообще всегда думал, что по части веба, все равняются на IE... Как самый массовый и правильный по отображению кода браузер.

В толпе разработчиков такая фраза вызывает бурный смех smile
Насчет массовости - возможно, но вот в следовании стандартам его упрекнуть сложно. Зачастую ребята из MS пытаются создать свои, а не поддерживать существующие.

Автор: diablero 2.8.2007, 18:59
Цитата(batigoal @  2.8.2007,  15:11 Найти цитируемый пост)
В толпе разработчиков такая фраза вызывает бурный смех

А какой браузер не вызывает смех?
Опера, которая отображает простой html код не так как положенно. Я конечно уважаю этот браузер, но косяков за ним достаточно.

Автор: Stampede 2.8.2007, 19:16
Цитата(batigoal @  2.8.2007,  06:11 Найти цитируемый пост)
Цитата(diablero @  2.8.2007,  15:20 Найти цитируемый пост)
Я вообще всегда думал, что по части веба, все равняются на IE... Как самый массовый и правильный по отображению кода браузер.

В толпе разработчиков такая фраза вызывает бурный смех smile


Тут на самом деле вот какое дело. IE  - это один из самых глючных браузерв, в плане следования стандартам. НО! - так уж сложилось - до сих пор самый массовый. Потому что рядовым пользователям, как правило, нет никакого дела до всяких там стандартов: что пришло с виндой, то и ладно. Процент посетителей, использующих IE, по разным прикидкам (и для разных целевых аудиторий) составляет 75-90%. Хотя надо сказать, с появлением Firefox эта цифра маленько ушла вниз, но все равно не до такой степени, чтобы серьезно подвинуть позиции Мелкософтного браузера.

Что остается разработчикам в такой ситуации? Правильно, "мыши плакали, кололись, но продолжали есть кактусы". То есть при любых раскладах необходимо кровь из носу обеспечить, чтобы в IE работало, как задумывалось. Но тут есть два принипиально разных подхода.
  • Можно сразу верстать в расчете на IE, а потом смотреть, как это будет выглядеть в других браузерах.
  • А можно - стараться делать по теории, по стандартам. И уже для IE искать и находить всякие хаки, чтобы заставить его работать как должно.

Так вот, второй подход - он идеологически более правильный. Потому что идет от общемировых, так сказать, принципов, закрепленных в спецификациях. [голосом пьяного Мягкова] "Это документ, между прочим" smile

Но ладно, это хоть и методологическая, но в общем-то лирика. Теперь по заданию. 

1. Ты в коде воркеров везде объявляешь и инициализируешь классовую переменную page. А ведь она у нас специально объявлена как protected в AbstractWorker, и там же и инициализируется. А ты ее, получается, перебиваешь. А что если у нас со временем добавятся дополнительные действия? Например, я хотел тебе предложить добавить в Page такой метод:

Код

public void setUser(User user) {
    this.user = user;
    if (user != null) {
        put("user", user);
    } else {
        remove("user");
    }
}


И чтобы этот метод вызывался при аутентификации по кукисам, то есть в общей для всех воркеров части кода, AbstractWorker.execute(). А иначе у тебя получается, что Velocity-переменная $user попадает в контекст только в HomeWorker . Соответственно, из-за этого на других страницах приветствие отображается неправильно (кстати, ты там зря вводишь дополнительно $flag="login", достаточно самой переменной $user).

2. Я в предыдущем посте намеком предложил тебе завести в Page переменную startDate. Ее смысл в том, чтобы использовать единое значение времени во всех местах, где оно понадобится (а оно таки еще понадобится многократно). Это предотвратит бессистемное использование несогласованых дат и сэкономит немножко времени на создание множественных new Date(). Инициализировать startDate, понятное дело, лучше в конструкторе Page. Заодно ее же можно положить в контекст, чтобы обеспечить доступ к ней из шаблонов.

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

Но сначала нужно внести коррективы по списку (особенно п. 1) и перезалить.

Автор: batigoal 2.8.2007, 21:14
Цитата(diablero @  2.8.2007,  19:59 Найти цитируемый пост)
А какой браузер не вызывает смех?

Обычно - FF.

Автор: diablero 2.8.2007, 23:30
Изменил и перезалил
З.Ы. Из-за всяких халявщиков, отпускников, работаю сутки через сутки. Так что пока, времени мало...

Автор: Stampede 3.8.2007, 00:11
Нет, все равно немного не так. У тебя после исправления:

Код

public class HomeWorker extends AbstractWorker implements Worker {
    protected Page page;

    public Page execute(HttpServletRequest request, HttpServletResponse response) {
        page = super.execute(request, response);
        page.put("content", "home.vm");
        return page;
    }
}



А надо бы:

Код

public class HomeWorker extends AbstractWorker implements Worker {
    public Page execute(HttpServletRequest request, HttpServletResponse response) {
        super.execute(request, response);
        page.put("content", "home.vm");
        return page;
    }
}


Но да ладно, не бери в голову. У меня созрела мысля, как все это дело оформить в менее запутанном виде. Но это все завтра. Щас - отдыхать smile

Автор: diablero 3.8.2007, 00:23
Вопрос на засыпку.
А нашу задачу, по обновлению страниц, можно решить с помощью javascript?
Может кто подкинет решение?

Автор: Stampede 3.8.2007, 00:51
Цитата(diablero @  2.8.2007,  15:23 Найти цитируемый пост)
А нашу задачу, по обновлению страниц, можно решить с помощью javascript?


Нет, нельзя. Даже если ты по клику подменишь переход по ссылке на код типа window.location = '/', браузер в своем решении о том, откуда брать контент, все равно будет руководствоваться своей внутренней политикой кэширования. По этой причине, например, у многих начинающих Аяксоидов возникают непонятки с подгружаемым динамическим контентом. Приходится в итоге все равно решать через заголовки.


Автор: diablero 3.8.2007, 13:30
Вот нашел небольшой туториал http://www.javaworld.com/javaworld/jw-10-2006/jw-1006-logout.html?page=3.

Там все через сесии, используется фильтр, и с logout у них все нормально. Я попробывал использовать фильтр, но он у меня не заработал. Посмотри, может мы найдем решение...

Автор: Stampede 3.8.2007, 19:15
diablero, я должен развеять перед тобой одно заблуждение. Ты полагаешь, что статьи в Java World публикуют какие-то гуры и небожители. Спешу тебя разочаровать: нет, это подчас обычные девелОперы, со своейственными каждому человеку ограничениями и заблуждениями.

Возьмем, к примеру, статью товарища Хуанг Ли, ссылку на которую ты приводишь. В чем суть его, тыкскыть, решения? Да все в той же идиоме PRG (Post-Redirect-Get)! Просто чувак, похоже, не знал такого слова, а то бы постеснялся с умным видом расписывать на трех страницах такие элементарные и всем известные вещи. Обрати внимание, что саму по себе идею PRG я тебе растолковал http://forum.vingrad.ru/forum/topic-124877/225.html#, Реализация этой идеи у нас тоже получилась простая и логичная:

/form/login.shtml -> /submit/login.do -> /confirmation.shtml

То, что Опера и старый Firefox не понимают каких-то заголовков - это, конечно, плохо, но видишь ли, от того, что мы закодируем ту же самую PRG логику в виде кривых и запутанных рецептов от товарища Хуанг Ли, понимаемость браузерами заголовков, увы, не улучшится. Чтобы тебе это наглядно продемонстрировать, я предлагаю тебе зайти по адресу, где у сего аффтара находится онлайновая демонстрация описанного решения для Logout: http://pragmaticobjects.org/properLogoutDemo/.

Как нетрудно убедиться, чуда не произошло. Если Опера не понимает директиву Cache-Control: no-store, то никакие Хуанг Ли тут не помогут.

Чтобы тебя окончательно стало ясно, что тут и к чему, открою еще один маленький секрет. Я эту статью уже видел раньше. Более того, я видел и еще более раннюю статью этого же аффтара, на эту же тему. Вот она: http://www.javaworld.com/javaworld/jw-09-2004/jw-0927-logout.html. Так вот, в этой первой статье он вообще пытался по логину перейти сразу на авторизованный ресурс через директиву jsp:forward, то есть посредством внутрисерверной переадресации, а не через ответ 302. Потом его ткнули носом, что это типа не работает, и тогда он выступил со второй статьей. Которую я в общем уже прокомментировал.

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

Код

   protected final void setNoCache(HttpServletResponse response) {
      response.setHeader("Cache-Control","no-cache"); 
      response.setHeader("Cache-Control","no-store");
      response.setDateHeader("Expires", 0);
      response.setHeader("Pragma","no-cache");
   }


Заметили ошибку? Правильно, setHeader("Cache-Control","no-store") забивает значение, выставленное в предыдущей строчке! Если он хотел его добавить, то должен был использовать addHeader("Cache-Control","no-store"). Тогда с сервера приехало бы Cache-Control: no-cache, no-store.

Вот такие вот у нас писатели.

Короче, все: тема закрыта.

Возвращаясь к заданию. Нам предстоить нарисовать несколько дополнительных страниц, в том числе форм. Если ты будешь делать это в том же ключе, как у тебя сделана form/login.vm, то мы очень скоро перестанем понимать, что там наворочено в разметке. Отсюда подготовительный этап:
  • Вынеси весь CSS в отдельный подключаемый файл.
  • Определи единый валидный семантичный HTML шаблон для всех страниц (template.vm) так, чтобы в нем присутствовали статичные шапка и подвал и место для меню навигации и основного контента. Не забудь про объявление DOCTYPE.
  • На базе этого шаблона нарисуй страницу поста. Текст для поста возьми подлиннее, чтобы было похоже на статью.

Лучше все это сделать в статике, то есть прям завел на диске файл template.htm, и пишешь в нем. Потом открываешь браузером с диска и смотришь, что получилось. Файл style.css можешь положить в эту же директорию.

Верстать надо семантоично, чтобы код отражал только структуру документа, но не его оформление. То есть чтобы никаких bgcolor, font и пр. Чтобы было за что зацепиться CSS'ом, используй атрибуты id и class.

Если будут вопросы - не стесняйся.

Автор: diablero 5.8.2007, 01:46
Подготовительный этап выполнил...
Выложил шаблоны.

Автор: Stampede 6.8.2007, 09:47
diablero, я посмотрел разметку. Не идеально, конечно, но для начала пойдет.

Я сейчас пока маленько занят, давай отложим до вторника. Между делом можешь нарисовать форму для создания/редактирования поста. Старайся придерживаться тех же правил семантичности.

Да, пока не забыл. У тебя встречаются элементы с одинаковым id. Это впоследствии может послужить источником больших непоняток. Для справки: id назначаются только тем элементам, про которые точно известно, что они всегда будут в единственном экземпляре. Например, id="navigation", id="content", id="footer", и т. д. Для повторяющихся (в пределах одной страницы) элементов одного типа используется понятие класса: например, class="post-title" на странице раздела (для списка постов).

Как запостишь форму, пойдем дальше.

Автор: diablero 6.8.2007, 15:05
Немного переделал и перезалил. Пока шаблоны будут в таком стиле.

У меня пока катострафически не хватает времени, чтобы во всем разобраться и сделать все основательно. 
Но я все обязательно переделаю. 
Во вторник вечером сделаю страницу для регистрации пользователя. Потом уже для добавления и редактирования постов. Надо найти скриптовый редактор, я вот когда-то давно видел чуть ли не ворд. 
Я думаю что нужно добавить еще один бин, это подраздел.

Еще несколько вопросов:
1. ты уже говорил о вариантах передачи css и прочих ресурсов, так как мы будем делать?
2. знаешь ли ты бесплатный хостинг, чтобы туда влез наш проект?
3. какой ты можешь порекомендовать из платных?

Автор: diablero 7.8.2007, 22:04
Сделал страницу регистрации, она конечно еще требует доработок и проверок корректности данных. Но продолжать уже можно. Этим я займусь походу.

Автор: Stampede 7.8.2007, 22:44
Посмотрел форму регистрации. Замечания:
  • Черный текст на болотном фоне почти не видно.
  • Имя инпута для Пароль и Повторите пароль одинаковое, password.
  • Значение value для Пол одинаковое, male.
  • Календарь просто выкинь нах! Тем более такое страшный и глючный. И вообще, с точки зрения юзабилити вводить дату рождения через такой календарь крайне неудобно. Сделай лучше обычным текстовым полем и предлоим маску ввода дд/мм/гггг.

    И вообще по поводу Javascript: давай пока не будем его трогать. Штука мощная, но надо пользоваться ею с умом. А от ламерских скриптов вообще держаться подальше smile
  • Для подписей к радиокнопкам используй тэг <label for="radiobitton-id">Мужской</label>. Тогда подписи будут тыкабельными.

По вопросам:

Цитата(diablero @  6.8.2007,  06:05 Найти цитируемый пост)
1. ты уже говорил о вариантах передачи css и прочих ресурсов, так как мы будем делать?
2. знаешь ли ты бесплатный хостинг, чтобы туда влез наш проект?
3. какой ты можешь порекомендовать из платных?


Первый вопрос имеется в виду, как выдавать статику? Да обычно, дефолтным сервлетом. Просто ты сгоряча прописал у себя в web xml маппинг "/" на ControllerServlet, а надо бы ло только по расширениям '.shtml и *.do. Как только исправишь, Томкат начнет выдавать картинки и прочую статику.

Бесплатных хостингов не знаю. Сам использую платный 4java.ca.

Завтра начнем делать обработчик форм.

Автор: Stampede 8.8.2007, 18:56
Так, ладно, давай делать обработчик форм.

Снечала предварительные замечания. Если бы мы делали мощное веб приложение с развернутой функциональностью, мы бы наваяли соответствующий фреймворк, обеспечивающий автоматическую валидацию: через рефлексию, по аннотациям, возможно с применением Javascript, и прочими штучками. Но поскольку форм у нас будет мало - пальцев одной руки хватит, чтоб посчитать - то мы всем этим заморачиваться не будем. Просто для каждой формы будет свой обработчик по типу LoginWorker, в котором и будет зашита логика валидации.

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

УРЛ: /form/registration.shtml
Воркер: RegistrationFormWorker
Шаблон: forms/profile.vm

    | (по сабмиту)
    V

УРЛ: /submit/register.do
Воркер: RegisterWorker    --> (если ошибка, то возврат на стр. формы)

    | (если все ОК, редирект на стр. подтверждения)
    V

УРЛ: /confirmation.shtml
Воркер: ConfirmationWorker
Шаблон: confirmation.vm


RegistrationFormWorker пока будет без логики (только положи в контекст $content = "forms/profile.vm").

В RegisterWorker проверь, чтобы все обязательные поля были на месте, чтобы пароль в обоих полях был одинаковым, и чтобы юзера с таким именем в базе не было. Если что-то из этого не выполняется, футболь обратно: page.setRedirectionUrl("/form/registration.shtml").

Потом создай нового юзера, пропиши для него куки по аналогии с LoginWorker, и отсылай на страницу подтверждения.

Если есть вопросы - не стесняйся.

Автор: diablero 9.8.2007, 01:12
Сделал, работает до того момента, как заливать юзера в базу.
Код

java.lang.IllegalStateException: Transaction not active
    org.hibernate.ejb.TransactionImpl.rollback(TransactionImpl.java:82)
    ru.selfexpression.ControllerServlet.service(ControllerServlet.java:68)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:802)

Код

public class RegistrationFormWorker extends AbstractWorker implements Worker {
    public Page execute(HttpServletRequest request, HttpServletResponse response) {
        super.execute(request, response);
        page.put("content", "form/profile.vm");
        return page;
    }
}

Код

public class RegistrationWorker extends AbstractWorker implements Worker {
    public Page execute(HttpServletRequest request, HttpServletResponse response) {
        super.execute(request, response);
        String userName = request.getParameter("userName");
        String password = request.getParameter("password");
        String repassword = request.getParameter("repassword");
        Infinite infinite = Infinite.getInstance();
        UserManager manager = infinite.getUserManger();
        EntityTransaction rtx = null;
        try {
            if(manager.containsUser(userName)) throw new Exception();
            if(!password.equals(repassword)) throw new Exception();

            User user = new User();
            user.setName(userName);
            user.setPassword(password);

            EntityManager entityManager = infinite.getEntityManager();
            rtx = entityManager.getTransaction();
            rtx.begin();
            entityManager.merge(user);
            rtx.commit();

            page.setLongLivedCookie("userName", user.getName());
            page.setLongLivedCookie("password", user.getPassword());
            page.setRedirectUrl("/confirmation.shtml");
        } catch (Exception err) {
            if (rtx != null) {
                rtx.rollback();
            }
            page.setRedirectUrl("/form/registration.shtml");
        }
        return page;
    }
}

Думается плохо, продолжаю разбиратьсяsmile

Автор: Shaggie 9.8.2007, 09:05
diableroStampede, один момент. В коде класса TemplateManager метод mergeTemplate из класса org.apache.velocity.app.VelocityContext вызывается таким способом:
Код

engine.mergeTemplate(templateName, context, sw);

Этот метод deprecated, и несмотря на то, что он хорошо работает под Tomcat, Jetty отчаянно рушил мне все русские слова в шаблонах, что стоило немалой головной боли. Заранее неизвестно, где выплывут новые грабли, поэтому рекомендую заменить его на более современный вариант:
Цитата(Javadoc to Velocity1.5)
public boolean mergeTemplate(String templateName, String encoding, Context context, Writer writer)

То есть:
Код

Infinite infinite = Infinite.getInstance();
engine.mergeTemplate(templateName, infinite.getConfiguration().getfileEncoding(), context, sw);

Автор: diablero 9.8.2007, 13:48
Shaggie посмотрел доки к пакету, и не увидел что метод этот deprecated.
Посмотри исходники, и с кодировками все прозрачно будет

Автор: diablero 9.8.2007, 19:01
Вылетает тут:
Код

tx.begin();

Пока никак не могу понять, по отдельности работает...

Автор: Stampede 9.8.2007, 19:27
Цитата(diablero @  9.8.2007,  10:01 Найти цитируемый пост)
1:
    
tx.begin();

Пока никак не могу понять, по отдельности работает... 


Дак ты же пытаешься заново начать транзакцию. Ты к этому моменту уже в транзакции! Так что просто убери все что у тебя там относится к транзакции, включая commit()  и rollback(), и все будет пучком. Обо всех этих вещах уже "позабочено" в методе service().

Автор: diablero 9.8.2007, 21:24
Я это понял, и чтобы загладить свою глупость хотел пост подредактировать. А ты уже ответилsmile

Автор: Stampede 9.8.2007, 21:32
Цитата(diablero @  9.8.2007,  12:24 Найти цитируемый пост)
Я это понял, и чтобы загладить свою глупость хотел пост подредактировать.


Не надо этого делать. На заглаженной глупости может поскользнуться кто-то другой smile

Автор: diablero 9.8.2007, 21:47
Код

public class RegistrationWorker extends AbstractWorker implements Worker {
    public Page execute(HttpServletRequest request, HttpServletResponse response) {
        super.execute(request, response);
        String userName = request.getParameter("userName");
        String password = request.getParameter("password");
        String repassword = request.getParameter("repassword");
        Infinite infinite = Infinite.getInstance();
        UserManager manager = infinite.getUserManger();

        try {
            if(manager.containsUser(userName)) throw new Exception();
            if(!password.equals(repassword) || password.length()==0) throw new Exception();

            User user = new User();
            user.setName(userName);
            user.setPassword(password);
            user.setDateRegistration(new Date());
            user.setGroup("junior");
            manager.setUser(user);

            page.setLongLivedCookie("userName", user.getName());
            page.setLongLivedCookie("password", user.getPassword());
            page.setRedirectUrl("/confirmation.shtml");
        } catch (Exception err) {
            page.setRedirectUrl("/form/registration.shtml");
        }
        return page;
    }
}

Что мы сейчас имеем.
1. проеверяем что пользователя такого нет.
2. что пароли совпадают, но не проверяем сложность.

Сразу вскрылась недоработка, нам нужно создать бин group.
Я еще не нашел как прикрутить шаблон ввода к дате.

Что делаем дальше? До конца прорабатываем форму?

Автор: Stampede 9.8.2007, 23:01
Цитата(diablero @  9.8.2007,  12:47 Найти цитируемый пост)
Что делаем дальше? До конца прорабатываем форму? 


Нет, не надо.

Сейчас мы добавим в нашу картину вселенной вебсайта один полезный элемент. Суть вот в чем. Когда юзер ошибается во вводе данных, мы должны перенаправить его обратно на страницу формы. Но на ней уже должны быть заполнены те поля, которые он ввел ранее, плюс каким-то образом обозначено, в чем заключалась ошибка. Возникает вопрос: как это сделать?

Прежде чем предложить решение, я хотел бы напомнить, что все, абсолютно все формные данные приезжают к нам на сервер в виде текста, а если точнее, то в виде пар parameter=value. Для удобства работы с этими парами в Servlet API есть метод request.getParameter(String name) и другие. Отсюда напрашивается решение: если мы каким-то образом сохраним эти значения между обращениями к /form.registration.shtml, то сможем при рисовании формы заполнять поля теми значениями, которые поступили по сабмиту!

Вопрос: где сохранять эти данные? Дак, вестимо, в сессии!

Остается еще один вопрос: а в каком виде это делать?

Вот тут мы и подходим к тому решению, которое я хотел предложить. Мы заведем достаточно простенький класс - назовем его FormData - унаследованный для удобства от HashMap. Можно, конечно, организовать связь с мапом и через containment, но это потребует больше писанины. Класс примерно такой:

Код

public class FormData<String, String> extends HashMap {
    private String message;

    public FormData() {
    }

    public FormData(Map map) {
        super(map);
    }

    // setter and getter for message
}


В принципе можно было бы вообще брать request.getParameterMap() и сохранять все мапом, но имея специализированный класс, мы сможем впоследствии добавлять всякие удобные штучки. Например, как видно из данного примера, мы можем сразу же добавить в обработчик такой код:

Код

if (manager.containsUser(userName)) {
    formData.setMessage("User with such name already exists");
} else if (!password.equals(repassword) {
    formData.setMessage("Passwords do not match");
} else if {
// ...
}

if (formData.getMessage() == null) {
    page.setRedirectUrl(/confirmation.shtml);
} else {
    request.getSession().setAttribute("formData", formData)
    page.setRedirectionUrl(/form/registration.shtml);
}


Теперь о том, как этим классом пользоваться. Во-первых, у нас изменится код RegistrationFormWorker. Если раньше мы просто клали в контекст имя шаблона, forms/registration.vm, то теперь работы будет побольше.

Чтобы сделать код шаблона проще, мы будем всегда передавать ему данные через объект FormData (например, в переменной контекста $formData). А объект этот сначала искать в атрибутах сессии, а если отсутствует - то создавать новый.

Замечу, что при регистрации этот новый объект FormData так и будет передаваться пустым, а вот когда мы ту же самую форму задействуем для редактирования профиля юзера, то мы этот новый экземпляр FormData сначала заполним значениями из объекта User.

Сначала все может показаться запутанным, но по мере того как будешь делать, все постепенно прояснится.

Автор: diablero 10.8.2007, 19:21
Если передавать в класс  FormData request.getParameterMap(), то выскакивает такое исключение. 
Как я понял, возникает когда нет значение у ключа.
Код

java.lang.ClassCastException: [Ljava.lang.String;
    ru.selfexpression.contents.worker.RegistrationWorker.execute(RegistrationWorker.java:22)
    ru.selfexpression.ControllerServlet.service(ControllerServlet.java:50)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:802)

Поэтому сделал следующим образом:
Код

public class RegistrationWorker extends AbstractWorker implements Worker {
    public Page execute(HttpServletRequest request, HttpServletResponse response) {
        super.execute(request, response);
        Infinite infinite = Infinite.getInstance();
        UserManager manager = infinite.getUserManger();

        FormData formData = new FormData();
        Enumeration param = request.getParameterNames();
        while(param.hasMoreElements()) {
            String key = (String)param.nextElement();
            formData.put(key, request.getParameter(key));
        }
        String userName = formData.get("userName");
        String password = formData.get("password");
        String repassword = formData.get("repassword");

        if (manager.containsUser(userName)) {
            formData.setMessage("User with such name already exists");
        } else if (!password.equals(repassword) || password.length() == 0) {
            formData.setMessage("Passwords do not match");
        }
        if (formData.getMessage()==null) {
            User user = new User();
            user.setName(userName);
            user.setPassword(password);
            user.setDateRegistration(new Date());
            user.setGroup("junior");
            manager.setUser(user);

            page.setLongLivedCookie("userName", user.getName());
            page.setLongLivedCookie("password", user.getPassword());
            page.setRedirectUrl("/confirmation.shtml");
        } else {
            request.getSession().setAttribute("formData", formData);
            page.setRedirectUrl("/form/registration.shtml");
        }
        return page;
    }
}

Код

public class RegistrationFormWorker extends AbstractWorker implements Worker {
    public Page execute(HttpServletRequest request, HttpServletResponse response) {
        super.execute(request, response);
        FormData formData = (FormData) request.getSession().getAttribute("formData");
        page.put("formData", formData);
        page.put("content", "form/profile.vm");
        return page;
    }
}

Код

<p class="redtext">$!formData.getMessage()</p>
<table border="0" cellspacing="1" cellpadding="0">
    <tr>
        <td>
            <table border="0" width="100%" class="white">
                <form action="/submit/registration.do" method="post" name="register">

                                     <tr>
                    <td>*Логин:</td><td><input type="Text" name="userName"></td>
                </tr>
                              <tr>
                    <td>*Пароль:</td><td><input type="Password" name="password"></td>
                </tr>
                <tr>
                    <td>*Повторите пароль:</td><td><input type="Password" name="repassword"></td>
                </tr>
                <tr>
                 <td rowspan="2">Пол:</td>
                 <td><input type="radio" name="gender" value="male" id="rbmale" checked><label for="rbmale">мужской</label></td>
                </tr>
                <tr>
                 <td><input type="radio" name="gender" value="female" id="rbfemale"><label for="rbfemale">женский</label></td>
                </tr>
                <tr>
                    <td>Дата рождения:</td><td><input type="Text" name="birthday" maxlength="10" value=$!formData.                                                                                                                                                                                                      get("birthday")></td>
                </tr>
                <tr>
                    <td>Город:</td><td><input type="Text" name="city" value=$!formData.get("city")></td>
                </tr>
                <tr>
                    <td>Страна:</td><td><input type="Text" name="country" value=$!formData.get("country")></td>
                </tr>
                <tr>
                    <td>Подпись:</td><td><input type="Text" name="signature" value=$!formData.get("signature")></td>
                </tr>
                <tr>
                    <td>*e-mail:</td><td><input type="Text" name="mail" value=$!formData.get("mail")></td>
                </tr>
                <tr>
                    <td>icq:</td><td><input type="Text" name="icq" value=$!formData.get("icq")></td>
                </tr>
                <tr>
                    <td>Аватар:</td><td><input type="file" name="avatar" value="Обзор"></td>
                </tr>
                 <tr>
                    <td colspan="2"><br>* - отмеченны поля обязательные к заполнению.<br><br></td>
                </tr>
                                    <tr>
                    <td align="center" colspan="2"><input type="Submit" value="Зарегестрировать"></td>
                </tr>
                <tr>
                    <td><br></td>
                </tr>
                
                </form>
            </table>
        </td>
    </tr>
</table>

Соответсвующим образом изменю LoginWorker'ы

Автор: Stampede 10.8.2007, 20:07
Цитата(diablero @  10.8.2007,  10:21 Найти цитируемый пост)

        FormData formData = new FormData();
        Enumeration param = request.getParameterNames();
        while(param.hasMoreElements()) {
            String key = (String)param.nextElement();
            formData.put(key, request.getParameter(key));
        }


Два замечания. Первое: ты преждевременно заводишь и инициализируешь объект FormData. Если регистрация проходит успешно, то он нам и не понадобится вовсе. Это раз.

Второе: трюки с перебором параметров неоправданно загромождают код воркеров, тем более что это дело можно запросто инкапсулировать. Так что все-таки используй конструктор new FormData(request.getParameterMap()), а уж в конструкторе делай всякие переборы с проверками.

Далее, вот здесь:

Код

FormData formData = (FormData) request.getSession().getAttribute("formData");


добавь все-таки еще одну строчку:

Код

formData = (formData == null) new FormData() : formData;


Это обезопасит тебя от ошибок в шаблоне.

И еще парочка предложений для реализации.

1. Текст сообщений об ошибке

Сейчас мы вынужденно пишем это сообщение по-английски. А нам бы надо по нашенски. Можно, конечно, сделать по i18n-ному, через ResourceBundle  и пр. Но можно и по простому. Например, передавать в качестве мессаджа не текст сообщения, а некую текстовую константу, скажем, "DUPLICATE_NAME" или "PASSWORD_MISMATCH", а в теле шаблона анализировать и выводить соответствующую фразу по-русски.

2. Текст подтверждения

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

Предлагаю класс для этой инфы разработать самостоятельно.

Автор: diablero 11.8.2007, 01:43
Цитата(Stampede @  10.8.2007,  20:07 Найти цитируемый пост)
Второе: трюки с перебором параметров неоправданно загромождают код воркеров, тем более что это дело можно запросто инкапсулировать.

Добавил в класс AbstractWorker:
Код

 public HashMap getParameterMap(){
        HashMap map = new HashMap();
        Enumeration param = request.getParameterNames();
        while(param.hasMoreElements()) {
            String key = (String)param.nextElement();
            map.put(key, request.getParameter(key));
        }
        return map;
 }

1. Текст сообщений об ошибке
Тут выбор на мой взгляд прост, если сайт многоязычный, то проще иметь несколько вариантов шаблонов.
Поэтому передаю текстовую константу.
2. Текст подтверждения
Сделал так:
Код

public class ConfirmData {
    public static final int DEFAULT_ACTION = 0;
    public static final int LOGIN_ACTION = 1;
    public static final int LOGOUT_ACTION = 2;
    public static final int REGISTRATION_ACTION = 3;

    private int actionType = DEFAULT_ACTION;
    
    public void setActionType(int actionType) {
        this.actionType = actionType;
    }
    public int getActionType() {
        return actionType;
    }
}

Код

#set( $DEFAULT_ACTION = 0 )
#set( $LOGIN_ACTION = 1 )
#set( $LOGOUT_ACTION = 2 )
#set( $REGISTRATION_ACTION = 3 )

<html>
<head>
    <title>$message</title>
</head>
<body>
<center>
#if( $confirmData.getActionType() == $LOGIN_ACTION )
    <br><br><br><h2>Вход выполнен. Добро пожаловать.</h2><br>
    Перейти на <a href="/home.shtml">главную страницу</a>
#elseif( $confirmData.getActionType() == $LOGOUT_ACTION )
    <br><br><br><h2>Выход выполнен. До свидания.</h2><br>
    Перейти на <a href="/home.shtml">главную страницу</a>
#elseif( $confirmData.getActionType() == $REGISTRATION_ACTION )
    <br><br><br><h2>Регистрация прошла успешно.</h2><br>
    Перейти на <a href="/home.shtml">главную страницу</a>
#else
    <br><br><br><h2>Действие выполнено.</h2><br>
    Перейти на <a href="/home.shtml">главную страницу</a>
#end
</center>
</body>
</html>

Автор: Tony 11.8.2007, 10:37
Mетод getParameterMap содержимое заменить на
@SuppressWarnings("unchecked")
    public Map<String,String> getParameterMap(){
          return request.getParameterMap();
    }

Автор: goodday1941 12.8.2007, 02:08
Извиняюсь за оффтоп, но это важно! 

Сейчас читая эту тему пытаюсь реализовать свой проект (пока до 11 странички добрался с JPA), в общем большая просьба к диаблеро - не править код в предедущих постах а создавать новые посты с исправлениями. Может к 20 страничке ситуация изменилась, но на 11 пока постоянные правки предедущих постов, непонятки получаються  smile 

PS... за затею большое спасибо  smile , уже много интерестного для себя почерпнул (XStream, JPA) 

Автор: Stampede 12.8.2007, 05:09
Welcome to the club, goodday1941!

Приятно видеть, что люди интересуются вопросом.

По поводу правки постов - да, я уже несколько раз говорил об этом diablero, вроде ситуация исправилась к лучшему.

Автор: diablero 12.8.2007, 06:42
Исправилась, исправилась ...smile

Автор: goodday1941 12.8.2007, 15:10
ну еще малек оффтопа.. сейчас с Velocity разбираюсь... данный инструмент оЧень понравился, но что то эта тема слабо раскрытой получилась в ваших топиках и нагуглить я толкового ничего не смог, что самое обидное (пол часа искал либы которые нужно подключить, кстатии в списке либ в приатаченом зип архиве их небыло, и сайт джакарты что то не выдал мне даунлоад ссылку на либы)... 

в общем то можете подсказать толковых текстовичков по Velocity? заранее благодарен smile 

Автор: diablero 12.8.2007, 16:04
Ссылка на Velocity: http://velocity.apache.org/index.html.
Документации на сайте, на мой взгляд достаточно.

Список подправлю.

Автор: Maksym 13.8.2007, 13:28
Stampede
Скажи, пожалуйста, знаком ли ты с технологей facelets? Какие преимущества и недостатки предлагаемого здесь подхода против facelt'ных шаблонов?

Автор: Stampede 14.8.2007, 00:33
Цитата(Maksym @  13.8.2007,  04:28 Найти цитируемый пост)
Скажи, пожалуйста, знаком ли ты с технологей facelets?


Нет, не знаком, но поскольку само название Facelets предполагает его связь с "лицами", а "лица" продвигают люди, чей образ веб-программистского мышления был сформирован под влиянием JSP, то скажу честно: я отношусь к Facelets с большим предубеждением. Вот я сейчас глянул обзорную статью http://www.ibm.com/developerworks/java/library/j-facelets/, и что я там увидел? Все тот же корявый синтаксис кастом тэгов, куча обязательных объявлений, невнятная связь шаблона с моделью...

Товарищи, вы поймите, кастом тэги - это, по большому счету, всего лишь способ организовать обратные вызовы (callbacks) из шаблона в некие Java-компоненты. Но для этого совсем не обязательно городить такой огород!

Maksym, я понимаю, тебя интересует, как у нас будет решен вопрос с компонентностью. Обещаю, очень положительно будет решен smile Потерпи, уже совсем чуть-чуть осталось. Я тебе могу даже конкретно сказать: когда будем делать механизм постраничного вывода.

diablero:

Посмотрел твой код. В принципе все нормально. Замечания:

1. Метод AbstractWorker.getParameterMap()

Я под инкапсуляцией имел в виду немного другое. Понимаешь, воркерам должно быть вообще до балды, какие там проблемы возникают при кастинге параметров. Скорее всего, проблема вообще решается правильным описанием дженериков в классе FormInfo. Но даже если не решается, то можно вручную перебрать все эти параметры в конструкторе FormInfo(Map map), и вот это и будет искомой инкапсуляцией.

2. Класс ConfirmData

Я ведь говорил о текстовых константах, а ты используешь числовые. Тогда ты мог бы прямо в коде Velocity сравнивать со строками, например #if($action == "LOGIN_ACTION"), и не надо было бы объявлять их по десять раз.

Кроме того, в классе можно было бы предусмотреть переменную типа Object, в которой передавать любой произвольный объект. Например, при подтверждении логина - объект User, а при подтверждении поста - объект Post. Это позволило бы выводить более конкретизированный текст сообщения (например, включающий названия поста), а в качестве ссылки - прописывать соответствующий УРЛ.

3. Воркер ConfirmationWorker

И еще одна важная вещь. В классе ConfirmationWorker нужно обязательно добавить код, который подчищает сессию: удаляет атрибуты formData и confirmData. Это нужно для того, чтобы отслеживать ситуации, когда юзер попал на страницу подтверждения неестественным образом: например, по ссылке извне, по кнопкам взад/вперед или через закладку браузера.

Соответственно, в шаблоне должна стоять проверка: если переменная $confirmData отсутствует, то говорить юзеру, чтобы он не лез куда не следует, и пернаправлять его на домашнюю страницу.

Следующим шагом предлагаю сделать ввод новых постов:

УРЛ: /form/post.shtml
Воркер: PostFormWorker
Шаблон: forms/post.vm

|
V

/submit/post.do
SubmitPostWorker

А вслед за этим сразу сделаем редактирование постов.

И еще, по поводу подразделов. Ты, кажется, собирался ввести для них новую сущность. А что если тебе впоследствии понадобится под-подраздел. А потом под-под-подраздел, и так далее?

Нет уж, вот есть у тебя сущность Section, вот в ней и надо сразу заложить иерархичность. Если самостоятельно не получится, будем решать вместе.

Автор: Maksym 14.8.2007, 18:06
Stampede, спасибо за ответ.

Автор: diablero 14.8.2007, 20:49
Цитата(Stampede @  14.8.2007,  00:33 Найти цитируемый пост)
 проблема вообще решается правильным описанием дженериков в классе FormInfo

чего-то не находиться решение...
Цитата(Stampede @  14.8.2007,  00:33 Найти цитируемый пост)
 Следующим шагом предлагаю сделать ввод новых постов

Почти доделал. Сдесь нужно сразу привести все в порядок. Я имею ввиду сущность Section, подразделы. Завтра после работы, доэксперементирую, и если понравиться реализация, все запостю.

Автор: ouks 15.8.2007, 22:55
Привет! слежу за вашей темой уже несколько недель. Спасибо за столь полезный топик. Пытаюсь вас догнать. Опыта как такового в java нет.   Застрял вот на сервлете, 
запуская Infinite1[1].1.3.4 второй день.
Да только выдает  упорно "error page" из шаблона  smile Оно у всех запускается без проблем? 

 ок..продолжаю.. попробую еще раз все по пунктам..  эх, как же на пхп все попроще  smile  

Автор: Shaggie 16.8.2007, 08:02
Цитата(ouks @  15.8.2007,  23:55 Найти цитируемый пост)
Да только выдает  упорно "error page" из шаблона

Было такое. Долго боролся.
Код MainController.java из Infinite1.1.3.4:
Код

...

    static {
        mappings = new HashMap<String, Class<Worker>>();
        mappings.put("/home.shtml", HomeWorker.class);
        mappings.put("/section.shtml", SectionWorker.class);
        mappings.put("/post.shtml", PostWorker.class);
    }

...

В этом коде, скорее всего, барахлят пути к классам, которые засовываются в mapping.
Проверить пути можно так:
Код

...

        Writer writer = response.getWriter();
        writer.write(html);

        writer.write("<br>" + url); // добавь эту строку в код

        writer.flush();
        tx.commit();

...

Посмотри пути, а потом либо впиши их в mapping, либо надо будет поизвращаться с настройками сервера.

Удачи.

 smile Да, на PHP поначалу проще, но когда проект разрастается... Где тут смайлик самоубийцы? И к тому же от от сервлетно-шаблонного подхода веет этаким... профессионализмом, что ли... программерским благородством... Не знаю даже, как описать.

Автор: goodday1941 16.8.2007, 15:42
вопрос в тему... 
Stampede, предлагал вынести стили в отдельный css файл... собсно вопрос в следующем где его размещать, что прописать в web.xml и какой путь к css файлу прописывать в самой веб страничке?

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

Автор: Maksym 16.8.2007, 15:57
Цитата(goodday1941 @  16.8.2007,  15:42 Найти цитируемый пост)
где его размещать

Нормально будет разместить его в папке css, созданной на одном уровне со стартовой страницей.
Цитата(goodday1941 @  16.8.2007,  15:42 Найти цитируемый пост)
что прописать в web.xml

Ничего не нужно.
Цитата(goodday1941 @  16.8.2007,  15:42 Найти цитируемый пост)
акой путь к css файлу прописывать в самой веб страничке

Относительный.
Цитата(goodday1941 @  16.8.2007,  15:42 Найти цитируемый пост)
еражть всю инфу о стилях  в отдельном файле имеет смысл в плане быстродействия, так как css файлы кешируються. Поправте если я не прав

Это так. Но главный плюс css в возможности отделить логические структуры страниц от деталей оформления и централизовать управление внешним видом системы.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)