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

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

Одним из требований, возникающих при внедрении корпоративных порталов, является их тесная интеграция с уже развернутой структурой организации – электронной почтой, персональными средствами планирования и управления рабочим графиком. В первую очередь это наиболее распространенные системы – 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)

            {

                  throw Exceptions.processing(e, core);

            }

            return "";

      }

      @Override

      public Map getSupp_Heartbeat(IGroupwareCredentials credential, Properties congfigProperty)

      {

            return new HashMap();

      }

      @Override

      public List getSupp_TransportConfigs()

      {

            return _configurations;

      }

}

Несколько комментариев по приведенному коду:

Первым вызываемым методом является метод «initialize». Он обеспечивает инициализацию (начальные значения) полей транспорта. Собственно, поэтому

Ограниченный доступ

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


Любое воспроизведение запрещено.
Копирайт © «Издательство ООО «Эксперт РП»