text: /**************************************************************
   * 
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   * 
   *   http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   * 
   *************************************************************/
  
  
  
  // MARKER(update_precomp.py): autogen include statement, do not remove
  #include "precompiled_desktop.hxx"
  
  #include "dp_registry.hrc"
  #include "dp_misc.h"
  #include "dp_resource.h"
  #include "dp_interact.h"
  #include "dp_ucb.h"
  #include "osl/diagnose.h"
  #include "rtl/ustrbuf.hxx"
  #include "rtl/uri.hxx"
  #include "cppuhelper/compbase2.hxx"
  #include "cppuhelper/exc_hlp.hxx"
  #include "comphelper/sequence.hxx"
  #include "ucbhelper/content.hxx"
  #include "com/sun/star/uno/DeploymentException.hpp"
  #include "com/sun/star/lang/DisposedException.hpp"
  #include "com/sun/star/lang/WrappedTargetRuntimeException.hpp"
  #include "com/sun/star/lang/XServiceInfo.hpp"
  #include "com/sun/star/lang/XSingleComponentFactory.hpp"
  #include "com/sun/star/lang/XSingleServiceFactory.hpp"
  #include "com/sun/star/util/XUpdatable.hpp"
  #include "com/sun/star/container/XContentEnumerationAccess.hpp"
  #include "com/sun/star/deployment/PackageRegistryBackend.hpp"
  #include <hash_map>
  #include <set>
  #include <hash_set>
  #include <memory>
  
  using namespace ::dp_misc;
  using namespace ::com::sun::star;
  using namespace ::com::sun::star::uno;
  using namespace ::com::sun::star::ucb;
  using ::rtl::OUString;
  
  
  namespace dp_registry {
  
  namespace backend {
  namespace bundle {
  Reference<deployment::XPackageRegistry> create(
      Reference<deployment::XPackageRegistry> const & xRootRegistry,
      OUString const & context, OUString const & cachePath, bool readOnly,
      Reference<XComponentContext> const & xComponentContext );
  }
  }
  
  namespace {
  
  typedef ::cppu::WeakComponentImplHelper2<
      deployment::XPackageRegistry, util::XUpdatable > t_helper;
  
  //==============================================================================
  class PackageRegistryImpl : private MutexHolder, public t_helper
  {
      struct ci_string_hash {
          ::std::size_t operator () ( OUString const & str ) const {
              return str.toAsciiLowerCase().hashCode();
          }
      };
      struct ci_string_equals {
          bool operator () ( OUString const & str1, OUString const & str2 ) const{
              return str1.equalsIgnoreAsciiCase( str2 );
          }
      };
      typedef ::std::hash_map<
          OUString, Reference<deployment::XPackageRegistry>,
          ci_string_hash, ci_string_equals > t_string2registry;
      typedef ::std::hash_map<
          OUString, OUString,
          ci_string_hash, ci_string_equals > t_string2string;
      typedef ::std::set<
          Reference<deployment::XPackageRegistry> > t_registryset;
      
      t_string2registry m_mediaType2backend;
      t_string2string m_filter2mediaType;
      t_registryset m_ambiguousBackends;
      t_registryset m_allBackends;
      ::std::vector< Reference<deployment::XPackageTypeInfo> > m_typesInfos;
      
      void insertBackend(
          Reference<deployment::XPackageRegistry> const & xBackend );
      
  protected:
      inline void check();
      virtual void SAL_CALL disposing();
      
      virtual ~PackageRegistryImpl();
      PackageRegistryImpl() : t_helper( getMutex() ) {}
  
  
  public:
      static Reference<deployment::XPackageRegistry> create(
          OUString const & context,
          OUString const & cachePath, bool readOnly,
          Reference<XComponentContext> const & xComponentContext );
  
      // XUpdatable
      virtual void SAL_CALL update() throw (RuntimeException);
      
      // XPackageRegistry
      virtual Reference<deployment::XPackage> SAL_CALL bindPackage(
          OUString const & url, OUString const & mediaType, sal_Bool bRemoved,
          OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv )
          throw (deployment::DeploymentException,
                 deployment::InvalidRemovedParameterException,
                 CommandFailedException,
                 lang::IllegalArgumentException, RuntimeException);
      virtual Sequence< Reference<deployment::XPackageTypeInfo> > SAL_CALL
      getSupportedPackageTypes() throw (RuntimeException);
      virtual void SAL_CALL packageRemoved(OUString const & url, OUString const & mediaType)
                  throw (deployment::DeploymentException,
                  RuntimeException);
  
  };
  
  //______________________________________________________________________________
  inline void PackageRegistryImpl::check()
  {
      ::osl::MutexGuard guard( getMutex() );
      if (rBHelper.bInDispose || rBHelper.bDisposed) {
          throw lang::DisposedException(
              OUSTR("PackageRegistry instance has already been disposed!"),
              static_cast<OWeakObject *>(this) );
      }
  }
  
  //______________________________________________________________________________
  void PackageRegistryImpl::disposing()
  {
      // dispose all backends:
      t_registryset::const_iterator iPos( m_allBackends.begin() );
      t_registryset::const_iterator const iEnd( m_allBackends.end() );
      for ( ; iPos != iEnd; ++iPos ) {
          try_dispose( *iPos );
      }
      m_mediaType2backend = t_string2registry();
      m_ambiguousBackends = t_registryset();
      m_allBackends = t_registryset();
      
      t_helper::disposing();
  }
  
  //______________________________________________________________________________
  PackageRegistryImpl::~PackageRegistryImpl()
  {
  }
  
  //______________________________________________________________________________
  OUString normalizeMediaType( OUString const & mediaType )
  {
      ::rtl::OUStringBuffer buf;
      sal_Int32 index = 0;
      for (;;) {
          buf.append( mediaType.getToken( 0, '/', index ).trim() );
          if (index < 0)
              break;
          buf.append( static_cast< sal_Unicode >('/') );
      }
      return buf.makeStringAndClear();
  }
  
  //______________________________________________________________________________
  
  void PackageRegistryImpl::packageRemoved(
      ::rtl::OUString const & url, ::rtl::OUString const & mediaType)
      throw (css::deployment::DeploymentException,
             css::uno::RuntimeException)
  {
      const t_string2registry::const_iterator i = 
          m_mediaType2backend.find(mediaType);
  
      if (i != m_mediaType2backend.end())
      {
          i->second->packageRemoved(url, mediaType);
      }
  }
  
  void PackageRegistryImpl::insertBackend(
      Reference<deployment::XPackageRegistry> const & xBackend )
  {
      m_allBackends.insert( xBackend );
      typedef ::std::hash_set<OUString, ::rtl::OUStringHash> t_stringset;
      t_stringset ambiguousFilters;
      
      const Sequence< Reference<deployment::XPackageTypeInfo> > packageTypes(
          xBackend->getSupportedPackageTypes() );
      for ( sal_Int32 pos = 0; pos < packageTypes.getLength(); ++pos )
      {
          Reference<deployment::XPackageTypeInfo> const & xPackageType =
              packageTypes[ pos ];
          m_typesInfos.push_back( xPackageType );
          
          const OUString mediaType( normalizeMediaType(
                                        xPackageType->getMediaType() ) );
          ::std::pair<t_string2registry::iterator, bool> mb_insertion(
              m_mediaType2backend.insert( t_string2registry::value_type(
                                              mediaType, xBackend ) ) );
          if (mb_insertion.second) 
          {
              // add parameterless media-type, too:
              sal_Int32 semi = mediaType.indexOf( ';' );
              if (semi >= 0) {
                  m_mediaType2backend.insert(
                      t_string2registry::value_type(
                          mediaType.copy( 0, semi ), xBackend ) );
              }
              const OUString fileFilter( xPackageType->getFileFilter() );
              //The package backend shall also be called to determine the mediatype
              //(XPackageRegistry.bindPackage) when the URL points to a directory.
              const bool bExtension = mediaType.equals(OUSTR("application/vnd.sun.star.package-bundle"));
              if (fileFilter.getLength() == 0 ||
                  fileFilter.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("*.*") ) ||
                  fileFilter.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM("*") ) ||
                  bExtension)
              {
                  m_ambiguousBackends.insert( xBackend );
              }
              else
              {
                  sal_Int32 nIndex = 0;
                  do {
                      OUString token( fileFilter.getToken( 0, ';', nIndex ) );
                      if (token.matchAsciiL( RTL_CONSTASCII_STRINGPARAM("*.") ))
                          token = token.copy( 1 );
                      if (token.getLength() == 0)
                          continue;
                      // mark any further wildcards ambig:
                      bool ambig = (token.indexOf('*') >= 0 ||
                                    token.indexOf('?') >= 0);
                      if (! ambig) {
                          ::std::pair<t_string2string::iterator, bool> ins(
                              m_filter2mediaType.insert(
                                  t_string2string::value_type(
                                      token, mediaType ) ) );
                          ambig = !ins.second;
                          if (ambig) {
                              // filter has already been in: add previously
                              // added backend to ambig set
                              const t_string2registry::const_iterator iFind(
                                  m_mediaType2backend.find(
                                      /* media-type of pr. added backend */
                                      ins.first->second ) );
                              OSL_ASSERT(
                                  iFind != m_mediaType2backend.end() );
                              if (iFind != m_mediaType2backend.end())
                                  m_ambiguousBackends.insert( iFind->second );
                          }
                      }
                      if (ambig) {
                          m_ambiguousBackends.insert( xBackend );
                          // mark filter to be removed later from filters map:
                          ambiguousFilters.insert( token );
                      }
                  }
                  while (nIndex >= 0);
              }
          }
  #if OSL_DEBUG_LEVEL > 0
          else {
              ::rtl::OUStringBuffer buf;
              buf.appendAscii(
                  RTL_CONSTASCII_STRINGPARAM(
                      "more than one PackageRegistryBackend for "
                      "media-type=\"") );
              buf.append( mediaType );
              buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\" => ") );
              buf.append( Reference<lang::XServiceInfo>(
                              xBackend, UNO_QUERY_THROW )->
                          getImplementationName() );
              buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\"!") );
              OSL_ENSURE( 0, ::rtl::OUStringToOString(
                              buf.makeStringAndClear(),
                              RTL_TEXTENCODING_UTF8 ) );
          }
  #endif
      }
      
      // cut out ambiguous filters:
      t_stringset::const_iterator iPos( ambiguousFilters.begin() );
      const t_stringset::const_iterator iEnd( ambiguousFilters.end() );
      for ( ; iPos != iEnd; ++iPos ) {
          m_filter2mediaType.erase( *iPos );
      }
  }
  
  //______________________________________________________________________________
  Reference<deployment::XPackageRegistry> PackageRegistryImpl::create(
      OUString const & context,
      OUString const & cachePath, bool readOnly,
      Reference<XComponentContext> const & xComponentContext )
  {
      PackageRegistryImpl * that = new PackageRegistryImpl;
      Reference<deployment::XPackageRegistry> xRet(that);
      
      // auto-detect all registered package registries:
      Reference<container::XEnumeration> xEnum(
          Reference<container::XContentEnumerationAccess>(
              xComponentContext->getServiceManager(),
              UNO_QUERY_THROW )->createContentEnumeration(
                  OUSTR("com.sun.star.deployment.PackageRegistryBackend") ) );
      if (xEnum.is())
      {
          while (xEnum->hasMoreElements())
          {
              Any element( xEnum->nextElement() );
              Sequence<Any> registryArgs(
                  cachePath.getLength() == 0 ? 1 : 3 );
              registryArgs[ 0 ] <<= context;
              if (cachePath.getLength() > 0)
              {
                  Reference<lang::XServiceInfo> xServiceInfo(
                      element, UNO_QUERY_THROW );
                  OUString registryCachePath(
                      makeURL( cachePath,
                               ::rtl::Uri::encode(
                                   xServiceInfo->getImplementationName(),
                                   rtl_UriCharClassPchar,
                                   rtl_UriEncodeIgnoreEscapes,
                                   RTL_TEXTENCODING_UTF8 ) ) );
                  registryArgs[ 1 ] <<= registryCachePath;
                  registryArgs[ 2 ] <<= readOnly;
                  if (! readOnly)
                      create_folder( 0, registryCachePath,
                                     Reference<XCommandEnvironment>() );
              }
              
              Reference<deployment::XPackageRegistry> xBackend;
              Reference<lang::XSingleComponentFactory> xFac( element, UNO_QUERY );
              if (xFac.is()) {
                  xBackend.set(
                      xFac->createInstanceWithArgumentsAndContext(
                          registryArgs, xComponentContext ), UNO_QUERY );
              }
              else {
                  Reference<lang::XSingleServiceFactory> xSingleServiceFac(
                      element, UNO_QUERY_THROW );
                  xBackend.set(
                      xSingleServiceFac->createInstanceWithArguments(
                          registryArgs ), UNO_QUERY );
              }
              if (! xBackend.is()) {
                  throw DeploymentException(
                      OUSTR("cannot instantiate PackageRegistryBackend service: ")
                      + Reference<lang::XServiceInfo>(
                          element, UNO_QUERY_THROW )->getImplementationName(),
                      static_cast<OWeakObject *>(that) );
              }
              
              that->insertBackend( xBackend );
          }
      }
      
      // Insert bundle back-end.
      // Always register as last, because we want to add extensions also as folders
      // and as a default we accept every folder, which was not recognized by the other 
      // backends.
      Reference<deployment::XPackageRegistry> extensionBackend =
          ::dp_registry::backend::bundle::create(
              that, context, cachePath, readOnly, xComponentContext);
      that->insertBackend(extensionBackend);
  
      Reference<lang::XServiceInfo> xServiceInfo(
          extensionBackend, UNO_QUERY_THROW );
  
      OSL_ASSERT(xServiceInfo.is());
      OUString registryCachePath(
          makeURL( cachePath,
                   ::rtl::Uri::encode(
                       xServiceInfo->getImplementationName(),
                       rtl_UriCharClassPchar,
                       rtl_UriEncodeIgnoreEscapes,
                       RTL_TEXTENCODING_UTF8 ) ) );
      create_folder( 0, registryCachePath, Reference<XCommandEnvironment>());
      
      
  #if OSL_DEBUG_LEVEL > 1
      // dump tables:
      {
          t_registryset allBackends;        
          dp_misc::TRACE("> [dp_registry.cxx] media-type detection:\n\n" );
          for ( t_string2string::const_iterator iPos(
                    that->m_filter2mediaType.begin() );
                iPos != that->m_filter2mediaType.end(); ++iPos )
          {
              ::rtl::OUStringBuffer buf;
              buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("extension \"") );
              buf.append( iPos->first );
              buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(
                                   "\" maps to media-type \"") );
              buf.append( iPos->second );
              buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(
                                   "\" maps to backend ") );
              const Reference<deployment::XPackageRegistry> xBackend(
                  that->m_mediaType2backend.find( iPos->second )->second );
              allBackends.insert( xBackend );
              buf.append( Reference<lang::XServiceInfo>(
                              xBackend, UNO_QUERY_THROW )
                          ->getImplementationName() );
              dp_misc::writeConsole( buf.makeStringAndClear() + OUSTR("\n"));
          }
          dp_misc::TRACE( "> [dp_registry.cxx] ambiguous backends:\n\n" );
          for ( t_registryset::const_iterator iPos(
                    that->m_ambiguousBackends.begin() );
                iPos != that->m_ambiguousBackends.end(); ++iPos )
          {
              ::rtl::OUStringBuffer buf;
              buf.append(
                  Reference<lang::XServiceInfo>(
                      *iPos, UNO_QUERY_THROW )->getImplementationName() );
              buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(": ") );
              const Sequence< Reference<deployment::XPackageTypeInfo> > types(
                  (*iPos)->getSupportedPackageTypes() );
              for ( sal_Int32 pos = 0; pos < types.getLength(); ++pos ) {
                  Reference<deployment::XPackageTypeInfo> const & xInfo =
                      types[ pos ];
                  buf.append( xInfo->getMediaType() );
                  const OUString filter( xInfo->getFileFilter() );
                  if (filter.getLength() > 0) {
                      buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(" (") );
                      buf.append( filter );
                      buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(")") );
                  }
                  if (pos < (types.getLength() - 1))
                      buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(", ") );
              }
              dp_misc::TRACE(buf.makeStringAndClear() + OUSTR("\n\n"));
          }
          allBackends.insert( that->m_ambiguousBackends.begin(),
                              that->m_ambiguousBackends.end() );
          OSL_ASSERT( allBackends == that->m_allBackends );
      }
  #endif
      
      return xRet;
  }
  
  // XUpdatable: broadcast to backends
  //______________________________________________________________________________
  void PackageRegistryImpl::update() throw (RuntimeException)
  {
      check();
      t_registryset::const_iterator iPos( m_allBackends.begin() );
      const t_registryset::const_iterator iEnd( m_allBackends.end() );
      for ( ; iPos != iEnd; ++iPos ) {
          const Reference<util::XUpdatable> xUpdatable( *iPos, UNO_QUERY );
          if (xUpdatable.is())
              xUpdatable->update();
      }
  }
  
  // XPackageRegistry
  //______________________________________________________________________________
  Reference<deployment::XPackage> PackageRegistryImpl::bindPackage(
      OUString const & url, OUString const & mediaType_, sal_Bool bRemoved,
      OUString const & identifier, Reference<XCommandEnvironment> const & xCmdEnv )
      throw (deployment::DeploymentException, deployment::InvalidRemovedParameterException,
             CommandFailedException,
             lang::IllegalArgumentException, RuntimeException)
  {
      check();
      OUString mediaType(mediaType_);
      if (mediaType.getLength() == 0)
      {
          ::ucbhelper::Content ucbContent;
          if (create_ucb_content(
                  &ucbContent, url, xCmdEnv, false /* no throw */ )
                  && !ucbContent.isFolder())
          {
              OUString title( ucbContent.getPropertyValue(
                                  StrTitle::get() ).get<OUString>() );
              for (;;)
              {
                  const t_string2string::const_iterator iFind(
                      m_filter2mediaType.find(title) );
                  if (iFind != m_filter2mediaType.end()) {
                      mediaType = iFind->second;
                      break;
                  }
                  sal_Int32 point = title.indexOf( '.', 1 /* consume . */ );
                  if (point < 0)
                      break;
                  title = title.copy(point);
              }
          }
      }
      if (mediaType.getLength() == 0)
      {
          // try ambiguous backends:
          t_registryset::const_iterator iPos( m_ambiguousBackends.begin() );
          const t_registryset::const_iterator iEnd( m_ambiguousBackends.end() );
          for ( ; iPos != iEnd; ++iPos )
          {
              try {
                  return (*iPos)->bindPackage( url, mediaType, bRemoved, 
                      identifier, xCmdEnv );
              }
              catch (lang::IllegalArgumentException &) {
              }
          }
          throw lang::IllegalArgumentException(
              getResourceString(RID_STR_CANNOT_DETECT_MEDIA_TYPE) + url,
              static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
      }
      else
      {
          // get backend by media-type:
          t_string2registry::const_iterator iFind(
              m_mediaType2backend.find( normalizeMediaType(mediaType) ) );
          if (iFind == m_mediaType2backend.end()) {
              // xxx todo: more sophisticated media-type argument parsing...
              sal_Int32 q = mediaType.indexOf( ';' );
              if (q >= 0) {
                  iFind = m_mediaType2backend.find(
                      normalizeMediaType(
                          // cut parameters:
                          mediaType.copy( 0, q ) ) );
              }
          }
          if (iFind == m_mediaType2backend.end()) {
              throw lang::IllegalArgumentException(
                  getResourceString(RID_STR_UNSUPPORTED_MEDIA_TYPE) + mediaType,
                  static_cast<OWeakObject *>(this), static_cast<sal_Int16>(-1) );
          }
          return iFind->second->bindPackage( url, mediaType, bRemoved, 
              identifier, xCmdEnv );
      }
  }
  
  //______________________________________________________________________________
  Sequence< Reference<deployment::XPackageTypeInfo> >
  PackageRegistryImpl::getSupportedPackageTypes() throw (RuntimeException)
  {
      return comphelper::containerToSequence(m_typesInfos);
  }
  } // anon namespace
  
  //==============================================================================
  Reference<deployment::XPackageRegistry> SAL_CALL create(
      OUString const & context,
      OUString const & cachePath, bool readOnly,
      Reference<XComponentContext> const & xComponentContext )
  {
      return PackageRegistryImpl::create(
          context, cachePath, readOnly, xComponentContext );
  }
  
  } // namespace dp_registry
  
  