Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware
VPS/VDS серверы. 30 локаций на выбор

Серверы VPS/VDS с большим диском

Хорошие условия для реселлеров

4VPS.SU - VPS в 17-ти странах

2Gbit/s безлимит

Современное железо!

Бесплатный конструктор сайтов и Landing Page

Хостинг с DDoS защитой от 2.5$ + Бесплатный SSL и Домен

SSD VPS в Нидерландах под различные задачи от 2.6$

✅ Дешевый VPS-хостинг на AMD EPYC: 1vCore, 3GB DDR4, 15GB NVMe всего за €3,50!

🔥 Anti-DDoS защита 12 Тбит/с!

2007 г.

Интерфейс Siebel => Oracle Server => Express Server

Антон Шмаков, старший консультант отдела бизнес-анализа и хранилищ данных, Консалтинговая группа "Борлас" (Москва)
Источник: Oracle Magazine - Русское издание

Долгое время Oracle Express, семейство OLAP-продуктов от Oracle, было лидером в области многомерных баз данных. Кроме стандартных средств OLAP-сервера (Express Server) оно обладает рядом важных и отличительных особенностей, таких как модели, формулы и, самое главное, собственным языком программирования - Express Language, а также рядом инструментов для их использования. В целом, для своего времени прикладные аналитические системы на Oracle Express работали достаточно быстро и эффективно. Одним из самих успешных из них стал продукт Oracle Financial Analyzer (OFA), предназначенный для формирования финансовой отчетности, проведения детального финансового анализа, ведения бюджета, финансового планирования и прогнозирования. Этот продукт получил широкое применение, в частности OFA был включен в состав финансовых модулей семейства Oracle E-Business Suite, и легко интегрировался с ними. В 2000 году корпорацией Oracle было принято решение о "переносе" Express Server в состав реляционной СУБД Oracle 9i, и таким образом в ней появилась OLAP Option. Вместо OFA, для работы в среде этой опции было предложено новое финансовое приложение - Oracle Enterprise Budgeting & Planning (EPB). Но и по сей день OFA (и другие аналитические приложения для среды Oracle Express) продолжают использоваться во многих компаниях.

Многие компании на сегодняшний день используют BI-приложения для сбора и анализа корпоративной информации, хранящийся в различных источниках. Для получения единого взгляда на всю информацию предприятия необходимо создание единой модели ее представления. Несомненно, Express Server и OFA являются важным компонентом в общей модели данных.

Один из клиентов компании "Борлас" решил внедрить у себя систему корпоративной отчетности на основе Oracle Business Intelligence Enterprise Edition. Среди множества всех источников, которые использовались для построения единой модели данных в компании была система бюджетирования OFA. Таким образом, пред нами встала задача интеграции OFA и Oracle BI Suite EE. Поскольку OFA является, по сути, надстройкой над Express Server, мы стали решать общую задачу интеграции Express Server и Oracle BI Suite EE.

Реализация интерфейса Oracle Server => Express Server

На сегодняшний день Oracle предлагает две аналитические платформы: Standard Edition, бывший Oracle Business Intelligence 10g, и Enterprise Edition, интегрированная платформа для реализации различных методов анализа данных, основанная на платформе Siebel Analytics. Кроме того, существует редакция Oracle Business Intelligence Standard Edition One являющаяся сокращенной версией Enterprise Edition для среднего и малого бизнеса.

Oracle BI Suite Standard Edition состоит из двух основных компонентов:

  • Oracle Discoverer и
  • Oracle Reports.

Oracle Discoverer позволяет строить произвольные отчеты и формировать нерегламентированные запросы. Он легко интегрируется с СУБД Oracle, в том числе с опцией OLAP. Однако он не имеет никаких адаптеров к Express Server и поэтому не может работать с ним. С другой стороны Oracle Reports, компонент для создания и публикации стандартных регламентированных отчетов, предоставляет доступ к различным источникам данных (SQL, PL/SQL, JDBC, XML-файлы и др.), включая готовый адаптер к Express Server.

