Меню

Интеграция SAP NetWeaver Portal 7.3 и MS Exchange 2010/2013

Многие заказчики при внедрении корпоративного портала выдвигают в числе обязательных требований удобную интеграцию с существующей корпоративной информационной структурой. В состав продукта SAP NetWeaver Portal с самого начала входили инструменты интеграции рабочей среды сотрудничества (Collaboration) с MS Exchange. Однако, почти все поставляемые инструменты (Collaboration) не поддерживают протоколы продуктов Microsoft, начиная с версии MS Exchange 2010. В статье рассматривается реализация интеграции SAP Portal с новыми версиями MS Exchange, приводится рабочий код.

История вопроса

Одним из требований, возникающих при внедрении корпоративных порталов, является их тесная интеграция с уже развернутой структурой организации – электронной почтой, персональными средствами планирования и управления рабочим графиком. В первую очередь это наиболее распространенные системы – Microsoft Exchange и Lotus Notes. Пользовательские функции взаимодействия с персональной почтой и календарем поставляются в составе рабочей среды сотрудничества (пакет Collaboration) в виде набора готовых iView и страниц. Для обеспечения универсальности разработанных визуальных интерфейсов в архитектуру NetWeaver был добавлен дополнительный транспортный слой. То есть взаимодействие с конечными интерфейсами корпоративных почтовых систем происходит через выделенный программный объект, преобразующий вызовы набора стандартных методов библиотеки SAP NetWeaver в специфичные протоколы и стандарты, поддерживаемые целевыми системами.

Для интеграции SAP Portal с MS Exchange существовали следующие возможности:

  • Взаимодействие с интерфейсом Outlook Web Access (OWA)
  • Использование CDO (Collabarative Data Objects);
  • Использование протокола WebDAV.

Первый способ исторически появился еще в SAP Portal 6.0 и не использует каких-либо транспортных слоев. Однако его использование сопряжено с рядом неудобств (например, сложностями сквозной авторизации) и фактически является внедрением интерфейса OWA внутрь портала. Для более полной интеграции и контроля были разработаны новые средства, указанные выше – интеграция посредством CDO и WebDAV, реализованная через транспортный слой. Наиболее часто из двух указанных применялась интеграция через WebDAV, поскольку она требовала меньшего количества действий по настройке и конфигурированию на стороне Exchange.

Однако, начиная с версии MS Exchange 2010, компания Microsoft объявила об отказе от поддержки этих средств коммуникации, введя единый интерфейсный стандарт на основе веб-сервисов – EWS (Exchange Web Services). В результате интеграция стандартными средствами SAP Portal с версиями MS Exchange 2010/2013 стала невозможна. Наша проектная команда столкнулась именно с такой ситуацией, потребовавшей разработки собственного интерфейса интеграции с использованием EWS. Далее будут описаны основные шаги по реализации собственного транспортного объекта.

Реализация объекта транспорта

Имеющиеся способы подключения портала (CDO, WebDAV) использовали механизм транспорта. Это определило техническую архитектуру – объект транспорта и формат файла – .sda. Дополнительное внутреннее требование – использование DC и NWDI.

