13.11.2009

Re-использование кода

Елена Сагалаева интересную заметку написала. Похоже, что у всех такие проблемы возникают и идеально не решаются. У нас гораздо меньше, чем 50 человек, но какая-то дисциплина тоже требуется. И решил я, что:

1. Нужен корпоративный портал, который предоставит всю функциональность в удобном виде (что-то типа code.google.com, sourceforge и т.п.)
2. Все модули, которые предполагается повторно использовать, должны версионироваться - если в каком-то проекте уже используется какая-то версия модуля, то его нельзя менять!
3. Модуль должен быть снабжен документацией в вики-формате, состоящей из двух частей - человеческой и автосгенерированной по коду.
4. Документация должна версионироваться вместе с модулем.
5. При создании новой версии модуля необходимо обязательно указывать отличия. Провели рефакторинг, ускорили алгоритм на 20% - будьте добры написать. Возможно, кто-то только этого и ждал.
6. Разработчики проекта, в котом используется такой общий модуль, автоматически подписываются (пожизненно :) на выход новых версий модуля.
7. Модули должны быть доступны как в собранном виде, так и в виде исходников, чтобы все-таки позволить сделать копи-паст.
8. И было бы неплохо иметь утилиту, которая попробует собрать все (!) проекты, использующие данный модуль, с новой версией модуля. И запустит тесты. Ибо удобство превыше всего.

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

28.10.2009

Как определить город посетителя (определение адреса по ip)

Все хорошие сайты умеют определять адрес, вернее, город, в котором живет посетитель. И делается это, понятно, по ip-адресу. Только вот кто откуда берет географические привязки ip? Лично я решил использовать сервис IpGeoBase. Он позволяет скачать базу целиком или же использовать xml-сервис. Я предпочел пока второй вариант, чтобы не заморачиваться с ежедневным выкачиванием базы. Понятно, что когда поток посетителей у меня возрастет до небывалых высот, я буду выкачивать базу. А пока у меня это работает очень быстро и выглядит так (код на Питоне 2.6):

#coding=utf-8