Oracle BI Suite Enterprise Edition состоит из набора различных компонентов. В основе лежит Oracle BI Server - сервер, обеспечивающий доступ к источникам данных и представляющий их в виде единой модели данных. Он имеет открытый и расширяемый набор адаптеров, отвечающих за связь с источниками данных. Были созданы индивидуальные адаптеры к различным системам, включая реляционные СУБД (Oracle, DB2, SQL Server, Teradata, Informix и др.), корпоративные приложения (OEBS, Peoplesoft, JD Edwards, SAP R/3, mySAP), OLAP-источники (Oracle OLAP, Microsoft Analysis, SAP BW, Hyperion), XML-источники. Однако адаптеров к Oracle Express не было создано.

Семейство программных продуктов Oracle Express состоит из сервера многомерных баз данных (Express Server, Personal Express), инструментов для администрирования (Express Administrator), инструментальные средства для разработки сложных интегрированных клиентских приложений (Express Analyzer, Express Objects), средства для публикации данных в Интернете (Web Publisher) и многое другое. На рисунке 1 приведена архитектура семейства Oracle Express.


Рис. 1 Архитектура семейства Oracle Express.

В состав семейства продуктов Oracle Express включены следующие интерфейсы:

  • XCA (eXpress Communications Architecture) - протокол, позволяющий двум Express-системам обмениваться данными;
  • EWA (Express Web Agent) - средство для создания приложений, работа с которыми осуществляется через Web-браузер, обеспечивающий работу с таблицами. Набор хранимых процедур Web Agent Developer's Toolkit обеспечивает базовую архитектуру для построения развитых Web-приложений;
  • SNAPI (Structured N-dimensional Application Programming Interface) - протокол для работы с многомерной базой данных Express. Представляет собой набор функций и переменных реализованных на языке "C" и помещенный в DLL-библиотеки, позволяющий писать свои собственные приложения.

Следует отметить, что клиентские приложения Express Analyzer, Express Objects, Web Publisher используют в своей работе XCA и/или SNAPI. XCA является закрытым протоколом для внутренней работы самого сервера базы данных и для наших целей не подходит. EWA - это дополнение к Express Server, основной функцией которого является построение данных в Web формате. На рисунке 2 показана схема работы EWA.


Рис. 2. Cхема работы Express Web Agent

Таким образом, на роль интерфейса доступа к Express из внешних систем и, в частности, Oracle Server подходит только SNAPI.

Поскольку Oracle BI Suite EE может работать с любым ODBC-источником, самым простым решением поставленной задачи было бы создание собственного ODBC-драйвера. Для этого можно воспользоваться готовыми коммерческими библиотеками (Software Developer Kit) для написания ODBC-драйверов. Мы сделали простой ODBC-драйвер на базе SimbaEngine SDK от компании Simba Technologies. При реализации базовых методов мы столкнулись со следующими трудностями:

  • представление многомерных данных в "плоском" реляционном виде;
  • преобразование SQL-запросов в многомерные запросы Express;
  • поддержка агрегатных функций SQL и сложных преобразований и многое другое.

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

Oracle BI Suite EE предоставляет интеграцию с множеством продуктов от Oracle, включая всестороннюю поддержку СУБД Oracle Database 10g. Таким образом, если мы бы могли представить данные из Express как таблицы Oracle, то задача была бы решена. Для этого нам требовалось опубликовать Express-кубы в виде представлений (view) на основе табличных функций (table function), которые получали данные из Express Server. Поскольку в состав Oracle Reports входит адаптер к Express Server, мы решили воспользоваться им. Адаптер реализован на Java c использованием технологии Oracle Reports Pluggable Data Source (PDS), позволяющей подключать к Oracle Reports произвольные источники данных. PDS - набор интерфейсов на Java (API). Он описан в Oracle Reports Java API Reference. В состав Oracle Reports входят готовые PDS-адаптеры к XML файлам, JDBC источникам и самое главное к Express Server.

Схема работы PDS-адаптеров к Express Server приведены на рисунке 3.


Рис. 3. Схема работы PDS-адаптеров к Express Server