Для работы с Exchange используется код из внешнего проекта «EWS JAVA API» (http://archive.msdn.microsoft.com/ewsjavaapi). На момент написания статьи проект имел версию 1.2. Для работы этого проекта необходимы дополнительные файлы, о чем прямо говорится в файле «Compiling the EWS Java API.RTF». Мы использовали commons-codec-1.8.jar, commons-httpclient-3.1.jar, commons-logging-1.1.3.jar, jcifs-1.3.17.jar, поместив их в DC типа «library». Основной проект использовал public part типа «compilation» и «assembly». В итоге эти файлы вошли в состав конечного sda-пакета.

Описание вспомогательного сервиса

Для работы транспорта нужно, чтобы .jar с ним был постоянно загружен в память и таким образом являлся доступным загрузчику классов. Это обеспечивается запуском вспомогательного портального сервиса. Вот как выглядит его описание в portalapp.xml:

<service name="ExchangeTransportService">

  <service-config>

    <property name="className" value="ru.energodata.ExchangeTransportService"/>

    <property name="classNameFactory" value=""/>

    <property name="classNameManager" value=""/>

    <property name="poolFactory" value="0"/>

    <property name="startup" value="true"/>

  </service-config>

  <service-profile>

    <property name="generic_service_key" value="ru.energodata.ExchangeTransportService"/>

    <property name="generic_classloader_registration" value="yes"/>

    <property name="generic_so_registration" value="no"/>

    <property name="proxy" value=""/>

  </service-profile>

</service>

Ключевыми являются следующие моменты:

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

Свойство «generic_classloader_registration» установлено в «yes», что позволяет избежать написания кода регистрации, воспользовавшись уже готовым функционалом. Подробнее об этом также далее по тексту.

Сам транспорт – это класс, созданный соответствующим мастером. Вот как выглядит интерфейсная часть:

package ru.energodata;

import com.sapportals.portal.prt.service.IService;

public interface IExchangeTransportService extends IService

{

    public static final String KEY = "ru.energodata~exchtransp.ExchangeTransportService";

    public String getProxy();

}

Вот так выглядит реализация:

package ru.energodata;

import com.sap.ip.collaboration.core.api.fwk.portal.GenericService;

import com.sapportals.portal.prt.service.IServiceContext;

public class ExchangeTransportService extends GenericService implements IExchangeTransportService

{

      // сервис нужен для регистрации ClassLoader-а

      // регистрация происходит в super, т.к. включен параметр

//"generic_classloader_registration"

      private IServiceContext _context;

      @Override

      public String getProxy()

      {

            return _context.getServiceProfile().getProperty("proxy");

      }

      @Override

      public void init(IServiceContext ctxt)

      {

            _context = ctxt;

            super.init(ctxt);

      }

}

Важный момент – класс наследуется от GenericService. Именно в нем находится код регистрации, который вкупе со свойством «generic_classloader_registration» позволяет не писать код этот код у себя.

Свойство «proxy» и возвращающий его метод «getProxy» необходим, если выход на веб-сервис Exchange идет через прокси-сервер. В нашем случае подключение прямое, поэтому свойство пустое.

  • Описание программного интерфейса транспорта

Портал при запуске создает экземпляр класса средствами reflection. Транспорт – класс, реализующий ряд интерфейсов и унаследованный от класса «AbstractTransport»:

package ru.energodata;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Locale;

import java.util.Map;

import java.util.Properties;

import com.sap.ip.collaboration.gw.api.enums.GroupwareItemType;

import com.sap.ip.collaboration.gw.api.enums.TransportType;

import com.sap.ip.collaboration.gw.api.framework.groupware.AbstractTransport;

import com.sap.ip.collaboration.gw.api.framework.groupware.GroupwareException;

import com.sap.ip.collaboration.gw.api.framework.groupware.IAttachment;

import com.sap.ip.collaboration.gw.api.framework.groupware.ICalendarItem;

import com.sap.ip.collaboration.gw.api.framework.groupware.ICalendarReadTransport;

import com.sap.ip.collaboration.gw.api.framework.groupware.ICalendarSendTransport;

import com.sap.ip.collaboration.gw.api.framework.groupware.IDateRange;

import com.sap.ip.collaboration.gw.api.framework.groupware.IGWResourceManager;

import com.sap.ip.collaboration.gw.api.framework.groupware.IGroupwareCredentials;

import com.sap.ip.collaboration.gw.api.framework.groupware.IGroupwareItem;

import com.sap.ip.collaboration.gw.api.framework.groupware.ISupportability;

@SuppressWarnings("unchecked")

public class ExchangeCalendarTransport extends AbstractTransport implements ICalendarReadTransport, ICalendarSendTransport, ISupportability

{

      private static String transportId = "ru.energodata.ExchangeCalendarTransport";

      private IGWResourceManager _manager;

      private List _configurations;

      private String _alias = "TransportErrorSeeLog";

      @Override

      public String getServerAlias()

      {

            return _alias;

      }

      @Override

      public GroupwareItemType getSupportedItemTypeEnum()

      {

            return GroupwareItemType.CALENDAR;

      }

      @Override

      public String getTransportDescription(Locale locale)

      {

            return "EWS Microsoft Exchange";

      }

      @Override

      public String getTransportName()

      {

            return transportId;

      }

      @Override

      public TransportType getTransportTypeEnum()

      {

            return TransportType.BOTH;

      }

      @Override

      public void initialize(IGWResourceManager manager, List configurations) throws GroupwareException

      {

            _manager = manager;

            _configurations = configurations;

            _alias = ExchangeCore.getAlias(configurations);

      }

      @Override

      public void terminate() throws GroupwareException

      {

      }

      @Override

      public Map getAvailabilityInfo(IDateRange range, int timeInterval, List addresses, IGroupwareCredentials credential) throws GroupwareException

      {

            ExchangeCore core = null;

            try

            {

                  HashMap res = new HashMap();

                  for (int i = 0; i < addresses.size(); i++)

                  {

                        String address = (String)addresses.get(i);

                        core = new ExchangeCore(_manager, _configurations, credential, address);

                        res.put(address, core.getAvailabilityInfo(range, timeInterval));

                  }

                  return res;

            }

            catch (Exception e)

            {

                  throw Exceptions.processing(e, core);

            }

      }

      @Override

      public IAttachment getAttachmentContent(String itemId, String attachmentId, IGroupwareCredentials credential) throws GroupwareException

      {

            throw new UnsupportedOperationException("Method getAttachmentContent() not yet implemented.");

      }

      @Override

      public String getContent(String arg0, IGroupwareCredentials arg1) throws GroupwareException

      {

            throw new UnsupportedOperationException("Method getContent() not yet implemented.");

      }

      @Override

      public IGroupwareItem getItem(String itemId, IGroupwareCredentials credential) throws GroupwareException

      {

            ExchangeCore core = null;

            try

            {

                  core = new ExchangeCore(_manager, _configurations, credential);

                  IGroupwareItem res = core.getItem(itemId);

                  if (res != null)

                        return res;

                  throw new Exception("getItem вернул null");

            }

            catch (Exception e)

            {

                  throw Exceptions.processing(e, core);

            }

      }

      @Override

      public List getItemList(IDateRange range, IGroupwareCredentials credential) throws GroupwareException

      {

            ExchangeCore core = null;

            try

            {

                  core = new ExchangeCore(_manager, _configurations, credential);

                  return core.getItemList(range);

            }

            catch (Exception e)

            {

                  throw Exceptions.processing(e, core);

            }

      }

      @Override

      public List getItemList(IDateRange range, Properties searchCriteria, IGroupwareCredentials credential, int nCount) throws GroupwareException

      {

            return new ArrayList();

      }

      @Override

      public GroupwareItemType getSupportedItemType()

      {

            return getSupportedItemTypeEnum();

      }

      @Override

      public TransportType getTransportType()

      {

            return getTransportTypeEnum();

      }

      @Override

      public void remove(String id, IGroupwareCredentials credential, boolean isSeries) throws GroupwareException

      {

      }

      @Override

      public void remove(String id, IGroupwareCredentials credential) throws GroupwareException

      {

      }

      @Override

      public String save(IGroupwareItem item, IGroupwareCredentials credential) throws GroupwareException

      {

            return "";

      }

      @Override

      public String send(IGroupwareItem item, IGroupwareCredentials credential) throws GroupwareException

      {

            ExchangeCore core = null;

            try

            {

                  core = new ExchangeCore(_manager, _configurations, credential);

                  if (item instanceof ICalendarItem)

                        core.send ((ICalendarItem) item);

                  else

                        throw new Exception("item не ICalendarItem");

            }

            catch (Exception e)

           

Если хотите прочитать статью полностью и оставить свои комментарии присоединяйтесь к sapland

У вас уже есть учетная запись?

Войти