def getGeoData(ip):
  """Получить город по ip-адресу (адрес предается как строка).
  Возвращает словарь с элементами-строками city, region, district
  "
""

  import httplib
  import re
  from xml.dom.minidom import parseString

  conn = httplib.HTTPConnection("194.85.91.253:8090")
  conn.request("POST", "/geo/geo.html", \
    "<ipquery><fields><all/></fields><ip-list><ip>" + ip + \
    "</ip></ip-list></ipquery>")
  resp = conn.getresponse()

  data = resp.read()
  conn.close()

  # если ничего не найдено
  if re.search("<message>Not found</message>", data):
    return None

  dom = parseString(data)
  city = dom.documentElement.getElementsByTagName('city')[0]\
    .firstChild.nodeValue
  region = dom.documentElement.getElementsByTagName('region')[0]\
    .firstChild.nodeValue
  district = dom.documentElement.getElementsByTagName('district')[0]\
    .firstChild.nodeValue

  return {'city' : city, 'region' : region, 'district' : district}


Теперь вопрос остальным: кто как справляется с этой задачей?

UPD: иногда сервис неверно определяет адрес по ip, потому что некоторые провайдеры используют общий DHCP для всех регионов, или же информация об адресах еще не собрана. Войдя на IpGeoBase, можно указать свой правильный адрес, если по ip он определился неверно.

15.10.2009

Memcached для Windows и .NET

Вообще, довольно странно размещать memcached на машине с виндой, так как за операционку надо платить. Гораздо дешевле под эти нужды поднимать линуховые тачки. Но, допустим, у вас веб-приложение на ASP.NET или веб-сервис, который хостится под IIS. Кэш вы храните как синглтон в рабочем процессе, у вас отлично реазилованы многопоточные блокировки и т.д. Или даже вы используете какую-нибудь библиотеку для кэширования. Однако, по расписанию регулярно происходит рестарт рабочего процесса, и весь кэш теряется - приходится грузить данные заново.

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

Собственно, сам memcached тянуть отсюда (ищите бинарник под свою винду). В архиве будет один exe-шник. Его можно скопировать, например, в C:\Program files\Memcached\ (папку предварительно надо создать). Потом из командной строки запускаем memcached -d install, в результате он регистрирует себя как windows-сервис (службу) и выполняется с правами "локальная система". По умолчанию он слушает на 11211 порту. Осталось только стартануть сервис memcached из консоли управления службами. После перезагрузки он будет запускаться автоматически.

Теперь про клиентскую часть. Под .NET клиентов несколько, я себе поставил этот. Из архива нам нужны только 2 модуля: log4net.dll и собственно Enyim.Caching.dll. В проекте не забудьте указать ссылки на оба.

sample.config содержит фрагменты, которые вы должны вставить в app.config или web.config. Там может быть указано несколько серверов - вам надо оставить только один с адресом 127.0.0.1 и прописать порт 11211. Особое внимание на тэг <memcached enabled="true"> - так работать не будет. Его надо заменить просто на <memcached>.

Все, можно пользоваться. Мануалов к этому клиенту я не нашел, но он должен придерживаться общепринятого стандарта, так что пойдет любая документация по memcached-клиенту. Еще хочется отметить, что все собственные типы данных, которые вы собираетесь класть к кэш, должны быть помечены атрибутом [Serializable] или [DataContract] (это из WCF). В общем, они должны быть сериализуемыми.

Небольшой пример на C#:

// создание клиента очень быстрое,
// так что не обязательно делать его синглтоном

MemcachedClient client = new MemcachedClient();
// сохранить в кэше
client.Store(StoreMode.Set, "MyIntValue", 12345,
    DateTime.Now.AddMinutes(20));
// получить из кэша
int value = client.Get<int>("MyIntValue");
// удалить из кэша
client.Remove("MyIntValue");

05.10.2009

Проблемы с easy_install MySQL-python

Пробовал поставить таким образом MySQL-python, чтобы Django заработал с MySQL, но никаким образом мне это не удавалось. У меня Windows XP и Python 2.6. Уже решил, что буду ставить Python25. Но, помог готовый инсталлятор под винду, собранный добрым человеком - вот ссылка. Качать надо "MySQL-python-1.2.3c1.win32-py2.6.exe" - это последняя версия.

Кстати, 1 октября вышел новый релиз Питона - 2.6.3. Исправлено около ста багов, новых фич не добавлено.

27.07.2009

Semantic Web: вы в него верите?

Web 3.0


Я здесь буду говорить о том, как плох Semantic Web.

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

Что имеем сейчас


Сначала про состояние вопроса в целом. Насколько я понял, на русском языке эта тема лучше всего освещена на сайте shcherbak.net, за что огромное спасибо Сергею Щербаку и его товарищам. Сайт позволяет очень быстро погрузиться в проблемы развития новой технологии.

Итак, технология обширна и сложна. W3C уже принял вторую версию языка онтологий – OWL 2.0, который совместим с другим творением - языком RDF (Resource Description Framework, модель для описания ресурсов). Документация по SPARQL – языку запросов к RDF, вообще приводит в ужас неподготовленного читателя своим объемом. На RDF мы пишем утверждения вида «субъект — предикат — объект», где субъекты и объекты являются представителями каких-то классов, на OWL мы описываем связь классов и отношения между ними, а на SPARQL пишем запросы. В целом, стандарты объемные, и редактировать все это вручную уже не так-то просто, поэтому появляется множество редакторов онтологий.

Механизмов семантического вывода оказалось тоже немало – взгляните хотя бы на Вики или сюда. Большинство проектов – open source. Раз столько разных подходов – значит, есть спорные моменты.

Кстати, о великих. О кое-какой (примитивной) поддержке Semantic Web заявляли в разное время Yahoo, Google и Яндекс (поддержка тега Friend-Of-A-Friend в блогах).

Красота


Я думаю, все, кому интересен Semantic Web, читали статью Тима Бернерса-Ли, родителя всей этой штуки. Там рассказывалось о том, как, используя семантическую метаинформацию, специальные программы-агенты могут, например, забронировать место у врача в нужное время в поликлинике неподалеку.

Из всей красиво звучащей концепции явно выпирает такой момент: онтологии, создаваемые различными пользователями, могут объединяться программами-агентами. Причем, они (онтологии) могут существенно различаться, создавая тем самым многозначность при анализе информации. Автор предполагает, что для некоторых «важных» областей, таких как медицина, будут существовать официально утвержденные онтологии. Идея неплохая, стандарты должны быть. Желательно международные. Хорошо, допустим, официальные онтологии утверждены, очень грамотно составлены и пригодны для повсеместного использования. Заодно отчасти решается другая проблема: «черные оптимизаторы» в целях поднять свой сайт повыше и опустить другие пониже могут создавать свои собственные онтологии, но умные поисковики и агенты будут доверять только официальным источникам.

Ну а как быть с теми онтологиями, которые не будут никем утверждены? Пусть там царит хаос? Ну нет, на помощь приходят механизмы вывода, работающие с нечеткими онтологиями, статистическими онтологиями и поддерживающими собственные расширения OWL (в примере – P-OWL).

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

Но забудем о простых смертных – давайте представим бизнес, основанный на Semantic Web и так красиво описанный в приведенной выше статье, учитывая все перечисленные аспекты…
Придут ли все к единому стандарту? Как вариант, я рассматриваю параллельное существование слабо совместимых языков описания онтологий и механизмов их анализа. Помимо этого, поисковые системы наверняка будут строить свои всеобъемлющие онтологии (на своем языке описания :).

Еще вопросы


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

Позволяет ли он описывать сложные ограничения, например, что «у мужчины может быть только одна жена, если он не мусульманин; иначе до 4»?

Позволяет ли он описать процесс преобразования одного объекта в другой, чтобы система вывода смогла составить цепочку преобразований для выполнения некоторого действия? Например, у вашего друга День Рождения, и он больше всего на свете любит вареную сгущенку вперемешку с обычной. Но ни один магазин не удосужился внести метаинформацию про не продает вареную сгущенку, зато везде можно найти невареную. Что надо сделать? Правильно, купить и сварить :) Но сварить только часть, чтобы и невареная осталась.

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

Мое ИМХО


Чтобы Интернет стал действительно семантическим, надо не вставлять метаинформацию в страницы, а анализировать уже имеющийся человеческий текст. Уже сейчас на конференциях Senseval показываются неплохие результаты в этом направлении (65.2% успехов в 2004 году, вот работа, в которой с темой можно познакомиться поближе). Таким образом, поисковые (и прочие) системы смогут строить семантические индексы, а не текстовые. За счет стройного языка представления модели мира (которую в единственном экземпляре надо всем миром построить), в отличие от той, что получится при описании с помощью OWL, информация не будет содержать многозначности и противоречий. Уже есть работы, в которых предлагается максимально простой способ представления модели мира с помощью минимального набора ограничений. Но это уже тема отдельной статьи.

Вместо того, чтобы рядом с Интернетом возводить «Инструкцию по его использованию для роботов», надо создавать механизмы по его Пониманию. Компьютер не станет ближе к человеку, пока не научится извлекать смысл из его слов. Независимо от количества интернетов. Вот и все.

24.07.2009

Автоматическое увеличение номера билда при автоматической сборке в MS Team Foundation Server

Для организации постоянно увеличивающегося номера сборки необходимо к файлу проекта подключить дополнительную задачу для выполнения. Сделать это можно путем создания .NET сборки, содержащей класс, унаследованный от Microsoft.Build.Utilities.Task.
Класс, содержащий метод для инкрементирования одного числа, записанного в файл может выглядеть следующим образом:

public class IncrementingNumber : Task
{
public override bool Execute()
{
NextNumber = IncrementNumber();
LastNumber = NextNumber - 1;
return true;
}

public int IncrementNumber()
{
using (FileStream fs = new FileStream(NumberFile,
FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
{
StreamReader reader = new StreamReader(fs);

long pos = 0;
String line = reader.ReadLine();

while (line != null && line.StartsWith("#"))
{
pos = pos +
line.Length +
System.Environment.NewLine.Length;
line = reader.ReadLine();
}

int number = -1;
if (line != null)
number = Int32.Parse(line);

NextNumber = number + 1;


fs.Position = pos;

StreamWriter writer = new StreamWriter(fs);
writer.WriteLine(NextNumber.ToString());
writer.Flush();
writer.Close();
}
return NextNumber;
}

[Required]
public string NumberFile { get; set; }

[Output]
public int NextNumber { get; set; }

[Output]
public int LastNumber { get; set; }

}

Метод Execute() должен быть переопределен, потому как именно с него и начинается выполнение задачи. В данном примере свойство NumberFile должно содержать имя файла, в котором хранится сборка, при чем это свойство помечено как обязательно для заполнения. Свойства с атрибутом [Output] хранят результаты выполнения задачи.
Для включения сборки в файл проекта .proj необходимо записать следующий тэг:
<usingtask taskname="" assemblyfile=""></usingtask>
где AssemblyFile - имя файла сборки, а TaskName – имя класса-задачи, включая пространство имен.
Далее, для инкрементирования и установки номера версии сборки необходимо переопределить target BuildNumberOverrideTarget примерно следующим образом:




При отсутствии файла с именем buldnumber.txt в указанном месте, он будет создан задачей IncrementingNumber и в него будет записан 0. Подобным образом можно увеличивать каждую из 4-х частей версии сборки, используя разные файлы и условия для их изменения.

22.07.2009

Планировщик заданий (Task Scheduler 1.0) Windows: программный доступ

Введение



Планировщик заданий является сервисом Windows, который умеет по расписанию запускать различные приложения. Windows-интерфейс для него обычно располагается в папке %WINDIR%\TASKS. Кроме того можно всю работу с ним организовать из командной строки путем вызова команды schtasks с необходимыми параметрами. Для программистов существует хорошо документированный Com-интерфейс, но в .Net нет обертки для него. Далее будет описано, как на C# организовать работу с планировщиком заданий.

Классы


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


ScheduledTasks

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

Класс ScheduledTasks является отображением системной папки того компьютера (локального или в сети), где хранятся все файлы с заданиями.

// создаем объект ScheduledTasks для машины с именем
//"DALLAS"

ScheduledTasks st = new ScheduledTasks(@"\\DALLAS");
С помощью этого объекта можно получить доступ к любой задаче для конкретного планировщика по имени задачи, которое равно имени файла задачи без расширения. Кроме того, можно получить все имена задач для данного планировщика:

// получение списка имен задач
string[] taskNames = st.GetTaskNames();
Т.к. этот класс оборачивает СОМ-ресурсов, то после использования его необходимо вызвать метод Dispose() для освобождения этого интерфейса.

Task

Этот класс оборачивает отдельное задание с предоставлением доступа к его параметрам (какое приложение необходимо запускать, время выполнения и др. параметры, которые можно увидеть в Windows-интерфейсе). Кроме этого у класса есть еще один параметр TriggerList, о котором будет рассказано ниже.
После содания, открытия и изменения необходимо сохранить задание в его файл с помощью функции Save(). Функция Save(string name) работает как и Windows-функция "Сохранить как". Данный класс не имеет public конструктора. Все процедуры по созданию, открытию выполняются с помощью класса ScheduledTasks посредсвтом вызова его методов Open() или Create().

// открываем задание с именем "foo" локальной машины
ScheduledTasks st = new ScheduledTasks();
Task t = st.OpenTask("foo");
Для освобождения СОМ-ресурсов используем функцию Close().

// закрываем задание для освобождения COM ресурса.
t.Close();

TriggerList

Каждое задание имеет одно или несколько временных или иных условий выполнения (запуска его программы). TriggerList является классом, представляющий список этих условий (триггеров). Класс нельзя самостоятельно создать, т.к. он не имеет public конструктора. Экземпляр класса создается при инициализации экземпляра класса Task и доступ к нему возможен только из класса Task.

// получение триггеров локального задания "foo".
ScheduledTasks st = new ScheduledTasks();
Task t = st.OpenTask("foo");
TriggerList tl = t.Triggers;
После закрытия задания данный список становится неактуальным.

Trigger

Триггер представляет собой условие, необходимое для выполнения задания. В Windows-интерфейсе они отображаются в виде списка. Существует несколько видов триггеров с различными параметрами (например те, которые запускаются один раз, и те, которые могут выполняться с некоторой периодичностью). Поэтому класс Trigger является абстрактным. Его наследуют конкретные классы-реализации различных типов триггеров. Каждый из наследников имеет свой уникальный конструктор и свои уникальные параметры настройки времени выполнения задания. Для создания триггера необходимо определиться с его типом и создать посредством его конструктора, затем добавить его в список триггеров задания:

// добавление триггера для задания "foo", чтобы оно
//выполнялось в 16:30 каждый день

Trigger tg = new DailyTrigger(16, 30); // часы и минуты
ScheduledTasks st = new ScheduledTasks();
Task t = st.OpenTask("foo");
t.Triggers.Add(tg);
t.Save();
Классы-наследники Trigger :
  1. RunOnceTrigger - выполнять однажды
  2. DailyTrigger - выполнять ежедневно
  3. WeeklyTrigger - выполнять еженедельно
  4. MonthlyDOWTrigger - выполнять ежемесячно с учетом дней в недели и самих недель
  5. MonthlyTrigger - выполнять ежемесячно с учетом дней с месяце
  6. OnIdleTrigger - выполнять при простое системы
  7. OnSystemStartTrigger - выполнять при старте системы
  8. OnLogonTrigger - выполнять при входе пользователя в систему
Созданный триггер непривязан ни к какому заданию, поэтому не занимает СОМ-ресурсов. Для привязки его необходимо включить в список TriggerList нужного задания. Освобождать его ресурсы нет необходимости, т.к. они автоматически освобождаюся при закрытии задания. Следующия правила позволят вам избежать некоторых ошибок при программировании планировщика заданий:
  • Конкретный экземпляр триггера может единыжды содержаться в списке триггеров задания. Невозможно добавить триггер в список, если он там уже присутствует (т.е. в списке должна быть только одна ссылка на этот объект). Для создания такого же триггера в другом списке необходимо воспользоваться функцией Clone() для создания непривязанного триггера и только потом добавить его в новый список.
  • Привязанный триггер невозможно использовать, если родительское задание закрыто.

Примеры использования

1. Получение списка заданий с удаленного компьютера (для этого пользователь, запустивший программу должен имет права администратора на удаленном компьютере):


// получение ScheduledTasks для компьютера "DALLAS"
ScheduledTasks st = new ScheduledTasks(@"\\DALLAS");

// полученеи списка имен заданий
string[] taskNames = st.GetTaskNames();

// получение каждого задания и вывод его описания
foreach (string name in taskNames) {
Task t = st.OpenTask(name);
Console.WriteLine(" " + t.ToString());
t.Close();
}

// освобождаем COM - ресурсы.
st.Dispose();

2. Создание нового задания
//получение ScheduledTasks локального компьютера.
ScheduledTasks st = new ScheduledTasks();

// Создание задания с именем "D checker"
Task t;
try
{
t = st.CreateTask("D checker");
}
catch
(ArgumentException)
{
Console.WriteLine("Задание с таким именем уже
существует"
);
return;
}

// Заполнение информации о программе для запуска
t.ApplicationName = "chkdsk.exe";
t.Parameters = "d: /f";
t.Comment = "Проверка диска D:";

// установка аккаунта, под которым будет
//происходить выполнение.

t.SetAccountInformation(@"THEDOMAIN\TheUser",
"
HisPasswd");

// Говорим системе ожидать 10 мин перед запуском
//программы

t.IdleWaitMinutes = 10;

// позволяем заданию выполняться не более 2 часов,
//30 минут.

t.MaxRunTime = new TimeSpan(2, 30, 0);

// говорим системе запускать задание только при
//простое.

t.Priority = System.Diagnostics.ProcessPriorityClass.Idle;

// создаем триггер для запуска каждое воскресенье
//в 6:30.

t.Triggers.Add(new WeeklyTrigger(6, 30,
DaysOfTheWeek.Sunday));

// Сохраняемся
t.Save();
// освобождаем все ресурсы.
t.Close();
// освобождаем ресурсы.
st.Dispose();

3. Меняем время выполнения задания

//получение ScheduledTasks локального компьютера.
ScheduledTasks st = new ScheduledTasks();

// открываем задание, которое было добавлено
Task task = st.OpenTask("D checker");

// удостоверяемся, что оно есть
if (task != null) {
// пробегаем по всем триггерам
foreach (Trigger tr in task.Triggers)
{
// если триггер имеет время начала выполнения задания,
// меняем на 4:15.

if (tr is StartableTrigger)
{
(tr as StartableTrigger).StartHour = 4;
(tr as StartableTrigger).StartMinute = 15;
}
}
task.Save();
task.Close();
}
st.Dispose();



Приведенные примеры частично взяты с:
http://www.codeproject.com/KB/cs/tsnewlib/TSNewLib_src.zip
из статьи:
http://www.codeproject.com/KB/cs/tsnewlib.aspx

GWT и Maven

Для удобства разработки систем используя gwt, можно использовать отслеживания зависимостей с помощью maven. Рассмотрим пример создания каркаса gwt-приложения в IDE Eclipse.
Cоздаем maven проект.
В качестве типа проекта выбираем, что проект будет просто (не использовать никаких архетипов).

Конфигурируем проект
После того, как проект создан, нобходимо настроить зависимости gwt. Для этого можно воспользоваться инструкцией, которую написали разработчики gwt. Полученный проект выглядит так:
Так как gwt это веб-приложение, то необходимо создать в проекте папку war, в ней WEB_INF и добавить файл настройки веб-приложения web.xml. Так же добавить в WEB_INF папку lib для хранения библиотек необходимых для работы gwt.

Для того чтобы скомпилировать gwt проект можно воспользоваться build.xml, который генерируется при создании проекта для eclipse. Его необходимо отредактировать, чтобы он понимал структуру проекта maven
<project name="org.test.gwtMaven"
....default="build"
....basedir="."
....xmlns:artifact="antlib:org.apache.maven.artifact.ant">
....
</project>
Необходимо добавить plugin к Ant для того чтобы он смог интегрироваться с maven. Подключаем plugin для данной сборки xmlns:artifact="antlib:org.apache.maven.artifact.ant">.
Добавляем свойства, которые можно будет использовать в тасках Ant:
<property name="gwt.version" value="1.7.0" />
<property name="gwt.sdk" location="war/WEB-INF/gwt-sdk" />
<property name="platform" value="windows" />
Создаем список файлов, который будет получен из pom для использования при сборки.
<artifact:dependencies filesetId="dependency.fileset">
....<pom file="pom.xml" />
</artifact:dependencies>
Настройка classpath:
<path id="project.class.path">
....<pathelement location="war/WEB-INF/classes" />
....<pathelement location="${gwt.sdk}/gwt-user-${gwt.version}.jar" />
....<fileset dir="${gwt.sdk}" includes="gwt-dev*.jar" />
....<fileset dir="${gwt.sdk}" includes="*.jar" />
....<fileset dir="war/WEB-INF/lib" includes="**/*.jar" />
</path>
Загруза библиотек необходимых для компиляции и работы GWT.
<target name="libs" description="">
....<mkdir dir="war/WEB-INF/gwt-sdk" />
....<copy todir="war/WEB-INF/gwt-sdk">
........<fileset refid="dependency.fileset" />
........<mapper type="flatten" />
....</copy>
....<unzip src="${gwt.sdk}/gwt-dev-${gwt.version}-${platform}-libs.zip"
............dest="${gwt.sdk}/" />
....<copy todir="war/WEB-INF/lib"
....
........file="${gwt.sdk}/gwt-servlet-${gwt.version}.jar" />
</target>
Компиляция исходных кодов.
<target name="javac" depends="libs" description="">
....<mkdir dir="war/WEB-INF/classes" />
....<javac srcdir="src/main/java"
.... ....includes="**"
.... ....encoding="utf-8"
........destdir="war/WEB-INF/classes"
........source="1.5" target="1.5"
........nowarn="true"
........debug="true"
........debuglevel="lines,vars,source">
<classpath refid="project.class.path" />
....</javac>

....<copy todir="war/WEB-INF/classes">
........<fileset dir="src/main/java" excludes="**/*.java" />
....</copy>
</target>
Компиляция Java в JavaScript.
<target name="gwtc" depends="javac" description="">
....<java failonerror="true" fork="true" classname="com.google.gwt.dev.Compiler">
........<classpath>
................<pathelement location="src/main/java" />
................<path refid="project.class.path" />
........</classpath>
........<jvmarg value="-Xmx256M" />
........<arg value="org.test.MainEntryPoint" />
....</java>
</target>
Сборка всего проекта GWT.
<target name="build" depends="gwtc" description="" />
Упаковка проект в war файл для размещения на веб-сервере.
<target name="war" depends="build" description="">
....<zip destfile="org.test.gwtMaven.war" basedir="war" />
</target>
Очистка проекта, удаление из папки war библиотек и скомпилированных классов.
<target name="clean" description="">
....<delete dir="war/WEB-INF/classes" failonerror="false" />
....<delete dir="war/gwt" failonerror="false" />
....<delete dir="war/WEB-INF/gwt-sdk" failonerror="false" />
....<delete dir="war/gwtMain" />
....<delete file="war/WEB-INF/lib/gwt-servlet-${gwt.version}.jar" />
</target>
На этом редактирование build.xml пока закончим. Теперь необходимо создать класс-точку входа в gwt приложения. Для этого создадим два namespace'a. org.test и org.test.gwtMavenClient в папке исходников src/main/java. В org.test.gwtMavenClient создадим класс MainEntryPoint, который реализует интерфейс com.google.gwt.core.client.EntryPoint.

package org.test.gwtMain;

import com.google.gwt.core.client.EntryPoint;

public class MainEntryPoint implements EntryPoint{
public void onModuleLoad() {
// TODO Auto-generated method stub
}
}
В org.test создим MainEntryPoint.gwt.xml, который будет содержать настройки gwt-приложения.

<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='gwtMain'>
<inherits name='com.google.gwt.user.User' />
<inherits name='com.google.gwt.i18n.I18N' />

<extend-property name="locale" values="ru_RU" />

<inherits name='com.google.gwt.user.theme.chrome.Chrome' />

<source path="gwtMain" />
<entry-point class='org.test.gwtMain.MainEntryPoint' />
</module>
Дерево проекта выглядет вот так.

Gwt-приложению необходимо где-то хоститься. Для этого в папке war необходимо создать index.html.
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta http-equiv="content-language" content="ru">
<title>Insert title here</title>
<script type="text/javascript" language="javascript"
src="gwtMain/gwtMain.nocache.js"></script>
</head>
<body>
<iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1'
style="position: absolute; width: 0; height: 0; border: 0"></iframe>

<div id="mainWorkSpace"></div>

</body>
</html>
А в build.xml добавить target, который позволит запускать приложения в специальном отладочном браузере
<target name="hosted"
....depends="javac" description="">
........<java
...............failonerror="true"
...............fork="true"
...............classname="com.google.gwt.dev.HostedMode">
............<classpath>
................<pathelement location="src/main/java" />
................<path refid="project.class.path" />
............</classpath>
............<jvmarg value="-Xmx256M" />
............<arg value="-startupUrl" />
............<arg value="index.html" />
............<arg value="org.test.MainEntryPoint" />
........</java>
</target>
Каркас готов. Продолжение следует...

Свой jdbc драйвер и Kettle (Pentaho Data Integration)

Для того чтобы подключенные jdbc драйвер смог работать в Kettle. В класс, который реализует интерфейс java.sql.Driver, необходимо добавить статичный блок для регистрации драйвера в менеджер драйверов.
static {
....try {
........DriverManager.registerDriver(new ParadoxDriver());
} catch (SQLException e) {
...
....}
}

Создание локального Maven репозитория

Для того что организовать maven репозиторий для хранения java packages (*.jar, *.war и т.д.). Необходимо настроить веб-сервер так, что бы он отдавал список файлов и папок при обращении по конкретному адресу.
В качестве веб-сервера берем Apache, устанавливаем его и настраиваем конфигурационные файлы следующим образом:
в hhtpd.conf
...
Include conf/extra/httpd-maven2repo.conf
...
в папке conf/extra/ создаем файл httpd-maven2repo.conf и заносим следующую информацию:

Alias /maven2repo/ "<путь_к_репо>"
Options Indexes FollowSymLinks MultiViews
Order allow,deny
Allow from all
<путь_к_репо> - адрес папки в файловой системе, где будет репозиторий.

Репозиторий готов.
Для того чтобы поместить готовый package в репозиторий, необходимо скачать maven, скопировать его на сервер, где будет распалагаться хранилище и настроить его. Для этого необходимо отредактировать файл /conf/settings.xml в папке maven'a
<localrepository><путь_к_репо></localrepository>
Теперь при запуске на сервере mvn install пакеты будут помещаться в наш репозиторий. А клиенты смогут получать эти пакеты через maven, указывая в качестве репозитория наш сервер.

11.06.2009

Особенности создания ярлыков при инсталляции

Казалось бы ярлык - такая простая вещь, содается двумя-тремя кликами мышки, кроме открытия файла особо ничего не делает. Но вот чтобы создать такую простую вещь в инсталляторе написанном на Wix необходимо учитывать несколько моментов, которые в самом злостном случае способны отнять кучу времени в бесплодных поисках. В основном вся загвоздка кроется в аттрибуте Advertise тега . Опуская подробности, о ярлыках с установленным Advertise="yes" можно сказать то, что они запускают файлы, которые установлены как KeyPath для текущего компонента. KeyPath, в свою очередь определяет тот элемент, по которому определяется присутствие компонента в системе(по-умолчанию - первый файл в списке компонента). Таким образом напрашиваются несколько выводов:
- для advertised ярлыков неприменимо использование атрибута Target, которой указывает на вызываемый файл.
- для вызова нужного файла ему необходимо установить атрубут KeyPath="true" или переместить в начало списка.
- тег должен обязательно находиться в том же компоненте, что и вызываемый файл, для физического помещения ярлыка в другую папку необходимо использовать атрибут Directory.

Если все эти моменты нам не подходят и выбор падает на ярлыки с атрибутом Advertise="no", то мы просто добавляем значение атрибута Target, указывающего на нужный файл. И, в случае если ярлык и файл в одной папке где-нибудь на диске и атрибут Directory не задействован, то все будет работать отлично. Ошибки полезут, если мы ярлык пожелаем положить в какое-либо более удобное место - на рабочий стол или в меню программ, например.
Выглядеть они будут примерно так:
error LGHT0204: ICE43: Component has non-advertised shortcuts. It should use a registry key under HKCU as its KeyPath, not a file.
error LGHT0204: ICE57: Component has both per-user and per-machine data with a per-machine KeyPath.
- в случае, если мы для размещения ярлыка используем атрибут Directory.
error LGHT0204: ICE18: KeyPath for Component: is Directory: 'DesktopFolder'. The Directory/Component pair must be listed in the CreateFolders table.
error LGHT0204: ICE38: Component installs to user profile. It must use a registry key under HKCU as its KeyPath, not a file.
error LGHT0204: ICE43: Component has non-advertised shortcuts. It should use a registry key under HKCU as its KeyPath, not a file.
- в случае, если мы размещаем тег ярлыка в новом компоненте, под директорией 'DesktopFolder'.
Выход из ситуации похоже предусмотрен только один - дать инсталлятору то, что он хочет, то есть создать элемент ветки реестра, с корнем в HKCU в том же компоненте, в котором располагается ярлык, и для элемента установить KeyPath="true".
После всех этих манипуляций ярлыки должны работать так, как им и положено.

02.04.2009

ChildData in Parallel Replicator

Использование Replicator'а бывает очень удобно и нужно. Но есть один очень не удобный момент: доступ к данным внутри дочерних элементов в параллельном репликаторе.
Если использовать последовательный репликатор, то текущий исполняемый экземпляр доступен через свойство репликатора CurrentIndex.
Иначе дело обстоит с параллельным репликатором, где CurrentIndex всегда указывает на один и тот же элемент в списке данных для реплицируемых элементов.
Для решения данной проблемы можно поступить следующим образом:
-определить свой SequenceActivity, содержащий ссылку на данные для экземпляра репликатора. (поле _instanceData)
[Serializable]
public class StoreSequence : SequenceActivity
{
--private object _instanceData = null;

--public object InstanceData
--{
------get { return _instanceData; }
------set { _instanceData = value; }
--}
}
-поместить данный Activity в качестве дочернего элемента в Replicator Activity, а реплицируемые элементы на StoreSequence .
-при выполнение метода ChildInitialized (инициализация дочерних activity) сохранить данные в созданный контейнер
...
private void sendAlertsReplicator_ChildInitialized(object sender, ReplicatorChildEventArgs e)
{
----((StoreSequence)e.Activity).InstanceData = e.InstanceData;
}
...
-для обращения внутри реплицируемых элементов необходимо выполнить операцию приведения типов (Activity)sender , а родительским элементом будет StoreSequence у которого можно получить свойство InstanceData.

31.03.2009

Бинарнарный сериализатор. (BinarySerializer)

Данный сериализатор формирует из типа, байт последовательность. Формат последовательности следующий:

<class_element_count> - количество полей в классе (4 байта)
[<element>, <element>] - поля класса

где <element> - это простой тип данных:
<size> - размер тела (4 байта)
- простой тип переведенный в байты
или массив представление сериализованного массива аналогично классу (см. выше)

Данный сериализатор поддерживает следующие простые типы:
bool, short, int, long(Int64), float(Single), double, Guid.

Механизм содержит три основные части:
1. Атрибуты, которые указывают, что класс или поле необходимо сериализовать.
2. Временный контейнер, который используется для хранения данных при сериализации.
3. Собственно сам механизм сериализации.

Атрибуты:


// Атрибут для класса. Используется в BinarySerializer
[AttributeUsage(
AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class ClassToBinaryAttribute : Attribute
{
}

/// Атрибут для поля класса. Используется в BinarySerializer
[AttributeUsage(
AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class FieldToBinaryAttribute : Attribute
{
}


Временный контейнер:


/// <summary>
/// Контейнер для хранение сериализованных бинарных данных
/// </summary>
public abstract class BinaryContainer
{
protected List<BinaryContainer> _childs =
new List<BinaryContainer>();
/// <summary>
/// Дочерние элементы контейнера
/// </summary>
public List<BinaryContainer> Childs
{
get { return this._childs; }
}

protected byte[] _body;
/// <summary>
/// Сериализованные данные
/// </summary>
public byte[] Body
{
//get { return _body; }
set { _body = value; }
}

/// <summary>
/// Получить размер бинарной информации
/// </summary>
/// <returns></returns>
public abstract int GetBodySize();

/// <summary>
/// Получить бинарные данные
/// </summary>
/// <returns></returns>
public abstract byte[] GetBody();
}

/// <summary>
/// Контейнер для поля
/// </summary>
class BinaryField : BinaryContainer
{
public override int GetBodySize()
{
if (this._body != null)
return this._body.Length + 4;
else
return 0;
}

public override byte[] GetBody()
{
if (this._body != null)
{
// весь размер буфера: размер + данные
int fullSize = this._body.Length + 4;
byte[] newBody = new byte[fullSize];

Buffer.BlockCopy(
BitConverter.GetBytes(this._body.Length), 0, newBody, 0, 4);
Buffer.BlockCopy(
this._body, 0, newBody, 4, this._body.Length);

return newBody;
}
else
return null;
}
}

/// <summary>
/// Контейнер для класса или массива
/// </summary>
class BinaryArray : BinaryContainer
{
public int ArrayElementCount
{
get { return this._childs.Count; }
}

public override int GetBodySize()
{
int size = 4;

if (this._childs.Count > 0)
{
foreach (BinaryContainer child in this._childs)
size += child.GetBodySize();

return size;
}
else
return size;
}

public override byte[] GetBody()
{
if (this._childs.Count > 0)
{
int bodySize = this.GetBodySize();
this._body = new byte[bodySize];

Buffer.BlockCopy(
BitConverter.GetBytes(
this.ArrayElementCount), 0, this._body, 0, 4);

int offset = 4;
foreach (BinaryContainer child in this._childs)
{
int childSize = child.GetBodySize();
byte[] childBody = child.GetBody();
Buffer.BlockCopy(
childBody, 0, this._body, offset, childSize);
offset += childSize;
}

return this._body;
}
else
return new byte[4];
}

}


Контейнер предоставляет два типа:
-тип для временного хранения простых типов (BinaryArray);
-тип для временного хранения сложных типов, таких как классы и массивы (BinaryField).
Два метода GetBodySize и GetBody предоставляют возможность для получения размера тела и сам байтовый массив. Паттерн Dependency Injection позволяет прозрачно, для основного механизма сериализации, использовать типы контейнера.

Сериализатор:


/// <summary>
/// Бинарный сериализатор
/// Поддержка следующих типов данных:
/// классы, массивы,
/// bool, short, int32, long(int64), float(single), double, Guid
/// Сереализация generic типов не поддерживается
/// </summary>
public class BinarySerializer
{
/// <summary>
/// Сериализация объекта
/// </summary>
/// <typeparam name="TType">Тип объекта</typeparam>
/// <param name="obj">Объект</param>
/// <returns>Массив байт</returns>
public byte[] Serialize<TType>(TType obj)
{
BinaryContainer binaryContainer = this.ClassSerializer(typeof(TType), obj);
byte[] result = binaryContainer.GetBody();

return result;
}

#region Хелперы для сериализации
/// <summary>
/// Сериализация объекта
/// </summary>
/// <param name="type">Тип объекта</param>
/// <param name="obj">Объект</param>
/// <returns>Временный контейнер</returns>
private BinaryContainer ClassSerializer(Type type, object obj)
{
// получение полей в типе
FieldInfo[] fInfos = type.GetFields(
BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public);

BinaryContainer classContainer = new BinaryArray();

foreach (FieldInfo fInfo in fInfos)
{
object[] customFieldAttributes =
fInfo.GetCustomAttributes(
ypeof(FieldToBinaryAttribute), false);

if (customFieldAttributes.Length == 1)
{
// получение аттрибута у типа поля
object[] customTypeAttributes =
fInfo.FieldType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);

if (customTypeAttributes.Length == 1) // поле сложного типа
{
classContainer.Childs.Add(
this.ClassSerializer(fInfo.FieldType, fInfo.GetValue(obj)));
}
else // поле простого типа
{
if (fInfo.FieldType.IsArray) // поле массив
{
//сериализация массива
classContainer.Childs.Add(
this.ArraySerializer(fInfo, obj));
}
else // поле простого типа
classContainer.Childs.Add(
this.SimpleTypeSerializer(
fInfo.FieldType, fInfo.GetValue(obj)));
}
}
}

return classContainer;
}

/// <summary>
/// Сериализация массива
/// </summary>
/// <param name="fInfo">Дескриптор поля</param>
/// <param name="obj">Объект</param>
/// <returns>Бинарный контейнер с сереализованным массивом</returns>
private BinaryContainer ArraySerializer(FieldInfo fInfo, object obj)
{
// тип элемента массива
Type elementType = fInfo.FieldType.GetElementType();
Array array = (Array)fInfo.GetValue(obj);

bool isClass = false;
// получение аттрибута у типа поля
object[] customTypeAttributes =
elementType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);

if (customTypeAttributes.Length == 1)
isClass = true;

// контейнер под хранения массива в бинарном виде
BinaryContainer binaryArray = new BinaryArray();

// испульзуем энумератор для передвижения по массиву
IEnumerator arrayEnumerator = array.GetEnumerator();
while (arrayEnumerator.MoveNext() &&
arrayEnumerator.Current != null)
{
BinaryContainer arrayElement = null;
if (isClass)
{
arrayElement =
this.ClassSerializer(
elementType, arrayEnumerator.Current);
}
else
{
// получение бинарного представления для элемента массива
arrayElement =
this.SimpleTypeSerializer(
elementType, arrayEnumerator.Current);
}

// добавление простого типа к бинарному контейнеру
binaryArray.Childs.Add(arrayElement);
}

return binaryArray;
}

/// <summary>
/// Сериализация простых типов
/// bool, short, int32, long(int64), float(single), double, Guid
/// </summary>
/// <param name="fieldType">Тип поля</param>
/// <param name="value">Значение поля</param>
/// <returns>Временный контейнер</returns>
private BinaryContainer SimpleTypeSerializer(Type fieldType, object value)
{
// буфер для хранения сериализовнных данных
byte[] buffer = null;

if (fieldType == typeof(Boolean))
{
buffer = BitConverter.GetBytes((Boolean)value);
}
else if (fieldType == typeof(Int16))
{
buffer = BitConverter.GetBytes((Int16)value);
}
else if (fieldType == typeof(Int32))
{
buffer = BitConverter.GetBytes((Int32)value);
}
else if (fieldType == typeof(Int64))
{
buffer = BitConverter.GetBytes((Int64)value);
}
else if (fieldType == typeof(Single))
{
buffer = BitConverter.GetBytes((Single)value);
}
else if (fieldType == typeof(Double))
{
buffer = BitConverter.GetBytes((Double)value);
}
else if (fieldType == typeof(Guid))
{
buffer = ((Guid)value).ToByteArray();
}

BinaryContainer tempC = null;
if (buffer != null && buffer.Length > 0)
{
tempC = new BinaryField();
tempC.Body = buffer;
}
else
throw new Exception("Ошибка в сериализациия поля типа " + fieldType.Name);

return tempC;
}
#endregion

/// <summary>
/// Десерилазиация
/// </summary>
/// <typeparam name="TType">Тип объекта</typeparam>
/// <param name="byteObj">Массив байт</param>
/// <returns>Объект заданного типа</returns>
public TType Deserialize<TType>(byte[] byteObj)
{
int offset = 0;
TType inst =
(TType)this.ClassDesirealize(
typeof(TType), byteObj, ref offset);

return inst;
}

#region Хелперы для десериализации

/// <summary>
/// Десериализация класса
/// </summary>
/// <param name="type">Тип класса</param>
/// <param name="data">Бинарные данные</param>
/// <returns>Экземпляр объекта</returns>
private object ClassDesirealize(
Type type, byte[] data, ref int offset)
{
// получение полей в типе
FieldInfo[] fInfos = type.GetFields(
BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public);

//создание объекта типа type
object obj = this.CreateInstance(type);

offset += 4; // первые 4 байта класса - количество полей

foreach (FieldInfo fInfo in fInfos)
{
object[] customFieldAttributes =
fInfo.GetCustomAttributes(
typeof(FieldToBinaryAttribute), false);

if (customFieldAttributes.Length == 1)
{
// получение аттрибута у типа поля
object[] customTypeAttributes =
fInfo.FieldType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);

object fieldValue = null;

if (customTypeAttributes.Length == 1) // поле сложного типа
{
fieldValue =
this.ClassDesirealize(
fInfo.FieldType, data, ref offset);
}
else
{
if (fInfo.FieldType.IsArray) // поле массив
{
fieldValue =
this.ArrayDeserializer(fInfo, data, ref offset);
}
else // поле простого типа
{
// массив байт для хранения размера
byte[] sizeByte = new byte[4];
Buffer.BlockCopy(
data, offset, sizeByte, 0, sizeByte.Length);
// получение размера
int size = BitConverter.ToInt32(sizeByte, 0);

// получение данных
byte[] dataBuffer = new byte[size];
Buffer.BlockCopy(
data, offset + sizeByte.Length,
dataBuffer, 0, dataBuffer.Length);

// десериализация поля
fieldValue =
this.SimpleTypeDeserializer(
fInfo.FieldType, dataBuffer);

// увелечени сдвига по буферу
offset += sizeByte.Length + size;
}
}

// помещение значение в объект
fInfo.SetValue(obj, fieldValue);
}
}

return obj;
}

/// <summary>
/// Десериализация массива
/// </summary>
/// <param name="fInfo">Дескриптор поля</param>
/// <param name="data">Массив байт</param>
/// <param name="offset">Смещение в массиве байт</param>
/// <returns>Массив</returns>
private object ArrayDeserializer(
FieldInfo fInfo, byte[] data, ref int offset)
{
// получение количества элементов в массиве
byte[] elCountByte = new byte[4];
Buffer.BlockCopy(data, offset, elCountByte, 0, elCountByte.Length);
int elementsCount = BitConverter.ToInt32(elCountByte, 0);

// увелечение счетчика движения по буферу
offset += elCountByte.Length;

bool isClass = false;
// тип элемента массива
Type elementType = fInfo.FieldType.GetElementType();
// получение аттрибута у типа поля
object[] customTypeAttributes =
elementType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);
if (customTypeAttributes.Length == 1)
isClass = true;

// создание экземпляра массива
Array array = Array.CreateInstance(elementType, elementsCount);

//получение элементов массива
for (int i = 0; i < elementsCount; i++)
{
object value = null;

if (isClass)
{
value =
this.ClassDesirealize(
elementType, data, ref offset);
}
else
{
if (elementType.IsArray)
{
throw new NotImplementedException(
"Ошибка при десериализации в массива ");
}
else
{
// получение размера элемента массива
byte[] elSizeByte = new byte[4];
Buffer.BlockCopy(
data, offset, elSizeByte, 0, elSizeByte.Length);
int elementSize = BitConverter.ToInt32(elSizeByte, 0);

offset += elSizeByte.Length;

//получение значения элемента массива
byte[] valueByte = new byte[elementSize];
Buffer.BlockCopy(
data, offset, valueByte, 0, valueByte.Length);
value = this.SimpleTypeDeserializer(elementType, valueByte);

offset += valueByte.Length;
}
}

// заполнение массива
array.SetValue(value, i);
}

return array;
}

/// <summary>
/// Создание экземпляра класса по его типу
/// </summary>
/// <param name="type">Тип</param>
/// <returns>Объект</returns>
private object CreateInstance(Type type)
{
ConstructorInfo constructor =
type.GetConstructor(new Type[] { });

object inst = null;

if (constructor != null)
{
inst = constructor.Invoke(new object[] { });
}
else
throw new Exception(
"Не возможно создать объект типа " +
type.Name +
". Объект должен иметь пустой конструктор");

return inst;
}

/// <summary>
/// Десериализация простых типов
/// bool, short, int32, long(int64), float(single), double, Guid
/// </summary>
/// <param name="fieldType"></param>
/// <param name="data"></param>
/// <returns></returns>
public object SimpleTypeDeserializer(Type fieldType, byte[] data)
{
object value = null;

if (fieldType == typeof(Boolean))
{
value = BitConverter.ToBoolean(data, 0);
}
else if (fieldType == typeof(Int16))
{
value = BitConverter.ToInt16(data, 0);
}
else if (fieldType == typeof(Int32))
{
value = BitConverter.ToInt32(data, 0);
}
else if (fieldType == typeof(Int64))
{
value = BitConverter.ToInt64(data, 0);
}
else if (fieldType == typeof(Single))
{
value = BitConverter.ToSingle(data, 0);
}
else if (fieldType == typeof(Double))
{
value = BitConverter.ToDouble(data, 0);
}
else if (fieldType == typeof(Guid))
{
value = new Guid(data);
}

if (value == null)
throw new Exception(
"Ошибка в десериализациия поля типа " +
fieldType.Name);

return value;
}

#endregion

}

30.03.2009

Каждому экшену - свое место

Итак, определившись с типом экшена, который мы хотим включить в инсталляцию, необходимо определиться с тем местом инсталляции, в котором его необходимо выполнить. В msi существует две таблицы в которые можно размещать экшены - InstallUISequence и InstallExecuteSequence. Действия, определенные в таблице InstallUISequence выполняются в самом начале установки и как правило именно в этой таблице описывается последовательность пользовательских диалогов, которая будет выводиться для сбора сведений об инсталляции от пользователя. При размещении своих экшенов в этой таблице надо помнить об одной важной вещи - существует "тихий" режим инсталляции, в котором таблица InstallUISequence не обрабатывается. Поэтому если экшен должен выполниться в любом случае, то логично разместить его в таблице InstallExecuteSequence. Действия в таблице InstallExecuteSequence по сути отвечают за сам процесс установки, именно они копируют файлы, ведут записи в реестре и т.д. И как правило именно при их выполнении пользователь видит перед собой диалоговое окно с прогресс-баром. Но при размещении в этой таблице своих экшенов надо помнить об опциях выполнения, которые в WIX определяются атрибутом "Execute" тега "CustomAction". Важнейшими из них являются deferred и immediate. Дело в том, что установка продукта не происходит "в один проход". На определенном этапе, а если точнее, то между действиями InstallInitialize и InstallFinalize происходит запись скрипта инсталляции и те экшены, которые помечены как deferred не выполняются сразу, а записываются в скрипт и исполняются после InstallFinalize, в то время как immediate-экшены выполняются всегда в тот момент, когда приходит их очередь по таблице. Отсюда следуют несколько интересных выводов: во-первых, deferred-экшен не может быть поставлен никуда кроме как между InstallInitialize и InstallFinalize. Во-вторых, если поставить immediate-экшен и deferred-экшен в одно место после копирования файлов, то на момент выполнения immediate-действия файлы еще скопированы не будут, потому как копируются во время исполнения скрипта, а не во время его записи, соттветственно deferred-экшен сможет использовать эти файлы. То же самое относится и к другим изменениям, выполняющимся в deferred-фазу.


Также важным моментом является использование rollback-экшенов. Эти экшены создаются с использованием значения "rollback" для атрибута "Execute". Такие экшены выполняются в том случае, если во время инсталляции произошла ошибка и производится откат. Использовать их следует в паре с написанными кастом-экшенами для отката изменений, ибо сам инсталлер не откатывает то, что делают кастом-экшены.
Порядок выполнения экшенов в таблицах задается целыми числами. В WIX эти числа можно определять как напрямую через атрибут "Sequence" тега "Custom", либо через атрибуты "Before" и "After", задавая перед или после какого экшена выполняться текущему действию.