SNAPI драйвер к Express Server представляет собой библиотеку на языке "C". Для того, чтобы можно было воспользоваться методами этого драйвера в Java, Oracle предложил специальную библиотеку Express Java (XRJ - eXpRess Java), которая является своеобразным мостиком между "натуральным" (компилированным в бинарный код) SNAPI-драйвером и современным Java-драйвером. Библиотека XRJ подключается в Java-пакете Express PDS и осуществляет все взаимодействие с SNAPI-драйвером. Далее будем обозначать как SNAPI/XRJ-драйвер связку из SNAPI-драйвера и XRJ-библиотеки. Аналогичная связка работает и еще в одном продукте Express Spreadsheet Add-In.

Публикация данных из Express Server как представлений в базе данных позволяла решить не только конкретную задачу реализации интерфейса Oracle BI Suite EE и Express Server, но позволяла включить Express Server в общую информационную среду через СУБД Oracle. Кроме того, такой подход оказался простым, эффективным и технологичным, поскольку всей обработкой занималась СУБД Oracle. Благодаря серьезной поддержке Java и широким возможностям самой СУБД Oracle, которая позволяют создавать конвейерные табличные функции на Java, решение задачи построения интерфейса Express Server - Oracle Server оказалось очень изящным.

Рассмотрим более подробно предлагаемое решение, построенное на следующих базовых продуктах: Oracle Express Server 6.4, Oracle Database 10g, Oracle Express PDS 10g.


Рис. 4.  

Как и в Oracle Reports, в основе решения лежит: SNAPI-драйвер, предоставляющий доступ к базам данным Express Server и специальная XRJ-библиотека, предоставляющая к нему Java-интерфейс. XRJ-библиотека имеет свой собственный синтаксис запросов, (похожий на синтаксис конфигурационных файлов), в котором указывается база данных Express Server, показатели, измерения, иерархии, ограничения на измерения и многое другое. Для построения таких запросов нами было сделано специальное приложение Express SNAPI Query Builder, использующее схожие с Express мастера построения запросов и селектор. С помощью этого приложения можно легко получать запросы к Express в формате XRJ-библиотеки.

Начиная с версии 8i в СУБД Oracle включена полноценная Java-машина, Oracle JVM. На сегодняшний день СУБД Oracle позволяет создавать, хранить и выполнять Java-приложения внутри себя. Кроме того, важным является то, что PL/SQL и Java код могут спокойно сосуществовать вместе в одном приложении. Однако СУБД Oracle запрещает использование JNI (стандартный интерфейс программирования для написания "натуральных", т.е. компилирующихся в бинарный код целевой операционной системы, методов) Несмотря на то, что использование JNI в большинстве случаев ведет к потере многоплатформенности Java-кода, данная возможность расширяет сферу применения самого языка Java на приложения, для которых это условие не является необходимым. В таких системах использование JNI позволяет сочетать современный объектно-ориентированный подход Java с существующим (LEGACY) системно-зависимым кодом на С/С++, каким и является драйвер SNAPI/XRJ. JNI-интерфейс использовалась в Oracle Reports, чтобы подключить XRJ-библиотеку в Java PDS-драйвер. Нам же пришлось реализовать клиент-серверный подход, когда сервер, стоящий отдельно от базы данных, может подключать любые библиотеки, а клиент еще взаимодействует с сервером.

Таким образом, нами был сделан промежуточный буферный сервер, который по JNI подключал SNAPI-драйвер. В его задачи входило получение запросов от клиентов и переправка их дальше SNAPI/XRJ-драйверу, который разбирал этот запрос и выдавал обратно результаты. Далее буферный сервер отдавал результаты клиенту.

В качестве реализации клиент-серверной технологии была выбрана RMI-архитектура. Система Remote Method Invocation (RMI) дает возможность объектам, выполняющимся на одной Java-машине, вызывать методы объекта, выполняющегося на другой. RMI обеспечивает функционал для удаленных коммуникаций между программами, написанными на языке Java.

