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, указывая в качестве репозитория наш сервер.