Для представления кубов в виде таблиц были использованы табличные функции, особый тип функций PL/SQL, которые возвращают не скалярное значение, а массив элементов произвольной структуры. Причем, имеются два вида табличных функций: возвращающие результат в виде массива и контролирующие обработку возвращаемой выборки с помощью реализации интерфейса ODCITable. В нашем решении мы использовали именно ODCITable-интерфейс, поскольку это позволяло реализовать конвейерные табличные функции и получить высокую производительность. Для представления кубов в виде таблиц необходимо было создать специальные структуры в СУБД Oracle, которые представляли результаты выборки данных из Express кубов. Здесь возможно два варианта: либо создается один большой тип, состоящий их максимального числа столбцов всех типов, либо для каждого куба создается свой собственный тип. Далее создаются представления или материализованные представления в СУБД Oracle, которые, используя конвейерные табличные функции, возвращают выборки из кубов.

После того, как Express-кубы, в том числе из OFA, опубликованы как представления (материализованные представления) в СУБД Oracle, на их основе можно строить отчеты средствами Oracle BI Suite EE.

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

Рассмотрим пример построения отчета в Oracle BI Suite EE на основе тестовой базы данных, которая поставляется с Oracle Express - demo.db. Возьмем куб - SALES, построенный по трем измерениям: MONTH, PRODUCT, DISTRICT. В представленных ниже листингах отсутствует Java-код для шлюзового RMI-сервера, RMI-клиента, поскольку они достаточно большие и сложные.

Создадим для него соответствующие структуры в СУБД Oracle:

-----------------------------------------------
-- Спецификация объектного типа для ячейки куба
-----------------------------------------------
create or replace type t_sales_row as object (
      sales  number,
      month  date,
      product  varchar2(30),
      district varchar2(30)
);
-----------------------------------------------
-- Спецификация табличного типа
-----------------------------------------------
create or replace type t_sales_table as table of t_sales_row;

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

-----------------------------------------------
-- ODCITable тип
-----------------------------------------------
create or replace type t_sales_rowset as object (
key integer,

-- Статическая функция, необходимая для создания контекста
-- query - эапрос в Express
static function ODCITableStart(sctx out t_sales_rowset, query varchar2)
return number
as language java name 'SalesRowset.ODCITableStart(	oracle.sql.STRUCT[],
java.lang.String) 
return java.math.BigDecimal',

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

	member function ODCITableFetch(self in out t_sales_rowset, nrows in number,
						outset out t_sales_table)
return number
as language java name 'SalesRowset.ODCITableFetch(	java.math.BigDecimal,
							oracle.sql.ARRAY[])
							return java.math.BigDecimal',

	-- Метод экхемпляра, необходимый для закрытия контекста

	member function ODCITableClose(self in t_sales_rowset)
		return number
		as language java name 'SalesRowset.ODCITableClose() return java.math.BigDecimal'
);

Теперь нам надо создать Java пакет SalesRowset опишем только основные методы, которые необходимы для работы ODCITable типа.

Первая функция ODCITableStart, необходимая для создания контекста и открытия курсора. Для нее входящим параметром будет Express запрос.

static public BigDecimal ODCITableStart(STRUCT[] sctx, String query) throws SQLException {
// Соединение текущей сессии
Connection conn = new OracleDriver().defaultConnection();
// Создаем контекст запроса, StoredCtx - произвольный класс, его определяем мы сами
	StoredCtx ctx = new StoredCtx(query);
	int key;
	try {
		// Создаем ключ для нашего контекста
		key = ContextManager.setContext(ctx);
	} catch (CountException ce) {
		return ERROR;
	}
	Object[] impAttr = new Object[1];
	impAttr[0] = new BigDecimal(key);
	// Создаем дескриптор для нашего ODCITable типа в базе
	StructDescriptor rowset = StructDescriptor.createDescriptor("T_SALES_ROWSET", conn);
	// Заполняем тип 

	sctx[0] = new STRUCT(rowset, conn, impAttr);
:
	// Далее мы обращаемся к RMI серверу и отправляем запрос Express
:
	return SUCCESS;
}

Вторая функция ODCITableFetch необходима для получения новой порции данных из выборки. Для нее входящим параметром будет Express запрос.

public BigDecimal ODCITableFetch(BigDecimal nrows, ARRAY[] outSet) throws SQLException {
StoredCtx ctx;
	try {
		// Получаем контекст по ключу
		ctx = (StoredCtx) ContextManager.getContext(key.intValue());
	} catch (InvalidKeyException ik) {
		return ERROR;
	}
	Connection conn = new OracleDriver().defaultConnection();
	try {
	
		ArrayList list = _srv.fetchQuery(ctx.nQueryId, nrowsval);

		StructDescriptor cube_row = StructDescriptor.
createDescriptor("T_SALES_ROW", conn);
		ArrayDescriptor cube_table = ArrayDescriptor.
createDescriptor("T_SALES_TABLE",conn);
ArrayList list = null;
:
		// Получаем в list через обращение к RMI серверу очередную порцию выборки
		// Пусть она возвращается в виде 
		// { SALES, MONTH , PRODUCT, DISTRICT}, соответствующим типу t_sales_row 

: Object[] table = new Object[list.size()]; int i = 0; // Проходим по всему массиву и заполняем структуру for (Iterator it = list.iterator(); it.hasNext();) { table[i++] = new STRUCT(_cube_row, _conn, (Object[]) it.next()); } // Создаем массив из структуру outSet[0] = new ARRAY(cube_table, conn, table); table = null; list.clear(); list = null; return SUCCESS; } catch (Exception e) { e.printStackTrace(); System.out.println(e.toString()); } }

Последняя функция ODCITableClose необходима для закрытия курсора и контекста.

public BigDecimal ODCITableClose() throws SQLException {
StoredCtx ctx;
	try {
		ctx = (StoredCtx) ContextManager.clearContext(key.intValue());
	} catch (InvalidKeyException ik) {
		return ERROR;
	}
:
	// Обращаемся к RMI серверу и закрываем курсор к Express
:

	ctx = null;
	return SUCCESS;
}

Теперь можно создать конвейерную табличную функцию, для которой тип T_SALES_ROWSET будет реализующим ее.

----------------------------------------------
-- Конвейерная функция на основе t_sales_rowset
-----------------------------------------------
create or replace function getExpressSales(query varchar2)
	return t_sales_table
	pipelined using t_sales_rowset;

Теперь для того, чтобы создать представление в СУБД на основе этой функции, нам надо сформировать запрос к Express Server. Для создания SNAPI запросов мы создали специальное приложение Express SNAPI Query Builder, с помощью которого мы соединяемся с Express и строим с помощью стандартного Express мастера запрос.

В итоге получаем запрос к Express Server в следующем виде:

DB0=DEMO.DB\
DBCount=1\
MeasureCount=1\
Measure0=SALES\
E0Count=2\
E1Count=1\
E2Count=1\
E0Dim0Name=XP_MEASUREDIM\
E0Dim0Script=CALL XP_SLLIMIT('XP_MEASUREDIM', 'CUBE','SALES')\
E0Dim1Name=MONTH\
E0Dim1Script=call XP_SELEVALUATE( 'MONTH', NA )\
E0Dim1DimLName=MONTH\
E0Dim1Hier=\
E1Dim0Name=PRODUCT\
E1Dim0Script=call XP_SELEVALUATE( 'PRODUCT', NA )\
E1Dim0DimLName=PRODUCT\
E1Dim0Hier=\
E2Dim0Name=DISTRICT\
E2Dim0Script=call XP_SELEVALUATE( 'DISTRICT', NA )\
E2Dim0DimLName=DISTRICT\
E2Dim0Hier=\
QL=1\

Создаем на его основе представление

----------------------------------------------
-- Представление для SALES
-----------------------------------------------
create or replace view v_express_sales as select * from table(getExpressSales('DB0=DEMO.DB\
DBCount=1\MeasureCount=1\Measure0=SALES\E0Count=2\E1Count=1\E2Count=1\E0Dim0Name=XP_MEASUREDIM\
E0Dim0Script=CALL XP_SLLIMIT(''XP_MEASUREDIM'', ''CUBE'',''SALES'')\E0Dim1Name=MONTH\
E0Dim1Script=call XP_SELEVALUATE( ''MONTH'', NA )\E0Dim1DimLName=MONTH\
E0Dim1Hier=\E1Dim0Name=PRODUCT\E1Dim0Script=call XP_SELEVALUATE( ''PRODUCT'', NA )\
E1Dim0DimLName=PRODUCT\E1Dim0Hier=\
E2Dim0Name=DISTRICT\E2Dim0Script=call XP_SELEVALUATE( ''DISTRICT'', NA )\
E2Dim0DimLName=DISTRICT\E2Dim0Hier=\QL=1\'));

Результат запроса

select * from v_express_sales;

     SALES MONTH       PRODUCT                   DISTRICT
---------- ----------- ------------------------- -------------------------
  32153,52 31.01.1995  TENTS                     BOSTON
   32536,3 28.02.1995  TENTS                     BOSTON
  43062,75 31.03.1995  TENTS                     BOSTON
  57608,39 30.04.1995  TENTS                     BOSTON
  81149,36 31.05.1995  TENTS                     BOSTON
  88996,35 30.06.1995  TENTS                     BOSTON
  87273,84 31.07.1995  TENTS                     BOSTON
  89379,13 31.08.1995  TENTS                     BOSTON
  71388,47 30.09.1995  TENTS                     BOSTON
  66412,33 31.10.1995  TENTS                     BOSTON
  66013,92 31.01.1995  CANOES                    BOSTON
  76083,84 28.02.1995  CANOES                    BOSTON
  91748,16 31.03.1995  CANOES                    BOSTON
 125594,28 30.04.1995  CANOES                    BOSTON
 126713,16 31.05.1995  CANOES                    BOSTON
 147412,44 30.06.1995  CANOES                    BOSTON
 152727,12 31.07.1995  CANOES                    BOSTON
 126433,44 31.08.1995  CANOES                    BOSTON
 122797,08 30.09.1995  CANOES                    BOSTON

87272,64 31.10.1995 CANOES BOSTON

Создадим отчет в Oracle BI EE Answers с помощью Direct Request.

Представим результаты запроса в виде кросс-таблицы

Заключение

Построенное нами решение позволяет осуществлять прямой доступ к Express Server из СУБД Oracle. Конечно, у такого решения есть свои ограничения. Во-первых, это ограничения, связанные с работой связки SNAPI/XRJ и SNAPI интерфейса Express сервера, а во-вторых, необходимо наличие СУБД Oracle 10g и Oracle Reports 10g. В целом же оно позволяет строить системы, в которых Express Server и OFA, в частности, будут уже не отдельно стоящими приложениями, а интегрированными в общую информационную среду. Причем решение локальной задачи реализации интерфейса OFA -> Oracle BI Suite EE, позволило нам создать общий подход для подключения Express Server к любым приложениям через СУБД Oracle.Таким приложением может быть, например Oracle Discoverer, входящий в состав Oracle BI Suite Standard Edition. Важным является и то, что теперь Express Server можно включать в любые ETL-процессы, т.е. теперь Express становится полноценным источником для построения хранилища данных.

Отдельный самостоятельный ODBC-драйвер или любой другой драйвер стандарта доступа к данным JDBC, ODBO или XMLA, конечно более универсален, поскольку может работать напрямую и иметь свой собственный движок и промежуточный слой. Еще одним важным плюсом отдельного драйвера отсутствие требования в СУБД Oracle 10g и Oracle Reports 10g.

VPS в 21 локации

От 104 рублей в месяц

Безлимитный трафик. Защита от ДДоС.

🔥 VPS до 5.7 ГГц под любые задачи с AntiDDoS в 7 локациях

💸 Гифткод CITFORUM (250р на баланс) и попробуйте уже сейчас!

🛒 Скидка 15% на первый платеж (в течение 24ч)

Скидка до 20% на услуги дата-центра. Аренда серверной стойки. Colocation от 1U!

Миграция в облако #SotelCloud. Виртуальный сервер в облаке. Выбрать конфигурацию на сайте!

Виртуальная АТС для вашего бизнеса. Приветственные бонусы для новых клиентов!

Виртуальные VPS серверы в РФ и ЕС

Dedicated серверы в РФ и ЕС

По промокоду CITFORUM скидка 30% на заказ VPS\VDS

Новости мира IT:

Архив новостей

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 495 7861149
Пресс-релизы — pr@citforum.ru
Обратная связь
Информация для авторов
Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2019 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...