Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
modules/search-service-impl/src/main/java/org/opencastproject/search/impl/SearchServiceImpl.java
### 代码内容：
/**
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 *
 * The Apereo Foundation licenses this file to you under the Educational
 * Community 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://opensource.org/licenses/ecl2.txt
 *
 * 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.
 *
 */

package org.opencastproject.search.impl;

import static org.opencastproject.security.api.SecurityConstants.GLOBAL_ADMIN_ROLE;

import org.opencastproject.job.api.AbstractJobProducer;
import org.opencastproject.job.api.Job;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageException;
import org.opencastproject.mediapackage.MediaPackageParser;
import org.opencastproject.mediapackage.MediaPackageSerializer;
import org.opencastproject.metadata.api.StaticMetadataService;
import org.opencastproject.metadata.mpeg7.Mpeg7CatalogService;
import org.opencastproject.search.api.SearchException;
import org.opencastproject.search.api.SearchQuery;
import org.opencastproject.search.api.SearchResult;
import org.opencastproject.search.api.SearchService;
import org.opencastproject.search.impl.persistence.SearchServiceDatabase;
import org.opencastproject.search.impl.persistence.SearchServiceDatabaseException;
import org.opencastproject.search.impl.solr.SolrIndexManager;
import org.opencastproject.search.impl.solr.SolrRequester;
import org.opencastproject.security.api.AccessControlList;
import org.opencastproject.security.api.AuthorizationService;
import org.opencastproject.security.api.Organization;
import org.opencastproject.security.api.OrganizationDirectoryService;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.security.api.StaticFileAuthorization;
import org.opencastproject.security.api.UnauthorizedException;
import org.opencastproject.security.api.User;
import org.opencastproject.security.api.UserDirectoryService;
import org.opencastproject.security.util.SecurityUtil;
import org.opencastproject.series.api.SeriesService;
import org.opencastproject.serviceregistry.api.ServiceRegistry;
import org.opencastproject.serviceregistry.api.ServiceRegistryException;
import org.opencastproject.solr.SolrServerFactory;
import org.opencastproject.util.LoadUtil;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.data.Tuple;
import org.opencastproject.workspace.api.Workspace;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.osgi.framework.ServiceException;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Dictionary;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A Solr-based {@link SearchService} implementation.
 */
public final class SearchServiceImpl extends AbstractJobProducer implements SearchService, ManagedService,
    StaticFileAuthorization {

  /** Log facility */
  private static final Logger logger = LoggerFactory.getLogger(SearchServiceImpl.class);

  /** Configuration key for a remote solr server */
  public static final String CONFIG_SOLR_URL = "org.opencastproject.search.solr.url";

  /** Configuration key for an embedded solr configuration and data directory */
  public static final String CONFIG_SOLR_ROOT = "org.opencastproject.search.solr.dir";

  /** The job type */
  public static final String JOB_TYPE = "org.opencastproject.search";

  /** The load introduced on the system by creating an add job */
  public static final float DEFAULT_ADD_JOB_LOAD = 0.1f;

  /** The load introduced on the system by creating a delete job */
  public static final float DEFAULT_DELETE_JOB_LOAD = 0.1f;

  /** The key to look for in the service configuration file to override the {@link DEFAULT_ADD_JOB_LOAD} */
  public static final String ADD_JOB_LOAD_KEY = "job.load.add";

  /** The key to look for in the service configuration file to override the {@link DEFAULT_DELETE_JOB_LOAD} */
  public static final String DELETE_JOB_LOAD_KEY = "job.load.delete";

  /** The load introduced on the system by creating an add job */
  private float addJobLoad = DEFAULT_ADD_JOB_LOAD;

  /** The load introduced on the system by creating a delete job */
  private float deleteJobLoad = DEFAULT_DELETE_JOB_LOAD;

  /** counter how often the index has already been tried to populate */
  private int retriesToPopulateIndex = 0;

  /** List of available operations on jobs */
  private enum Operation {
    Add, Delete
  };

  /** Solr server */
  private SolrServer solrServer;

  private SolrRequester solrRequester;

  private SolrIndexManager indexManager;

  private List<StaticMetadataService> mdServices = new ArrayList<StaticMetadataService>();

  private Mpeg7CatalogService mpeg7CatalogService;

  private SeriesService seriesService;

  /** The local workspace */
  private Workspace workspace;

  /** The security service */
  private SecurityService securityService;

  /** The authorization service */
  private AuthorizationService authorizationService;

  /** The service registry */
  private ServiceRegistry serviceRegistry;

  /** Persistent storage */
  private SearchServiceDatabase persistence;

  /** The user directory service */
  protected UserDirectoryService userDirectoryService = null;

  /** The organization directory service */
  protected OrganizationDirectoryService organizationDirectory = null;

  /** The optional Mediapackage serializer */
  protected MediaPackageSerializer serializer = null;

  private LoadingCache<Tuple<User, String>, Boolean> cache = null;

  private static final Pattern staticFilePattern = Pattern.compile("^/([^/]+)/engage-player/([^/]+)/.*$");

  /**
   * Creates a new instance of the search service.
   */
  public SearchServiceImpl() {
    super(JOB_TYPE);

    cache = CacheBuilder.newBuilder()
        .maximumSize(2048)
        .expireAfterWrite(1, TimeUnit.MINUTES)
        .build(new CacheLoader<Tuple<User, String>, Boolean>() {
          @Override
          public Boolean load(Tuple<User, String> key) {
            return loadUrlAccess(key.getB());
          }
        });
  }

  /**
   * Return the solr index manager
   *
   * @return indexManager
   */
  public SolrIndexManager getSolrIndexManager() {
    return indexManager;
  }

  /**
   * Service activator, called via declarative services configuration. If the solr server url is configured, we try to
   * connect to it. If not, the solr data directory with an embedded Solr server is used.
   *
   * @param cc
   *          the component context
   */
  @Override
  public void activate(final ComponentContext cc) throws IllegalStateException {
    super.activate(cc);
    final String solrServerUrlConfig = StringUtils.trimToNull(cc.getBundleContext().getProperty(CONFIG_SOLR_URL));

    logger.info("Setting up solr server");

    solrServer = new Object() {
      SolrServer create() {
        if (solrServerUrlConfig != null) {
          /* Use external SOLR server */
          try {
            logger.info("Setting up solr server at {}", solrServerUrlConfig);
            URL solrServerUrl = new URL(solrServerUrlConfig);
            return setupSolr(solrServerUrl);
          } catch (MalformedURLException e) {
            throw connectError(solrServerUrlConfig, e);
          }
        } else {
          /* Set-up embedded SOLR */
          String solrRoot = SolrServerFactory.getEmbeddedDir(cc, CONFIG_SOLR_ROOT, "search");

          try {
            logger.debug("Setting up solr server at {}", solrRoot);
            return setupSolr(new File(solrRoot));
          } catch (IOException e) {
            throw connectError(solrServerUrlConfig, e);
          } catch (SolrServerException e) {
            throw connectError(solrServerUrlConfig, e);
          }
        }
      }

      IllegalStateException connectError(String target, Exception e) {
        logger.error("Unable to connect to solr at {}: {}", target, e.getMessage());
        return new IllegalStateException("Unable to connect to solr at " + target, e);
      }
      // CHECKSTYLE:OFF
    }.create();
    // CHECKSTYLE:ON

    solrRequester = new SolrRequester(solrServer, securityService, serializer);
    indexManager = new SolrIndexManager(solrServer, workspace, mdServices, seriesService, mpeg7CatalogService,
            securityService);

    String systemUserName = cc.getBundleContext().getProperty(SecurityUtil.PROPERTY_KEY_SYS_USER);
    populateIndex(systemUserName);
  }

  /**
   * Service deactivator, called via declarative services configuration.
   */
  public void deactivate() {
    SolrServerFactory.shutdown(solrServer);
  }

  /**
   * Prepares the embedded solr environment.
   *
   * @param solrRoot
   *          the solr root directory
   */
  static SolrServer setupSolr(File solrRoot) throws IOException, SolrServerException {
    logger.info("Setting up solr search index at {}", solrRoot);
    File solrConfigDir = new File(solrRoot, "conf");

    // Create the config directory
    if (solrConfigDir.exists()) {
      logger.info("solr search index found at {}", solrConfigDir);
    } else {
      logger.info("solr config directory doesn't exist.  Creating {}", solrConfigDir);
      FileUtils.forceMkdir(solrConfigDir);
    }

    // Make sure there is a configuration in place
    copyClasspathResourceToFile("/solr/conf/protwords.txt", solrConfigDir);
    copyClasspathResourceToFile("/solr/conf/schema.xml", solrConfigDir);
    copyClasspathResourceToFile("/solr/conf/scripts.conf", solrConfigDir);
    copyClasspathResourceToFile("/solr/conf/solrconfig.xml", solrConfigDir);
    copyClasspathResourceToFile("/solr/conf/stopwords.txt", solrConfigDir);
    copyClasspathResourceToFile("/solr/conf/synonyms.txt", solrConfigDir);

    // Test for the existence of a data directory
    File solrDataDir = new File(solrRoot, "data");
    if (!solrDataDir.exists()) {
      FileUtils.forceMkdir(solrDataDir);
    }

    // Test for the existence of the index. Note that an empty index directory will prevent solr from
    // completing normal setup.
    File solrIndexDir = new File(solrDataDir, "index");
    if (solrIndexDir.isDirectory() && solrIndexDir.list().length == 0) {
      FileUtils.deleteDirectory(solrIndexDir);
    }

    return SolrServerFactory.newEmbeddedInstance(solrRoot, solrDataDir);
  }

  /**
   * Prepares the embedded solr environment.
   *
   * @param url
   *          the url of the remote solr server
   */
  static SolrServer setupSolr(URL url) {
    logger.info("Connecting to solr search index at {}", url);
    return SolrServerFactory.newRemoteInstance(url);
  }

  // TODO: generalize this method
  static void copyClasspathResourceToFile(String classpath, File dir) {
    InputStream in = null;
    FileOutputStream fos = null;
    try {
      in = SearchServiceImpl.class.getResourceAsStream(classpath);
      File file = new File(dir, FilenameUtils.getName(classpath));
      logger.debug("copying " + classpath + " to " + file);
      fos = new FileOutputStream(file);
      IOUtils.copy(in, fos);
    } catch (IOException e) {
      throw new RuntimeException("Error copying solr classpath resource to the filesystem", e);
    } finally {
      IOUtils.closeQuietly(in);
      IOUtils.closeQuietly(fos);
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.search.api.SearchService#getByQuery(java.lang.String, int, int)
   */
  public SearchResult getByQuery(String query, int limit, int offset) throws SearchException {
    try {
      logger.debug("Searching index using custom query '" + query + "'");
      return solrRequester.getByQuery(query, limit, offset);
    } catch (SolrServerException e) {
      throw new SearchException(e);
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.search.api.SearchService#add(org.opencastproject.mediapackage.MediaPackage)
   */
  public Job add(MediaPackage mediaPackage) throws SearchException, MediaPackageException, IllegalArgumentException,
          UnauthorizedException, ServiceRegistryException {
    try {
      return serviceRegistry.createJob(JOB_TYPE, Operation.Add.toString(),
              Arrays.asList(MediaPackageParser.getAsXml(mediaPackage)), addJobLoad);
    } catch (ServiceRegistryException e) {
      throw new SearchException(e);
    }
  }

  /**
   * Immediately adds the mediapackage to the search index.
   *
   * @param mediaPackage
   *          the media package
   * @throws SearchException
   *           if the media package cannot be added to the search index
   * @throws MediaPackageException
   *           if the mediapckage is invalid
   * @throws IllegalArgumentException
   *           if the mediapackage is null
   * @throws UnauthorizedException
   *           if the user does not have the rights to add the mediapackage
   */
  public void addSynchronously(MediaPackage mediaPackage) throws SearchException, MediaPackageException,
          IllegalArgumentException, UnauthorizedException {
    if (mediaPackage == null) {
      throw new IllegalArgumentException("Unable to add a null mediapackage");
    }
    logger.debug("Attempting to add mediapackage {} to search index", mediaPackage.getIdentifier());
    AccessControlList acl = authorizationService.getActiveAcl(mediaPackage).getA();

    Date now = new Date();

    try {
      if (indexManager.add(mediaPackage, acl, now)) {
        logger.info("Added mediapackage `{}` to the search index, using ACL `{}`", mediaPackage, acl);
      } else {
        logger.warn("Failed to add mediapackage {} to the search index", mediaPackage.getIdentifier());
      }
    } catch (SolrServerException e) {
      throw new SearchException(e);
    }

    try {
      persistence.storeMediaPackage(mediaPackage, acl, now);
    } catch (SearchServiceDatabaseException e) {
      logger.error("Could not store media package to search database {}: {}", mediaPackage.getIdentifier(), e);
      throw new SearchException(e);
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.search.api.SearchService#delete(java.lang.String)
   */
  public Job delete(String mediaPackageId) throws SearchException, UnauthorizedException, NotFoundException {
    try {
      return serviceRegistry.createJob(JOB_TYPE, Operation.Delete.toString(), Arrays.asList(mediaPackageId), deleteJobLoad);
    } catch (ServiceRegistryException e) {
      throw new SearchException(e);
    }
  }

  /**
   * Immediately removes the given mediapackage from the search service.
   *
   * @param mediaPackageId
   *          the mediapackage
   * @return true if the mediapackage was deleted
   * @throws SearchException
   *           if deletion failed
   * @throws UnauthorizedException
   *           if the user did not have access to the media package
   * @throws NotFoundException
   *           if the mediapackage did not exist
   */
  public boolean deleteSynchronously(String mediaPackageId) throws SearchException, UnauthorizedException,
          NotFoundException {
    SearchResult result;
    try {
      result = solrRequester.getForWrite(new SearchQuery().withId(mediaPackageId));
      if (result.getItems().length == 0) {
        logger.warn(
                "Can not delete mediapackage {}, which is not available for the current user to delete from the search index.",
                mediaPackageId);
        return false;
      }
      logger.info("Removing mediapackage {} from search index", mediaPackageId);

      Date now = new Date();
      try {
        persistence.deleteMediaPackage(mediaPackageId, now);
        logger.info("Removed mediapackage {} from search persistence", mediaPackageId);
      } catch (NotFoundException e) {
        // even if mp not found in persistence, it might still exist in search index.
        logger.info("Could not find mediapackage with id {} in persistence, but will try remove it from index, anyway.",
                mediaPackageId);
      } catch (SearchServiceDatabaseException e) {
        logger.error("Could not delete media package with id {} from persistence storage", mediaPackageId);
        throw new SearchException(e);
      }

      return indexManager.delete(mediaPackageId, now);
    } catch (SolrServerException e) {
      logger.info("Could not delete media package with id {} from search index", mediaPackageId);
      throw new SearchException(e);
    }
  }

  /**
   * Clears the complete solr index.
   *
   * @throws SearchException
   *           if clearing the index fails
   */
  public void clear() throws SearchException {
    try {
      logger.info("Clearing the search index");
      indexManager.clear();
    } catch (SolrServerException e) {
      throw new SearchException(e);
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.search.api.SearchService#getByQuery(org.opencastproject.search.api.SearchQuery)
   */
  public SearchResult getByQuery(SearchQuery q) throws SearchException {
    try {
      logger.debug("Searching index using query object '" + q + "'");
      return solrRequester.getForRead(q);
    } catch (SolrServerException e) {
      throw new SearchException(e);
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.search.api.SearchService#getForAdministrativeRead(org.opencastproject.search.api.SearchQuery)
   */
  @Override
  public SearchResult getForAdministrativeRead(SearchQuery q) throws SearchException, UnauthorizedException {
    User user = securityService.getUser();
    if (!user.hasRole(GLOBAL_ADMIN_ROLE) && !user.hasRole(user.getOrganization().getAdminRole()))
      throw new UnauthorizedException(user, getClass().getName() + ".getForAdministrativeRead");

    try {
      return solrRequester.getForAdministrativeRead(q);
    } catch (SolrServerException e) {
      throw new SearchException(e);
    }
  }

  protected void populateIndex(String systemUserName) {
    long instancesInSolr = 0L;

    try {
      instancesInSolr = indexManager.count();
    } catch (Exception e) {
      throw new IllegalStateException(e);
    }

    if (instancesInSolr > 0) {
      logger.debug("Search index found");
      return;
    }

    if (instancesInSolr == 0L) {
      logger.info("No search index found");
      Iterator<Tuple<MediaPackage, String>> mediaPackages;
      int total = 0;
      try {
        total = persistence.countMediaPackages();
        logger.info("Starting population of search index from {} items in database", total);
        mediaPackages = persistence.getAllMediaPackages();
      } catch (SearchServiceDatabaseException e) {
        logger.error("Unable to load the search entries: {}", e.getMessage());
        throw new ServiceException(e.getMessage());
      }
      int errors = 0;
      int current = 0;
      while (mediaPackages.hasNext()) {
        current++;
        try {
          Tuple<MediaPackage, String> mediaPackage = mediaPackages.next();

          String mediaPackageId = mediaPackage.getA().getIdentifier().toString();

          Organization organization = organizationDirectory.getOrganization(mediaPackage.getB());
          securityService.setOrganization(organization);
          securityService.setUser(SecurityUtil.createSystemUser(systemUserName, organization));

          AccessControlList acl = persistence.getAccessControlList(mediaPackageId);
          Date modificationDate = persistence.getModificationDate(mediaPackageId);
          Date deletionDate = persistence.getDeletionDate(mediaPackageId);

          indexManager.add(mediaPackage.getA(), acl, deletionDate, modificationDate);
        } catch (Exception e) {
          logger.error("Unable to index search instances:", e);
          if (retryToPopulateIndex(systemUserName)) {
            logger.warn("Trying to re-index search index later. Aborting for now.");
            return;
          }
          errors++;
        } finally {
          securityService.setOrganization(null);
          securityService.setUser(null);
        }

        // log progress
        if (current % 100 == 0) {
          logger.info("Indexing search {}/{} ({} percent done)", current, total, current * 100 / total);
        }
      }
      if (errors > 0)
        logger.error("Skipped {} erroneous search entries while populating the search index", errors);
      logger.info("Finished populating search index");
    }
  }

  private boolean retryToPopulateIndex(final String systemUserName) {
    if (retriesToPopulateIndex > 0) {
      return false;
    }

    long instancesInSolr = 0L;

    try {
      instancesInSolr = indexManager.count();
    } catch (Exception e) {
      throw new IllegalStateException(e);
    }

    if (instancesInSolr > 0) {
      logger.debug("Search index found, other files could be indexed. No retry needed.");
      return false;
    }

    retriesToPopulateIndex++;

      new Thread() {
        public void run() {
          try {
            Thread.sleep(30000);
          } catch (InterruptedException ex) {
          }
          populateIndex(systemUserName);
        }
      }.start();
    return true;
  }

  /**
   * @see org.opencastproject.job.api.AbstractJobProducer#process(org.opencastproject.job.api.Job)
   */
  @Override
  protected String process(Job job) throws Exception {
    Operation op = null;
    String operation = job.getOperation();
    List<String> arguments = job.getArguments();
    try {
      op = Operation.valueOf(operation);
      switch (op) {
        case Add:
          MediaPackage mediaPackage = MediaPackageParser.getFromXml(arguments.get(0));
          addSynchronously(mediaPackage);
          return null;
        case Delete:
          String mediapackageId = arguments.get(0);
          boolean deleted = deleteSynchronously(mediapackageId);
          return Boolean.toString(deleted);
        default:
          throw new IllegalStateException("Don't know how to handle operation '" + operation + "'");
      }
    } catch (IllegalArgumentException e) {
      throw new ServiceRegistryException("This service can't handle operations of type '" + op + "'", e);
    } catch (IndexOutOfBoundsException e) {
      throw new ServiceRegistryException("This argument list for operation '" + op + "' does not meet expectations", e);
    } catch (Exception e) {
      throw new ServiceRegistryException("Error handling operation '" + op + "'", e);
    }
  }

  /** For testing purposes only! */
  void testSetup(SolrServer server, SolrRequester requester, SolrIndexManager manager) {
    this.solrServer = server;
    this.solrRequester = requester;
    this.indexManager = manager;
  }

  /** Dynamic reference. */
  public void setStaticMetadataService(StaticMetadataService mdService) {
    this.mdServices.add(mdService);
    if (indexManager != null)
      indexManager.setStaticMetadataServices(mdServices);
  }

  public void unsetStaticMetadataService(StaticMetadataService mdService) {
    this.mdServices.remove(mdService);
    if (indexManager != null)
      indexManager.setStaticMetadataServices(mdServices);
  }

  public void setMpeg7CatalogService(Mpeg7CatalogService mpeg7CatalogService) {
    this.mpeg7CatalogService = mpeg7CatalogService;
  }

  public void setPersistence(SearchServiceDatabase persistence) {
    this.persistence = persistence;
  }

  public void setSeriesService(SeriesService seriesService) {
    this.seriesService = seriesService;
  }

  public void setWorkspace(Workspace workspace) {
    this.workspace = workspace;
  }

  public void setAuthorizationService(AuthorizationService authorizationService) {
    this.authorizationService = authorizationService;
  }

  public void setServiceRegistry(ServiceRegistry serviceRegistry) {
    this.serviceRegistry = serviceRegistry;
  }

  /**
   * Callback for setting the security service.
   *
   * @param securityService
   *          the securityService to set
   */
  public void setSecurityService(SecurityService securityService) {
    this.securityService = securityService;
  }

  /**
   * Callback for setting the user directory service.
   *
   * @param userDirectoryService
   *          the userDirectoryService to set
   */
  public void setUserDirectoryService(UserDirectoryService userDirectoryService) {
    this.userDirectoryService = userDirectoryService;
  }

  /**
   * Sets a reference to the organization directory service.
   *
   * @param organizationDirectory
   *          the organization directory
   */
  public void setOrganizationDirectoryService(OrganizationDirectoryService organizationDirectory) {
    this.organizationDirectory = organizationDirectory;
  }

  /**
   * @see org.opencastproject.job.api.AbstractJobProducer#getOrganizationDirectoryService()
   */
  @Override
  protected OrganizationDirectoryService getOrganizationDirectoryService() {
    return organizationDirectory;
  }

  /**
   * @see org.opencastproject.job.api.AbstractJobProducer#getSecurityService()
   */
  @Override
  protected SecurityService getSecurityService() {
    return securityService;
  }

  /**
   * @see org.opencastproject.job.api.AbstractJobProducer#getServiceRegistry()
   */
  @Override
  protected ServiceRegistry getServiceRegistry() {
    return serviceRegistry;
  }

  /**
   * @see org.opencastproject.job.api.AbstractJobProducer#getUserDirectoryService()
   */
  @Override
  protected UserDirectoryService getUserDirectoryService() {
    return userDirectoryService;
  }

  /**
   * Sets the optional MediaPackage serializer.
   *
   * @param serializer
   *          the serializer
   */
  protected void setMediaPackageSerializer(MediaPackageSerializer serializer) {
    this.serializer = serializer;
    if (solrRequester != null)
      solrRequester.setMediaPackageSerializer(serializer);
  }

  @Override
  public void updated(@SuppressWarnings("rawtypes") Dictionary properties) throws ConfigurationException {
    addJobLoad = LoadUtil.getConfiguredLoadValue(properties, ADD_JOB_LOAD_KEY, DEFAULT_ADD_JOB_LOAD, serviceRegistry);
    deleteJobLoad = LoadUtil.getConfiguredLoadValue(properties, DELETE_JOB_LOAD_KEY, DEFAULT_DELETE_JOB_LOAD, serviceRegistry);
  }

  @Override
  public List<Pattern> getProtectedUrlPattern() {
    return Collections.singletonList(staticFilePattern);
  }

  private boolean loadUrlAccess(final String mediaPackageId) {
    logger.debug("Check if user `{}` has access to media package `{}`", securityService.getUser(), mediaPackageId);
    final SearchQuery query = new SearchQuery()
        .withId(mediaPackageId)
        .includeEpisodes(true)
        .includeSeries(false);
    return getByQuery(query).size() > 0;
  }

  @Override
  public boolean verifyUrlAccess(final String path) {
    // Always allow access for admin
    final User user = securityService.getUser();
    if (user.hasRole(GLOBAL_ADMIN_ROLE)) {
      logger.debug("Allow access for admin `{}`", user);
      return true;
    }

    // Check pattern
    final Matcher m = staticFilePattern.matcher(path);
    if (!m.matches()) {
      logger.debug("Path does not match pattern. Preventing access.");
      return false;
    }

    // Check organization
    final String organizationId = m.group(1);
    if (!securityService.getOrganization().getId().equals(organizationId)) {
      logger.debug("The user's organization does not match. Preventing access.");
      return false;
    }

    // Check search index/cache
    final String mediaPackageId = m.group(2);
    final boolean access = cache.getUnchecked(Tuple.tuple(user, mediaPackageId));
    logger.debug("Check if user `{}` has access to media package `{}` using cache: {}", user, mediaPackageId, access);
    return access;
  }
}

## 内容2
### 文件路径：
modules/search-service-impl/src/main/java/org/opencastproject/search/impl/persistence/SearchEntity.java
### 代码内容：
/**
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 *
 * The Apereo Foundation licenses this file to you under the Educational
 * Community 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://opensource.org/licenses/ecl2.txt
 *
 * 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.
 *
 */

package org.opencastproject.search.impl.persistence;

import org.opencastproject.security.api.Organization;
import org.opencastproject.security.impl.jpa.JpaOrganization;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

/**
 * Entity object for storing search in persistence storage. Media package id is stored as primary key.
 */
@Entity(name = "SearchEntity")
@Table(name = "oc_search", indexes = {
    @Index(name = "IX_oc_search_series", columnList = ("series_id")),
    @Index(name = "IX_oc_search_organization", columnList = ("organization")) })
@NamedQueries({
    @NamedQuery(name = "Search.findAll", query = "SELECT s FROM SearchEntity s"),
    @NamedQuery(name = "Search.getCount", query = "SELECT COUNT(s) FROM SearchEntity s"),
    @NamedQuery(name = "Search.findById", query = "SELECT s FROM SearchEntity s WHERE s.mediaPackageId=:mediaPackageId"),
    @NamedQuery(name = "Search.findBySeriesId", query = "SELECT s FROM SearchEntity s WHERE s.seriesId=:seriesId"),
    @NamedQuery(name = "Search.getNoSeries", query = "SELECT s FROM SearchEntity s WHERE s.seriesId IS NULL")})
public class SearchEntity {

  /** media package id, primary key */
  @Id
  @Column(name = "id", length = 128)
  private String mediaPackageId;

  @Column(name = "series_id", length = 128)
  protected String seriesId;

  /** Organization id */
  @OneToOne(targetEntity = JpaOrganization.class)
  @JoinColumn(name = "organization", referencedColumnName = "id")
  protected JpaOrganization organization;

  /** The media package deleted */
  @Column(name = "deletion_date")
  @Temporal(TemporalType.TIMESTAMP)
  private Date deletionDate;

  /** The media package deleted */
  @Column(name = "modification_date")
  @Temporal(TemporalType.TIMESTAMP)
  private Date modificationDate;

  /** Serialized media package */
  @Lob
  @Column(name = "mediapackage_xml", length = 65535)
  private String mediaPackageXML;

  /** Serialized access control */
  @Lob
  @Column(name = "access_control", length = 65535)
  protected String accessControl;

  /**
   * Default constructor without any import.
   */
  public SearchEntity() {
  }

  /**
   * Returns media package id.
   *
   * @return media package id
   */
  public String getMediaPackageId() {
    return mediaPackageId;
  }

  /**
   * Sets media package id. Id length limit is 128 charachters.
   *
   * @param mediaPackageId
   */
  public void setMediaPackageId(String mediaPackageId) {
    this.mediaPackageId = mediaPackageId;
  }

  /**
   * Returns serialized media package.
   *
   * @return serialized media package
   */
  public String getMediaPackageXML() {
    return mediaPackageXML;
  }

  /**
   * Sets serialized media package
   *
   * @param mediaPackageXML
   */
  public void setMediaPackageXML(String mediaPackageXML) {
    this.mediaPackageXML = mediaPackageXML;
  }

  /**
   * Returns serialized access control
   *
   * @return serialized access control
   */
  public String getAccessControl() {
    return accessControl;
  }

  /**
   * Sets serialized access control.
   *
   * @param accessControl
   *          serialized access control
   */
  public void setAccessControl(String accessControl) {
    this.accessControl = accessControl;
  }

  /**
   * @return the organization
   */
  public JpaOrganization getOrganization() {
    return organization;
  }

  /**
   * @param organization
   *          the organization to set
   */
  public void setOrganization(Organization organization) {
    if (organization instanceof JpaOrganization) {
      this.organization = (JpaOrganization) organization;
    } else {
      this.organization = new JpaOrganization(organization.getId(), organization.getName(), organization.getServers(),
          organization.getAdminRole(), organization.getAnonymousRole(), organization.getProperties());
    }
  }

  /**
   * @return the deletion date
   */
  public Date getDeletionDate() {
    return deletionDate;
  }

  /**
   * Sets the deletion date
   *
   * @param deletionDate
   *          the deletion date
   */
  public void setDeletionDate(Date deletionDate) {
    this.deletionDate = deletionDate;
  }

  /**
   * @return the modification date
   */
  public Date getModificationDate() {
    return modificationDate;
  }

  /**
   * Sets the modification date
   *
   * @param modificationDate
   *          the modification date
   */
  public void setModificationDate(Date modificationDate) {
    this.modificationDate = modificationDate;
  }

  /**
   * @return the series Id for this search entry
   */
  public String getSeriesId() {
    return seriesId;
  }

  /**
   * Sets the series ID
   *
   * @param seriesId
   *          the series ID
   */
  public void setSeriesId(String seriesId) {
    this.seriesId = seriesId;
  }
}

## 内容3
### 文件路径：
modules/search-service-impl/src/main/java/org/opencastproject/search/impl/persistence/SearchServiceDatabase.java
### 代码内容：
/**
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 *
 * The Apereo Foundation licenses this file to you under the Educational
 * Community 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://opensource.org/licenses/ecl2.txt
 *
 * 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.
 *
 */

package org.opencastproject.search.impl.persistence;

import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.security.api.AccessControlList;
import org.opencastproject.security.api.UnauthorizedException;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.data.Tuple;

import java.util.Date;
import java.util.Iterator;

/**
 * API that defines persistent storage of series.
 *
 */
public interface SearchServiceDatabase {

  /**
   * Returns all search entries in persistent storage.
   *
   * @return {@link Tuple} array representing stored media packages
   * @throws SearchServiceDatabaseException
   *           if exception occurs
   */
  Iterator<Tuple<MediaPackage, String>> getAllMediaPackages() throws SearchServiceDatabaseException;

  /**
   * Returns the organization id of the selected media package
   *
   * @param mediaPackageId
   *          the media package id to select
   * @return the organization id
   * @throws NotFoundException
   *           if media package with specified id and version does not exist
   * @throws SearchServiceDatabaseException
   *           if an error occurs
   */
  String getOrganizationId(String mediaPackageId) throws NotFoundException, SearchServiceDatabaseException;

  /**
   * Returns the number of mediapackages in persistent storage, including deleted entries.
   *
   * @return the number of mediapackages in storage
   * @throws SearchServiceDatabaseException
   *           if an error occurs
   */
  int countMediaPackages() throws SearchServiceDatabaseException;

  /**
   * Gets a single media package by its identifier.
   *
   * @param mediaPackageId
   *          the media package identifier
   * @return the media package
   * @throws NotFoundException
   *           if there is no media package with this identifier
   * @throws SearchServiceDatabaseException
   *           if there is a problem communicating with the underlying data store
   */
  MediaPackage getMediaPackage(String mediaPackageId) throws NotFoundException, SearchServiceDatabaseException;

  /**
   * Retrieves ACL for series with given ID.
   *
   * @param mediaPackageId
   *          media package for which ACL will be retrieved
   * @return {@link AccessControlList} of media package or null if media package does not have ACL associated with it
   * @throws NotFoundException
   *           if media package with given ID does not exist
   * @throws SearchServiceDatabaseException
   *           if exception occurred
   */
  AccessControlList getAccessControlList(String mediaPackageId) throws NotFoundException,
          SearchServiceDatabaseException;

  /**
   * Returns the modification date from the selected media package.
   *
   * @param mediaPackageId
   *          the media package id to select
   * @return the modification date
   * @throws NotFoundException
   *           if media package with specified id and version does not exist
   * @throws SearchServiceDatabaseException
   *           if an error occurs
   */
  Date getModificationDate(String mediaPackageId) throws NotFoundException, SearchServiceDatabaseException;

  /**
   * Returns the deletion date from the selected media package.
   *
   * @param mediaPackageId
   *          the media package id to select
   * @return the deletion date
   * @throws NotFoundException
   *           if media package with specified id does not exist
   * @throws SearchServiceDatabaseException
   *           if an error occurs
   */
  Date getDeletionDate(String mediaPackageId) throws NotFoundException, SearchServiceDatabaseException;

  /**
   * Removes media package from persistent storage.
   *
   * @param mediaPackageId
   *          id of the media package to be removed
   * @param deletionDate
   *          the deletion date to set
   * @throws SearchServiceDatabaseException
   *           if exception occurs
   * @throws NotFoundException
   *           if media package with specified id is not found
   */
  void deleteMediaPackage(String mediaPackageId, Date deletionDate) throws SearchServiceDatabaseException,
          NotFoundException;

  /**
   * Store (or update) media package.
   *
   * @param mediaPackage
   *          {@link MediaPackage} to store
   * @param acl
   *          the acl of the media package
   * @param now
   *          the store date
   * @throws SearchServiceDatabaseException
   *           if exception occurs
   * @throws UnauthorizedException
   *           if the current user is not authorized to perform this action
   */
  void storeMediaPackage(MediaPackage mediaPackage, AccessControlList acl, Date now)
          throws SearchServiceDatabaseException, UnauthorizedException;

}

## 内容4
### 文件路径：
modules/search-service-impl/src/main/java/org/opencastproject/search/impl/persistence/SearchServiceDatabaseImpl.java
### 代码内容：
/**
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 *
 * The Apereo Foundation licenses this file to you under the Educational
 * Community 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://opensource.org/licenses/ecl2.txt
 *
 * 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.
 *
 */

package org.opencastproject.search.impl.persistence;

import static org.opencastproject.security.api.Permissions.Action.CONTRIBUTE;
import static org.opencastproject.security.api.Permissions.Action.READ;
import static org.opencastproject.security.api.Permissions.Action.WRITE;

import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageParser;
import org.opencastproject.security.api.AccessControlList;
import org.opencastproject.security.api.AccessControlParser;
import org.opencastproject.security.api.AccessControlUtil;
import org.opencastproject.security.api.Organization;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.security.api.UnauthorizedException;
import org.opencastproject.security.api.User;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.data.Tuple;

import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

/**
 * Implements {@link SearchServiceDatabase}. Defines permanent storage for series.
 */
public class SearchServiceDatabaseImpl implements SearchServiceDatabase {

  /** JPA persistence unit name */
  public static final String PERSISTENCE_UNIT = "org.opencastproject.search.impl.persistence";

  /** Logging utilities */
  private static final Logger logger = LoggerFactory.getLogger(SearchServiceDatabaseImpl.class);

  /** Factory used to create {@link EntityManager}s for transactions */
  protected EntityManagerFactory emf;

  /** The security service */
  protected SecurityService securityService;

  /** OSGi DI */
  public void setEntityManagerFactory(EntityManagerFactory emf) {
    this.emf = emf;
  }

  /**
   * Creates {@link EntityManagerFactory} using persistence provider and properties passed via OSGi.
   *
   * @param cc
   * @throws SearchServiceDatabaseException
   */
  public void activate(ComponentContext cc) throws SearchServiceDatabaseException {
    logger.info("Activating persistence manager for search service");
    this.populateSeriesData();
  }

  /**
   * OSGi callback to set the security service.
   *
   * @param securityService
   *          the securityService to set
   */
  public void setSecurityService(SecurityService securityService) {
    this.securityService = securityService;
  }

  private void populateSeriesData() throws SearchServiceDatabaseException {
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
      em = emf.createEntityManager();
      tx = em.getTransaction();
      tx.begin();
      TypedQuery<SearchEntity> q = (TypedQuery<SearchEntity>) em.createNamedQuery("Search.getNoSeries");
      List<SearchEntity> seriesList = q.getResultList();
      for (SearchEntity series : seriesList) {
        String mpSeriesId = MediaPackageParser.getFromXml(series.getMediaPackageXML()).getSeries();
        if (StringUtils.isNotBlank(mpSeriesId) && !mpSeriesId.equals(series.getSeriesId())) {
          logger.info("Fixing missing series ID for episode {}, series is {}", series.getMediaPackageId(), mpSeriesId);
          series.setSeriesId(mpSeriesId);
          em.merge(series);
        }
      }
      tx.commit();
    } catch (Exception e) {
      logger.error("Could not update media package: {}", e.getMessage());
      if (tx.isActive()) {
        tx.rollback();
      }
      throw new SearchServiceDatabaseException(e);
    } finally {
      if (em != null)
        em.close();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.search.impl.persistence.SearchServiceDatabase#deleteMediaPackage(String, Date)
   */
  @Override
  public void deleteMediaPackage(String mediaPackageId, Date deletionDate) throws SearchServiceDatabaseException,
  NotFoundException {
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
      em = emf.createEntityManager();
      tx = em.getTransaction();
      tx.begin();

      SearchEntity searchEntity = getSearchEntity(mediaPackageId, em);
      if (searchEntity == null)
        throw new NotFoundException("No media package with id=" + mediaPackageId + " exists");

      // Ensure this user is allowed to delete this episode
      String accessControlXml = searchEntity.getAccessControl();
      if (accessControlXml != null) {
        AccessControlList acl = AccessControlParser.parseAcl(accessControlXml);
        User currentUser = securityService.getUser();
        Organization currentOrg = securityService.getOrganization();
        if (!AccessControlUtil.isAuthorized(acl, currentUser, currentOrg, WRITE.toString()))
          throw new UnauthorizedException(currentUser + " is not authorized to delete media package " + mediaPackageId);

        searchEntity.setDeletionDate(deletionDate);
        em.merge(searchEntity);
      }
      tx.commit();
    } catch (NotFoundException e) {
      throw e;
    } catch (Exception e) {
      logger.error("Could not delete episode {}: {}", mediaPackageId, e.getMessage());
      if (tx.isActive()) {
        tx.rollback();
      }
      throw new SearchServiceDatabaseException(e);
    } finally {
      if (em != null)
        em.close();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.search.impl.persistence.SearchServiceDatabase#countMediaPackages()
   */
  @Override
  public int countMediaPackages() throws SearchServiceDatabaseException {
    EntityManager em = emf.createEntityManager();
    Query query = em.createNamedQuery("Search.getCount");
    try {
      Long total = (Long) query.getSingleResult();
      return total.intValue();
    } catch (Exception e) {
      logger.error("Could not find number of mediapackages", e);
      throw new SearchServiceDatabaseException(e);
    } finally {
      em.close();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.search.impl.persistence.SearchServiceDatabase#getAllMediaPackages()
   */
  @Override
  @SuppressWarnings("unchecked")
  public Iterator<Tuple<MediaPackage, String>> getAllMediaPackages() throws SearchServiceDatabaseException {
    List<SearchEntity> searchEntities = null;
    EntityManager em = null;
    try {
      em = emf.createEntityManager();
      Query query = em.createNamedQuery("Search.findAll");
      searchEntities = (List<SearchEntity>) query.getResultList();
    } catch (Exception e) {
      logger.error("Could not retrieve all episodes: {}", e.getMessage());
      throw new SearchServiceDatabaseException(e);
    } finally {
      em.close();
    }
    List<Tuple<MediaPackage, String>> mediaPackageList = new LinkedList<Tuple<MediaPackage, String>>();
    try {
      for (SearchEntity entity : searchEntities) {
        MediaPackage mediaPackage = MediaPackageParser.getFromXml(entity.getMediaPackageXML());
        mediaPackageList.add(Tuple.tuple(mediaPackage, entity.getOrganization().getId()));
      }
    } catch (Exception e) {
      logger.error("Could not parse series entity: {}", e.getMessage());
      throw new SearchServiceDatabaseException(e);
    }
    return mediaPackageList.iterator();
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.search.impl.persistence.SearchServiceDatabase#getAccessControlList(String)
   */
  @Override
  public AccessControlList getAccessControlList(String mediaPackageId) throws NotFoundException,
  SearchServiceDatabaseException {
    EntityManager em = null;
    try {
      em = emf.createEntityManager();
      SearchEntity entity = getSearchEntity(mediaPackageId, em);
      if (entity == null) {
        throw new NotFoundException("Could not found media package with ID " + mediaPackageId);
      }
      if (entity.getAccessControl() == null) {
        return null;
      } else {
        return AccessControlParser.parseAcl(entity.getAccessControl());
      }
    } catch (NotFoundException e) {
      throw e;
    } catch (Exception e) {
      logger.error("Could not retrieve ACL {}: {}", mediaPackageId, e.getMessage());
      throw new SearchServiceDatabaseException(e);
    } finally {
      em.close();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.search.impl.persistence.SearchServiceDatabase#storeMediaPackage(MediaPackage,
   *      AccessControlList, Date)
   */
  @Override
  public void storeMediaPackage(MediaPackage mediaPackage, AccessControlList acl, Date now)
          throws SearchServiceDatabaseException, UnauthorizedException {
    String mediaPackageXML = MediaPackageParser.getAsXml(mediaPackage);
    String mediaPackageId = mediaPackage.getIdentifier().toString();
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
      em = emf.createEntityManager();
      tx = em.getTransaction();
      tx.begin();
      SearchEntity entity = getSearchEntity(mediaPackageId, em);
      if (entity == null) {
        // Create new search entity
        SearchEntity searchEntity = new SearchEntity();
        searchEntity.setOrganization(securityService.getOrganization());
        searchEntity.setMediaPackageId(mediaPackageId);
        searchEntity.setMediaPackageXML(mediaPackageXML);
        searchEntity.setAccessControl(AccessControlParser.toXml(acl));
        searchEntity.setModificationDate(now);
        searchEntity.setSeriesId(mediaPackage.getSeries());
        em.persist(searchEntity);
      } else {
        // Ensure this user is allowed to update this media package
        String accessControlXml = entity.getAccessControl();
        if (accessControlXml != null) {
          AccessControlList accessList = AccessControlParser.parseAcl(accessControlXml);
          User currentUser = securityService.getUser();
          Organization currentOrg = securityService.getOrganization();
          if (!AccessControlUtil.isAuthorized(accessList, currentUser, currentOrg, WRITE.toString())) {
            throw new UnauthorizedException(currentUser + " is not authorized to update media package "
                    + mediaPackageId);
          }
        }
        entity.setOrganization(securityService.getOrganization());
        entity.setMediaPackageId(mediaPackageId);
        entity.setMediaPackageXML(mediaPackageXML);
        entity.setAccessControl(AccessControlParser.toXml(acl));
        entity.setModificationDate(now);
        entity.setDeletionDate(null);
        entity.setSeriesId(mediaPackage.getSeries());
        em.merge(entity);
      }
      tx.commit();
    } catch (Exception e) {
      logger.error("Could not update media package: {}", e.getMessage());
      if (tx.isActive()) {
        tx.rollback();
      }
      throw new SearchServiceDatabaseException(e);
    } finally {
      if (em != null)
        em.close();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.search.impl.persistence.SearchServiceDatabase#getMediaPackage(String)
   */
  @Override
  public MediaPackage getMediaPackage(String mediaPackageId) throws NotFoundException, SearchServiceDatabaseException {
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
      em = emf.createEntityManager();
      tx = em.getTransaction();
      tx.begin();
      SearchEntity episodeEntity = getSearchEntity(mediaPackageId, em);
      if (episodeEntity == null)
        throw new NotFoundException("No episode with id=" + mediaPackageId + " exists");
      // Ensure this user is allowed to read this episode
      String accessControlXml = episodeEntity.getAccessControl();
      if (accessControlXml != null) {
        AccessControlList acl = AccessControlParser.parseAcl(accessControlXml);
        User currentUser = securityService.getUser();
        Organization currentOrg = securityService.getOrganization();
        // There are several reasons a user may need to load a episode: to read content, to edit it, or add content
        if (!AccessControlUtil.isAuthorized(acl, currentUser, currentOrg, READ.toString())
                && !AccessControlUtil.isAuthorized(acl, currentUser, currentOrg, CONTRIBUTE.toString())
                && !AccessControlUtil.isAuthorized(acl, currentUser, currentOrg, WRITE.toString())) {
          throw new UnauthorizedException(currentUser + " is not authorized to see episode " + mediaPackageId);
        }
      }
      return MediaPackageParser.getFromXml(episodeEntity.getMediaPackageXML());
    } catch (NotFoundException e) {
      throw e;
    } catch (Exception e) {
      logger.error("Could not get episode {} from database: {} ", mediaPackageId, e.getMessage());
      if (tx.isActive()) {
        tx.rollback();
      }
      throw new SearchServiceDatabaseException(e);
    } finally {
      if (em != null)
        em.close();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.search.impl.persistence.SearchServiceDatabase#getModificationDate(String)
   */
  @Override
  public Date getModificationDate(String mediaPackageId) throws NotFoundException, SearchServiceDatabaseException {
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
      em = emf.createEntityManager();
      tx = em.getTransaction();
      tx.begin();
      SearchEntity searchEntity = getSearchEntity(mediaPackageId, em);
      if (searchEntity == null)
        throw new NotFoundException("No media package with id=" + mediaPackageId + " exists");
      // Ensure this user is allowed to read this media package
      String accessControlXml = searchEntity.getAccessControl();
      if (accessControlXml != null) {
        AccessControlList acl = AccessControlParser.parseAcl(accessControlXml);
        User currentUser = securityService.getUser();
        Organization currentOrg = securityService.getOrganization();
        if (!AccessControlUtil.isAuthorized(acl, currentUser, currentOrg, READ.toString()))
          throw new UnauthorizedException(currentUser + " is not authorized to read media package " + mediaPackageId);
      }
      return searchEntity.getModificationDate();
    } catch (NotFoundException e) {
      throw e;
    } catch (Exception e) {
      logger.error("Could not get modification date {}: {}", mediaPackageId, e.getMessage());
      if (tx.isActive()) {
        tx.rollback();
      }
      throw new SearchServiceDatabaseException(e);
    } finally {
      if (em != null)
        em.close();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.search.impl.persistence.SearchServiceDatabase#getDeletionDate(String)
   */
  @Override
  public Date getDeletionDate(String mediaPackageId) throws NotFoundException, SearchServiceDatabaseException {
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
      em = emf.createEntityManager();
      tx = em.getTransaction();
      tx.begin();
      SearchEntity searchEntity = getSearchEntity(mediaPackageId, em);
      if (searchEntity == null) {
        throw new NotFoundException("No media package with id=" + mediaPackageId + " exists");
      }
      // Ensure this user is allowed to read this media package
      String accessControlXml = searchEntity.getAccessControl();
      if (accessControlXml != null) {
        AccessControlList acl = AccessControlParser.parseAcl(accessControlXml);
        User currentUser = securityService.getUser();
        Organization currentOrg = securityService.getOrganization();
        if (!AccessControlUtil.isAuthorized(acl, currentUser, currentOrg, READ.toString()))
          throw new UnauthorizedException(currentUser + " is not authorized to read media package " + mediaPackageId);
      }
      return searchEntity.getDeletionDate();
    } catch (NotFoundException e) {
      throw e;
    } catch (Exception e) {
      logger.error("Could not get deletion date {}: {}", mediaPackageId, e.getMessage());
      if (tx.isActive()) {
        tx.rollback();
      }
      throw new SearchServiceDatabaseException(e);
    } finally {
      if (em != null)
        em.close();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.opencastproject.search.impl.persistence.SearchServiceDatabase#getOrganizationId(String)
   */
  @Override
  public String getOrganizationId(String mediaPackageId) throws NotFoundException, SearchServiceDatabaseException {
    EntityManager em = null;
    EntityTransaction tx = null;
    try {
      em = emf.createEntityManager();
      tx = em.getTransaction();
      tx.begin();
      SearchEntity searchEntity = getSearchEntity(mediaPackageId, em);
      if (searchEntity == null)
        throw new NotFoundException("No media package with id=" + mediaPackageId + " exists");
      // Ensure this user is allowed to read this media package
      String accessControlXml = searchEntity.getAccessControl();
      if (accessControlXml != null) {
        AccessControlList acl = AccessControlParser.parseAcl(accessControlXml);
        User currentUser = securityService.getUser();
        Organization currentOrg = securityService.getOrganization();
        if (!AccessControlUtil.isAuthorized(acl, currentUser, currentOrg, READ.toString()))
          throw new UnauthorizedException(currentUser + " is not authorized to read media package " + mediaPackageId);
      }
      return searchEntity.getOrganization().getId();
    } catch (NotFoundException e) {
      throw e;
    } catch (Exception e) {
      logger.error("Could not get deletion date {}: {}", mediaPackageId, e.getMessage());
      if (tx.isActive()) {
        tx.rollback();
      }
      throw new SearchServiceDatabaseException(e);
    } finally {
      if (em != null)
        em.close();
    }
  }

  /**
   * Gets a search entity by it's id, using the current organizational context.
   *
   * @param id
   *          the media package identifier
   * @param em
   *          an open entity manager
   * @return the search entity, or null if not found
   */
  private SearchEntity getSearchEntity(String id, EntityManager em) {
    Query q = em.createNamedQuery("Search.findById").setParameter("mediaPackageId", id);
    try {
      return (SearchEntity) q.getSingleResult();
    } catch (NoResultException e) {
      return null;
    }
  }
}

## 内容5
### 文件路径：
modules/search-service-impl/src/main/java/org/opencastproject/search/impl/solr/SolrIndexManager.java
### 代码内容：
/**
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 *
 * The Apereo Foundation licenses this file to you under the Educational
 * Community 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://opensource.org/licenses/ecl2.txt
 *
 * 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.
 *
 */

package org.opencastproject.search.impl.solr;

import static org.opencastproject.security.api.Permissions.Action.READ;
import static org.opencastproject.security.api.Permissions.Action.WRITE;
import static org.opencastproject.util.RequireUtil.notNull;
import static org.opencastproject.util.data.Collections.flatMap;
import static org.opencastproject.util.data.Collections.head;
import static org.opencastproject.util.data.Collections.map;
import static org.opencastproject.util.data.Option.option;

import org.opencastproject.mediapackage.Attachment;
import org.opencastproject.mediapackage.Catalog;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageElement;
import org.opencastproject.mediapackage.MediaPackageElements;
import org.opencastproject.mediapackage.MediaPackageException;
import org.opencastproject.mediapackage.MediaPackageParser;
import org.opencastproject.mediapackage.MediaPackageReference;
import org.opencastproject.metadata.api.MetadataValue;
import org.opencastproject.metadata.api.StaticMetadata;
import org.opencastproject.metadata.api.StaticMetadataService;
import org.opencastproject.metadata.api.util.Interval;
import org.opencastproject.metadata.dublincore.DCMIPeriod;
import org.opencastproject.metadata.dublincore.DublinCore;
import org.opencastproject.metadata.dublincore.DublinCoreCatalog;
import org.opencastproject.metadata.dublincore.DublinCoreValue;
import org.opencastproject.metadata.dublincore.EncodingSchemeUtils;
import org.opencastproject.metadata.dublincore.Temporal;
import org.opencastproject.metadata.mpeg7.AudioVisual;
import org.opencastproject.metadata.mpeg7.FreeTextAnnotation;
import org.opencastproject.metadata.mpeg7.KeywordAnnotation;
import org.opencastproject.metadata.mpeg7.MediaDuration;
import org.opencastproject.metadata.mpeg7.MediaTime;
import org.opencastproject.metadata.mpeg7.MediaTimePoint;
import org.opencastproject.metadata.mpeg7.Mpeg7Catalog;
import org.opencastproject.metadata.mpeg7.Mpeg7CatalogService;
import org.opencastproject.metadata.mpeg7.MultimediaContent;
import org.opencastproject.metadata.mpeg7.MultimediaContentType;
import org.opencastproject.metadata.mpeg7.SpatioTemporalDecomposition;
import org.opencastproject.metadata.mpeg7.TextAnnotation;
import org.opencastproject.metadata.mpeg7.Video;
import org.opencastproject.metadata.mpeg7.VideoSegment;
import org.opencastproject.metadata.mpeg7.VideoText;
import org.opencastproject.search.api.SearchResultItem.SearchResultItemType;
import org.opencastproject.search.impl.persistence.SearchServiceDatabaseException;
import org.opencastproject.security.api.AccessControlEntry;
import org.opencastproject.security.api.AccessControlList;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.security.api.UnauthorizedException;
import org.opencastproject.series.api.SeriesException;
import org.opencastproject.series.api.SeriesService;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.SolrUtils;
import org.opencastproject.util.data.Function;
import org.opencastproject.util.data.Option;
import org.opencastproject.workspace.api.Workspace;

import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.servlet.SolrRequestParsers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * Utility class used to manage the search index.
 */
public class SolrIndexManager {

  /** Logging facility */
  private static final Logger logger = LoggerFactory.getLogger(SolrIndexManager.class);

  /** Connection to the database */
  private SolrServer solrServer = null;

  /**
   * Factor multiplied to fine tune relevance and confidence impact on important keyword decision. importance =
   * RELEVANCE_BOOST * relevance + confidence
   */
  private static final double RELEVANCE_BOOST = 2.0;

  /** Number of characters an important should have at least. */
  private static final int MAX_CHAR = 3;

  /** Maximum number of important keywords to detect. */
  private static final int MAX_IMPORTANT_COUNT = 10;

  /** List of metadata services sorted by priority in reverse order. */
  private List<StaticMetadataService> mdServices;

  private SeriesService seriesService;

  private Mpeg7CatalogService mpeg7CatalogService;

  private Workspace workspace;

  private SecurityService securityService;

  /** Convert a DublinCoreValue into a date. */
  private static Function<DublinCoreValue, Option<Date>> toDateF = new Function<DublinCoreValue, Option<Date>>() {
    @Override
    public Option<Date> apply(DublinCoreValue v) {
      return EncodingSchemeUtils.decodeTemporal(v).fold(new Temporal.Match<Option<Date>>() {
        @Override
        public Option<Date> period(DCMIPeriod period) {
          return option(period.getStart());
        }

        @Override
        public Option<Date> instant(Date instant) {
          return Option.some(instant);
        }

        @Override
        public Option<Date> duration(long duration) {
          return Option.none();
        }
      });
    }
  };

  /** Convert a DublinCoreValue into a duration (long). */
  private static Function<DublinCoreValue, Option<Long>> toDurationF = new Function<DublinCoreValue, Option<Long>>() {
    @Override
    public Option<Long> apply(DublinCoreValue dublinCoreValue) {
      return option(EncodingSchemeUtils.decodeDuration(dublinCoreValue));
    }
  };

  /** Dynamic reference. */
  public void setStaticMetadataServices(List<StaticMetadataService> mdServices) {
    this.mdServices = new ArrayList<StaticMetadataService>(mdServices);
    Collections.sort(this.mdServices, new Comparator<StaticMetadataService>() {
      @Override
      public int compare(StaticMetadataService a, StaticMetadataService b) {
        return b.getPriority() - a.getPriority();
      }
    });
  }

  /**
   * Creates a new management instance for the search index.
   *
   * @param connection
   *          connection to the database
   */
  public SolrIndexManager(SolrServer connection, Workspace workspace, List<StaticMetadataService> mdServices,
          SeriesService seriesService, Mpeg7CatalogService mpeg7CatalogService, SecurityService securityService) {

    this.solrServer = notNull(connection, "solr connection");
    this.workspace = notNull(workspace, "workspace");
    this.seriesService = notNull(seriesService, "series service");
    this.mpeg7CatalogService = notNull(mpeg7CatalogService, "mpeg7 service");
    this.securityService = notNull(securityService, "security service");
    setStaticMetadataServices(notNull(mdServices, "metadata service"));
  }

  /**
   * Clears the search index. Make sure you know what you are doing.
   *
   * @throws SolrServerException
   *           if an errors occurs while talking to solr
   */
  public void clear() throws SolrServerException {
    try {
      solrServer.deleteByQuery("*:*");
      solrServer.commit();
    } catch (IOException e) {
      throw new SolrServerException(e);
    }
  }

  /**
   * Removes the entry with the given id from the database. The entry can either be a series or an episode.
   *
   * @param id
   *          identifier of the series or episode to delete
   * @param deletionDate
   *          the deletion date
   * @throws SolrServerException
   *           if an errors occurs while talking to solr
   */
  public boolean delete(String id, Date deletionDate) throws SolrServerException {
    try {
      // Load the existing episode
      QueryResponse solrResponse = null;
      try {
        SolrQuery query = new SolrQuery(Schema.ID + ":" + ClientUtils.escapeQueryChars(id) + " AND -"
                + Schema.OC_DELETED + ":[* TO *]");
        solrResponse = solrServer.query(query);
      } catch (Exception e1) {
        throw new SolrServerException(e1);
      }

      // Did we find the episode?
      if (solrResponse.getResults().size() == 0) {
        logger.warn("Trying to delete non-existing media package {} from the search index", id);
        return false;
      }

      // Use all existing fields
      SolrDocument doc = solrResponse.getResults().get(0);
      SolrInputDocument inputDocument = new SolrInputDocument();
      for (String field : doc.getFieldNames()) {
        inputDocument.setField(field, doc.get(field));
      }

      // Set the oc_deleted field to the current date, then update
      Schema.setOcDeleted(inputDocument, deletionDate);
      solrServer.add(inputDocument);
      solrServer.commit();
      return true;
    } catch (IOException e) {
      throw new SolrServerException(e);
    }
  }

  /**
   * Posts the media package to solr. Depending on what is referenced in the media package, the method might create one
   * or two entries: one for the episode and one for the series that the episode belongs to.
   *
   * This implementation of the search service removes all references to non "engage/download" media tracks
   *
   * @param sourceMediaPackage
   *          the media package to post
   * @param acl
   *          the access control list for this mediapackage
   * @param now
   *          current date
   * @throws SolrServerException
   *           if an errors occurs while talking to solr
   */
  public boolean add(MediaPackage sourceMediaPackage, AccessControlList acl, Date now) throws SolrServerException,
          UnauthorizedException {
    try {
      SolrInputDocument episodeDocument = createEpisodeInputDocument(sourceMediaPackage, acl);
      Schema.setOcModified(episodeDocument, now);

      SolrInputDocument seriesDocument = createSeriesInputDocument(sourceMediaPackage.getSeries(), acl);
      if (seriesDocument != null)
        Schema.enrich(episodeDocument, seriesDocument);

      // If neither an episode nor a series was contained, there is no point in trying to update
      if (episodeDocument == null && seriesDocument == null) {
        logger.warn("Neither episode nor series metadata found");
        return false;
      }

      // Post everything to the search index
      if (episodeDocument != null)
        solrServer.add(episodeDocument);
      if (seriesDocument != null)
        solrServer.add(seriesDocument);
      solrServer.commit();
      return true;
    } catch (Exception e) {
      logger.error("Unable to add mediapackage {} to index", sourceMediaPackage.getIdentifier());
      throw new SolrServerException(e);
    }
  }

  /**
   * Posts the media package to solr. Depending on what is referenced in the media package, the method might create one
   * or two entries: one for the episode and one for the series that the episode belongs to.
   *
   * This implementation of the search service removes all references to non "engage/download" media tracks
   *
   * @param sourceMediaPackage
   *          the media package to post
   * @param acl
   *          the access control list for this mediapackage
   * @param deletionDate
   *          the deletion date
   * @param modificationDate
   *          the modification date
   * @return true if successfully added
   * @throws SolrServerException
   *           if an errors occurs while talking to solr
   */
  public boolean add(MediaPackage sourceMediaPackage, AccessControlList acl, Date deletionDate, Date modificationDate)
          throws SolrServerException {
    try {
      SolrInputDocument episodeDocument = createEpisodeInputDocument(sourceMediaPackage, acl);

      SolrInputDocument seriesDocument = createSeriesInputDocument(sourceMediaPackage.getSeries(), acl);
      if (seriesDocument != null)
        Schema.enrich(episodeDocument, seriesDocument);

      Schema.setOcModified(episodeDocument, modificationDate);
      if (deletionDate != null)
        Schema.setOcDeleted(episodeDocument, deletionDate);

      solrServer.add(episodeDocument);
      solrServer.add(seriesDocument);
      solrServer.commit();
      return true;
    } catch (Exception e) {
      logger.error("Unable to add mediapackage {} to index", sourceMediaPackage.getIdentifier());
      try {
        solrServer.rollback();
      } catch (IOException e1) {
        throw new SolrServerException(e1);
      }
      throw new SolrServerException(e);
    }
  }

  /**
   * Creates a solr input document for the episode metadata of the media package.
   *
   * @param mediaPackage
   *          the media package
   * @param acl
   *          the access control list for this mediapackage
   * @return an input document ready to be posted to solr
   * @throws MediaPackageException
   *           if serialization of the media package fails
   */
  private SolrInputDocument createEpisodeInputDocument(MediaPackage mediaPackage, AccessControlList acl)
          throws MediaPackageException, IOException {

    SolrInputDocument doc = new SolrInputDocument();
    String mediaPackageId = mediaPackage.getIdentifier().toString();

    // Fill the input document
    Schema.setId(doc, mediaPackageId);
    // /
    // OC specific fields
    Schema.setOcMediatype(doc, SearchResultItemType.AudioVisual.toString());
    Schema.setOrganization(doc, securityService.getOrganization().getId());
    Schema.setOcMediapackage(doc, MediaPackageParser.getAsXml(mediaPackage));
    Schema.setOcElementtags(doc, tags(mediaPackage));
    Schema.setOcElementflavors(doc, flavors(mediaPackage));
    // Add cover
    Attachment[] cover = mediaPackage.getAttachments(MediaPackageElements.MEDIAPACKAGE_COVER_FLAVOR);
    if (cover != null && cover.length > 0) {
      Schema.setOcCover(doc, cover[0].getURI().toString());
    }

    // /
    // Add standard dublin core fields
    // naive approach. works as long as only setters, not adders are available in the schema
    for (StaticMetadata md : getMetadata(mdServices, mediaPackage))
      addEpisodeMetadata(doc, md);

    // /
    // Add mpeg7
    logger.debug("Looking for mpeg-7 catalogs containing segment texts");
    Catalog[] mpeg7Catalogs = mediaPackage.getCatalogs(MediaPackageElements.TEXTS);
    if (mpeg7Catalogs.length == 0) {
      logger.debug("No text catalogs found, trying segments only");
      mpeg7Catalogs = mediaPackage.getCatalogs(MediaPackageElements.SEGMENTS);
    }
    // TODO: merge the segments from each mpeg7 if there is more than one mpeg7 catalog
    if (mpeg7Catalogs.length > 0) {
      try {
        Mpeg7Catalog mpeg7Catalog = loadMpeg7Catalog(mpeg7Catalogs[0]);
        addMpeg7Metadata(doc, mediaPackage, mpeg7Catalog);
      } catch (IOException e) {
        logger.error("Error loading mpeg7 catalog. Skipping catalog", e);
      }
    } else {
      logger.debug("No segmentation catalog found");
    }

    // /
    // Add authorization
    setAuthorization(doc, securityService, acl);

    return doc;
  }

  static void addEpisodeMetadata(final SolrInputDocument doc, final StaticMetadata md) {
    Schema.fill(doc, new Schema.FieldCollector() {
      @Override
      public Option<String> getId() {
        return Option.none();
      }

      @Override
      public Option<String> getOrganization() {
        return Option.none();
      }

      @Override
      public Option<Date> getDcCreated() {
        return md.getCreated();
      }

      @Override
      public Option<Long> getDcExtent() {
        return md.getExtent();
      }

      @Override
      public Option<String> getDcLanguage() {
        return md.getLanguage();
      }

      @Override
      public Option<String> getDcIsPartOf() {
        return md.getIsPartOf();
      }

      @Override
      public Option<String> getDcReplaces() {
        return md.getReplaces();
      }

      @Override
      public Option<String> getDcType() {
        return md.getType();
      }

      @Override
      public Option<Date> getDcAvailableFrom() {
        return md.getAvailable().flatMap(new Function<Interval, Option<Date>>() {
          @Override
          public Option<Date> apply(Interval interval) {
            return interval.fold(new Interval.Match<Option<Date>>() {
              @Override
              public Option<Date> bounded(Date leftBound, Date rightBound) {
                return Option.some(leftBound);
              }

              @Override
              public Option<Date> leftInfinite(Date rightBound) {
                return Option.none();
              }

              @Override
              public Option<Date> rightInfinite(Date leftBound) {
                return Option.some(leftBound);
              }
            });
          }
        });
      }

      @Override
      public Option<Date> getDcAvailableTo() {
        return md.getAvailable().flatMap(new Function<Interval, Option<Date>>() {
          @Override
          public Option<Date> apply(Interval interval) {
            return interval.fold(new Interval.Match<Option<Date>>() {
              @Override
              public Option<Date> bounded(Date leftBound, Date rightBound) {
                return Option.some(rightBound);
              }

              @Override
              public Option<Date> leftInfinite(Date rightBound) {
                return Option.some(rightBound);
              }

              @Override
              public Option<Date> rightInfinite(Date leftBound) {
                return Option.none();
              }
            });
          }
        });
      }

      @Override
      public List<DField<String>> getDcTitle() {
        return fromMValue(md.getTitles());
      }

      @Override
      public List<DField<String>> getDcSubject() {
        return fromMValue(md.getSubjects());
      }

      @Override
      public List<DField<String>> getDcCreator() {
        return fromMValue(md.getCreators());
      }

      @Override
      public List<DField<String>> getDcPublisher() {
        return fromMValue(md.getPublishers());
      }

      @Override
      public List<DField<String>> getDcContributor() {
        return fromMValue(md.getContributors());
      }

      @Override
      public List<DField<String>> getDcDescription() {
        return fromMValue(md.getDescription());
      }

      @Override
      public List<DField<String>> getDcRightsHolder() {
        return fromMValue(md.getRightsHolders());
      }

      @Override
      public List<DField<String>> getDcSpatial() {
        return fromMValue(md.getSpatials());
      }

      @Override
      public List<DField<String>> getDcAccessRights() {
        return fromMValue(md.getAccessRights());
      }

      @Override
      public List<DField<String>> getDcLicense() {
        return fromMValue(md.getLicenses());
      }

      @Override
      public Option<String> getOcMediatype() {
        return Option.none(); // set elsewhere
      }

      @Override
      public Option<String> getOcMediapackage() {
        return Option.none(); // set elsewhere
      }

      @Override
      public Option<String> getOcKeywords() {
        return Option.none(); // set elsewhere
      }

      @Override
      public Option<String> getOcCover() {
        return Option.none(); // set elsewhere
      }

      @Override
      public Option<Date> getOcModified() {
        return Option.none(); // set elsewhere
      }

      @Override
      public Option<Date> getOcDeleted() {
        return Option.none(); // set elsewhere
      }

      @Override
      public Option<String> getOcElementtags() {
        return Option.none(); // set elsewhere
      }

      @Override
      public Option<String> getOcElementflavors() {
        return Option.none(); // set elsewhere
      }

      @Override
      public List<DField<String>> getOcAcl() {
        return Collections.EMPTY_LIST; // set elsewhere
      }

      @Override
      public List<DField<String>> getSegmentText() {
        return Collections.EMPTY_LIST; // set elsewhere
      }

      @Override
      public List<DField<String>> getSegmentHint() {
        return Collections.EMPTY_LIST; // set elsewhere
      }
    });
  }

  static List<DField<String>> fromMValue(List<MetadataValue<String>> as) {
    return map(as, new ArrayList<DField<String>>(), new Function<MetadataValue<String>, DField<String>>() {
      @Override
      public DField<String> apply(MetadataValue<String> v) {
        return new DField<String>(v.getValue(), v.getLanguage());
      }
    });
  }

  static List<DField<String>> fromDCValue(List<DublinCoreValue> as) {
    return map(as, new ArrayList<DField<String>>(), new Function<DublinCoreValue, DField<String>>() {
      @Override
      public DField<String> apply(DublinCoreValue v) {
        return new DField<String>(v.getValue(), v.getLanguage());
      }
    });
  }

  /**
   * Adds authorization fields to the solr document.
   *
   * @param doc
   *          the solr document
   * @param acl
   *          the access control list
   */
  static void setAuthorization(SolrInputDocument doc, SecurityService securityService, AccessControlList acl) {
    Map<String, List<String>> permissions = new HashMap<String, List<String>>();

    // Define containers for common permissions
    List<String> reads = new ArrayList<String>();
    permissions.put(READ.toString(), reads);
    List<String> writes = new ArrayList<String>();
    permissions.put(WRITE.toString(), writes);

    String adminRole = securityService.getOrganization().getAdminRole();

    // The admin user can read and write
    if (adminRole != null) {
      reads.add(adminRole);
      writes.add(adminRole);
    }

    for (AccessControlEntry entry : acl.getEntries()) {
      if (!entry.isAllow()) {
        logger.warn("Search service does not support denial via ACL, ignoring {}", entry);
        continue;
      }
      List<String> actionPermissions = permissions.get(entry.getAction());
      /*
       * MH-8353 a series could have a permission defined we don't know how to handle -DH
       */
      if (actionPermissions == null) {
        logger.warn("Search service doesn't know how to handle action: " + entry.getAction());
        continue;
      }
      if (acl == null) {
        actionPermissions = new ArrayList<String>();
        permissions.put(entry.getAction(), actionPermissions);
      }
      actionPermissions.add(entry.getRole());

    }

    // Write the permissions to the solr document
    for (Map.Entry<String, List<String>> entry : permissions.entrySet()) {
      Schema.setOcAcl(doc, new DField<String>(mkString(entry.getValue(), " "), entry.getKey()));
    }
  }

  static String mkString(Collection<?> as, String sep) {
    StringBuffer b = new StringBuffer();
    for (Object a : as) {
      b.append(a).append(sep);
    }
    return b.substring(0, b.length() - sep.length());
  }

  private Mpeg7Catalog loadMpeg7Catalog(Catalog catalog) throws IOException {
    try (InputStream in = workspace.read(catalog.getURI())) {
      return mpeg7CatalogService.load(in);
    } catch (NotFoundException e) {
      throw new IOException("Unable to load metadata from mpeg7 catalog " + catalog);
    }
  }

  /**
   * Creates a solr input document for the series metadata of the media package.
   *
   * @param seriesId
   *          the id of the series
   * @param acl
   *          the access control list for this mediapackage
   * @return an input document ready to be posted to solr or null
   */
  private SolrInputDocument createSeriesInputDocument(String seriesId, AccessControlList acl) throws IOException,
          UnauthorizedException {

    if (seriesId == null)
      return null;
    DublinCoreCatalog dc = null;
    try {
      dc = seriesService.getSeries(seriesId);
    } catch (SeriesException e) {
      logger.debug("No series dublincore found for series id " + seriesId);
      return null;
    } catch (NotFoundException e) {
      logger.debug("No series dublincore found for series id " + seriesId);
      return null;
    }

    SolrInputDocument doc = new SolrInputDocument();

    // Populate document with existing data
    try {
      StringBuffer query = new StringBuffer("q=");
      query = query.append(Schema.ID).append(":").append(SolrUtils.clean(seriesId));
      SolrParams params = SolrRequestParsers.parseQueryString(query.toString());
      QueryResponse solrResponse = solrServer.query(params);
      if (solrResponse.getResults().size() > 0) {
        SolrDocument existingSolrDocument = solrResponse.getResults().get(0);
        for (String fieldName : existingSolrDocument.getFieldNames()) {
          doc.addField(fieldName, existingSolrDocument.getFieldValue(fieldName));
        }
      }
    } catch (Exception e) {
      logger.error("Error trying to load series " + seriesId, e);
    }

    // Fill document
    Schema.setId(doc, seriesId);

    // OC specific fields
    Schema.setOrganization(doc, securityService.getOrganization().getId());
    Schema.setOcMediatype(doc, SearchResultItemType.Series.toString());
    Schema.setOcModified(doc, new Date());

    // DC fields
    addSeriesMetadata(doc, dc);

    // Authorization
    setAuthorization(doc, securityService, acl);

    return doc;
  }

  /**
   * Add the standard dublin core fields to a series document.
   *
   * @param doc
   *          the solr document to fill
   * @param dc
   *          the dublin core catalog to get the data from
   */
  static void addSeriesMetadata(final SolrInputDocument doc, final DublinCoreCatalog dc) throws IOException {
    Schema.fill(doc, new Schema.FieldCollector() {
      @Override
      public Option<String> getId() {
        return Option.some(dc.getFirst(DublinCore.PROPERTY_IDENTIFIER));
      }

      @Override
      public Option<String> getOrganization() {
        return Option.none();
      }

      @Override
      public Option<Date> getDcCreated() {
        return head(dc.get(DublinCore.PROPERTY_CREATED)).flatMap(toDateF);
      }

      @Override
      public Option<Long> getDcExtent() {
        return head(dc.get(DublinCore.PROPERTY_EXTENT)).flatMap(toDurationF);
      }

      @Override
      public Option<String> getDcLanguage() {
        return option(dc.getFirst(DublinCore.PROPERTY_LANGUAGE));
      }

      @Override
      public Option<String> getDcIsPartOf() {
        return option(dc.getFirst(DublinCore.PROPERTY_IS_PART_OF));
      }

      @Override
      public Option<String> getDcReplaces() {
        return option(dc.getFirst(DublinCore.PROPERTY_REPLACES));
      }

      @Override
      public Option<String> getDcType() {
        return option(dc.getFirst(DublinCore.PROPERTY_TYPE));
      }

      @Override
      public Option<Date> getDcAvailableFrom() {
        return option(dc.getFirst(DublinCore.PROPERTY_AVAILABLE)).flatMap(new Function<String, Option<Date>>() {
          @Override
          public Option<Date> apply(String s) {
            return option(EncodingSchemeUtils.decodePeriod(s).getStart());
          }
        });
      }

      @Override
      public Option<Date> getDcAvailableTo() {
        return option(dc.getFirst(DublinCore.PROPERTY_AVAILABLE)).flatMap(new Function<String, Option<Date>>() {
          @Override
          public Option<Date> apply(String s) {
            return option(EncodingSchemeUtils.decodePeriod(s).getEnd());
          }
        });
      }

      @Override
      public List<DField<String>> getDcTitle() {
        return fromDCValue(dc.get(DublinCore.PROPERTY_TITLE));
      }

      @Override
      public List<DField<String>> getDcSubject() {
        return fromDCValue(dc.get(DublinCore.PROPERTY_SUBJECT));
      }

      @Override
      public List<DField<String>> getDcCreator() {
        return fromDCValue(dc.get(DublinCore.PROPERTY_CREATOR));
      }

      @Override
      public List<DField<String>> getDcPublisher() {
        return fromDCValue(dc.get(DublinCore.PROPERTY_PUBLISHER));
      }

      @Override
      public List<DField<String>> getDcContributor() {
        return fromDCValue(dc.get(DublinCore.PROPERTY_CONTRIBUTOR));

      }

      @Override
      public List<DField<String>> getDcDescription() {
        return fromDCValue(dc.get(DublinCore.PROPERTY_DESCRIPTION));
      }

      @Override
      public List<DField<String>> getDcRightsHolder() {
        return fromDCValue(dc.get(DublinCore.PROPERTY_RIGHTS_HOLDER));
      }

      @Override
      public List<DField<String>> getDcSpatial() {
        return fromDCValue(dc.get(DublinCore.PROPERTY_SPATIAL));
      }

      @Override
      public List<DField<String>> getDcAccessRights() {
        return fromDCValue(dc.get(DublinCore.PROPERTY_ACCESS_RIGHTS));
      }

      @Override
      public List<DField<String>> getDcLicense() {
        return fromDCValue(dc.get(DublinCore.PROPERTY_LICENSE));
      }

      @Override
      public Option<String> getOcMediatype() {
        return Option.none();
      }

      @Override
      public Option<String> getOcMediapackage() {
        return Option.none();
      }

      @Override
      public Option<String> getOcKeywords() {
        return Option.none();
      }

      @Override
      public Option<String> getOcCover() {
        return Option.none();
      }

      @Override
      public Option<Date> getOcModified() {
        return Option.none();
      }

      @Override
      public Option<Date> getOcDeleted() {
        return Option.none();
      }

      @Override
      public Option<String> getOcElementtags() {
        return Option.none();
      }

      @Override
      public Option<String> getOcElementflavors() {
        return Option.none();
      }

      @Override
      public List<DField<String>> getOcAcl() {
        return Collections.EMPTY_LIST;
      }

      @Override
      public List<DField<String>> getSegmentText() {
        return Collections.EMPTY_LIST;
      }

      @Override
      public List<DField<String>> getSegmentHint() {
        return Collections.EMPTY_LIST;
      }
    });
  }

  /**
   * Add the mpeg 7 catalog data to the solr document.
   *
   * @param doc
   *          the input document to the solr index
   * @param mpeg7
   *          the mpeg7 catalog
   */
  @SuppressWarnings("unchecked")
  static void addMpeg7Metadata(SolrInputDocument doc, MediaPackage mediaPackage, Mpeg7Catalog mpeg7) {

    // Check for multimedia content
    if (!mpeg7.multimediaContent().hasNext()) {
      logger.warn("Mpeg-7 doesn't contain  multimedia content");
      return;
    }

    // Get the content duration by looking at the first content track. This
    // of course assumes that all tracks are equally long.
    MultimediaContent<? extends MultimediaContentType> mc = mpeg7.multimediaContent().next();
    MultimediaContentType mct = mc.elements().next();
    MediaTime mediaTime = mct.getMediaTime();
    Schema.setDcExtent(doc, mediaTime.getMediaDuration().getDurationInMilliseconds());

    // Check if the keywords have been filled by (manually) added dublin
    // core data. If not, look for the most relevant fields in mpeg-7.
    SortedSet<TextAnnotation> sortedAnnotations = null;
    if (!"".equals(Schema.getOcKeywords(doc))) {
      sortedAnnotations = new TreeSet<TextAnnotation>(new Comparator<TextAnnotation>() {
        @Override
        public int compare(TextAnnotation a1, TextAnnotation a2) {
          if ((RELEVANCE_BOOST * a1.getRelevance() + a1.getConfidence()) > (RELEVANCE_BOOST * a2.getRelevance() + a2
                  .getConfidence()))
            return -1;
          else if ((RELEVANCE_BOOST * a1.getRelevance() + a1.getConfidence()) < (RELEVANCE_BOOST * a2.getRelevance() + a2
                  .getConfidence()))
            return 1;
          return 0;
        }
      });
    }

    // Iterate over the tracks and extract keywords and hints
    Iterator<MultimediaContent<? extends MultimediaContentType>> mmIter = mpeg7.multimediaContent();
    int segmentCount = 0;

    while (mmIter.hasNext()) {
      MultimediaContent<?> multimediaContent = mmIter.next();

      // We need to process visual segments first, due to the way they are handled in the ui.
      for (Iterator<?> iterator = multimediaContent.elements(); iterator.hasNext();) {

        MultimediaContentType type = (MultimediaContentType) iterator.next();
        if (!(type instanceof Video) && !(type instanceof AudioVisual))
          continue;

        // for every segment in the current multimedia content track

        Video video = (Video) type;
        Iterator<VideoSegment> vsegments = (Iterator<VideoSegment>) video.getTemporalDecomposition().segments();
        while (vsegments.hasNext()) {
          VideoSegment segment = vsegments.next();

          StringBuffer segmentText = new StringBuffer();
          StringBuffer hintField = new StringBuffer();

          // Collect the video text elements to a segment text
          SpatioTemporalDecomposition spt = segment.getSpatioTemporalDecomposition();
          if (spt != null) {
            for (VideoText videoText : spt.getVideoText()) {
              if (segmentText.length() > 0)
                segmentText.append(" ");
              segmentText.append(videoText.getText().getText());
              // TODO: Add hint on bounding box
            }
          }

          // Add keyword annotations
          Iterator<TextAnnotation> textAnnotations = segment.textAnnotations();
          while (textAnnotations.hasNext()) {
            TextAnnotation textAnnotation = textAnnotations.next();
            Iterator<?> kwIter = textAnnotation.keywordAnnotations();
            while (kwIter.hasNext()) {
              KeywordAnnotation keywordAnnotation = (KeywordAnnotation) kwIter.next();
              if (segmentText.length() > 0)
                segmentText.append(" ");
              segmentText.append(keywordAnnotation.getKeyword());
            }
          }

          // Add free text annotations
          Iterator<TextAnnotation> freeIter = segment.textAnnotations();
          if (freeIter.hasNext()) {
            Iterator<FreeTextAnnotation> freeTextIter = freeIter.next().freeTextAnnotations();
            while (freeTextIter.hasNext()) {
              FreeTextAnnotation freeTextAnnotation = freeTextIter.next();
              if (segmentText.length() > 0)
                segmentText.append(" ");
              segmentText.append(freeTextAnnotation.getText());
            }
          }

          // add segment text to solr document
          Schema.setSegmentText(doc, new DField<String>(segmentText.toString(), Integer.toString(segmentCount)));

          // get the segments time properties
          MediaTimePoint timepoint = segment.getMediaTime().getMediaTimePoint();
          MediaDuration duration = segment.getMediaTime().getMediaDuration();

          // TODO: define a class with hint field constants
          hintField.append("time=" + timepoint.getTimeInMilliseconds() + "\n");
          hintField.append("duration=" + duration.getDurationInMilliseconds() + "\n");

          // Look for preview images. Their characteristics are that they are
          // attached as attachments with a flavor of preview/<something>.
          String time = timepoint.toString();
          for (Attachment slide : mediaPackage.getAttachments(MediaPackageElements.PRESENTATION_SEGMENT_PREVIEW)) {
            MediaPackageReference ref = slide.getReference();
            if (ref != null && time.equals(ref.getProperty("time"))) {
              hintField.append("preview");
              hintField.append(".");
              hintField.append(ref.getIdentifier());
              hintField.append("=");
              hintField.append(slide.getURI().toString());
              hintField.append("\n");
            }
          }

          logger.trace("Adding segment: " + timepoint.toString());
          Schema.setSegmentHint(doc, new DField<String>(hintField.toString(), Integer.toString(segmentCount)));

          // increase segment counter
          segmentCount++;
        }
      }
    }

    // Put the most important keywords into a special solr field
    if (sortedAnnotations != null) {
      Schema.setOcKeywords(doc, importantKeywordsString(sortedAnnotations).toString());
    }
  }

  /**
   * Generates a string with the most important kewords from the text annotation.
   *
   * @param sortedAnnotations
   * @return The keyword string.
   */
  static StringBuffer importantKeywordsString(SortedSet<TextAnnotation> sortedAnnotations) {

    // important keyword:
    // - high relevance
    // - high confidence
    // - occur often
    // - more than MAX_CHAR chars

    // calculate keyword occurences (histogram) and importance
    ArrayList<String> list = new ArrayList<String>();
    Iterator<TextAnnotation> textAnnotations = sortedAnnotations.iterator();
    TextAnnotation textAnnotation = null;
    String keyword = null;

    HashMap<String, Integer> histogram = new HashMap<String, Integer>();
    HashMap<String, Double> importance = new HashMap<String, Double>();
    int occ = 0;
    double imp;
    while (textAnnotations.hasNext()) {
      textAnnotation = textAnnotations.next();
      Iterator<KeywordAnnotation> keywordAnnotations = textAnnotation.keywordAnnotations();
      while (keywordAnnotations.hasNext()) {
        KeywordAnnotation annotation = keywordAnnotations.next();
        keyword = annotation.getKeyword().toLowerCase();
        if (keyword.length() > MAX_CHAR) {
          occ = 0;
          if (histogram.keySet().contains(keyword)) {
            occ = histogram.get(keyword);
          }
          histogram.put(keyword, occ + 1);

          // here the importance value is calculated
          // from relevance, confidence and frequency of occurence.
          imp = (RELEVANCE_BOOST * getMaxRelevance(keyword, sortedAnnotations) + getMaxConfidence(keyword,
                  sortedAnnotations)) * (occ + 1);
          importance.put(keyword, imp);
        }
      }
    }

    // get the MAX_IMPORTANT_COUNT most important keywords
    StringBuffer buf = new StringBuffer();

    while (list.size() < MAX_IMPORTANT_COUNT && importance.size() > 0) {
      double max = 0.0;
      String maxKeyword = null;

      // get maximum from importance list
      for (Entry<String, Double> entry : importance.entrySet()) {
        keyword = entry.getKey();
        if (max < entry.getValue()) {
          max = entry.getValue();
          maxKeyword = keyword;
        }
      }

      // pop maximum
      importance.remove(maxKeyword);

      // append keyword to string
      if (buf.length() > 0)
        buf.append(" ");
      buf.append(maxKeyword);
    }

    return buf;
  }

  /**
   * Gets the maximum confidence for a given keyword in the text annotation.
   *
   * @param keyword
   * @param sortedAnnotations
   * @return The maximum confidence value.
   */
  static double getMaxConfidence(String keyword, SortedSet<TextAnnotation> sortedAnnotations) {
    double max = 0.0;
    String needle = null;
    TextAnnotation textAnnotation = null;
    Iterator<TextAnnotation> textAnnotations = sortedAnnotations.iterator();
    while (textAnnotations.hasNext()) {
      textAnnotation = textAnnotations.next();
      Iterator<KeywordAnnotation> keywordAnnotations = textAnnotation.keywordAnnotations();
      while (keywordAnnotations.hasNext()) {
        KeywordAnnotation ann = keywordAnnotations.next();
        needle = ann.getKeyword().toLowerCase();
        if (keyword.equals(needle)) {
          if (max < textAnnotation.getConfidence()) {
            max = textAnnotation.getConfidence();
          }
        }
      }
    }
    return max;
  }

  /**
   * Gets the maximum relevance for a given keyword in the text annotation.
   *
   * @param keyword
   * @param sortedAnnotations
   * @return The maximum relevance value.
   */
  static double getMaxRelevance(String keyword, SortedSet<TextAnnotation> sortedAnnotations) {
    double max = 0.0;
    String needle = null;
    TextAnnotation textAnnotation = null;
    Iterator<TextAnnotation> textAnnotations = sortedAnnotations.iterator();
    while (textAnnotations.hasNext()) {
      textAnnotation = textAnnotations.next();
      Iterator<KeywordAnnotation> keywordAnnotations = textAnnotation.keywordAnnotations();
      while (keywordAnnotations.hasNext()) {
        KeywordAnnotation ann = keywordAnnotations.next();
        needle = ann.getKeyword().toLowerCase();
        if (keyword.equals(needle)) {
          if (max < textAnnotation.getRelevance()) {
            max = textAnnotation.getRelevance();
          }
        }
      }
    }
    return max;
  }

  /**
   * Get metadata from all registered metadata services.
   */
  static List<StaticMetadata> getMetadata(final List<StaticMetadataService> mdServices, final MediaPackage mp) {
    return flatMap(mdServices, new ArrayList<StaticMetadata>(),
            new Function<StaticMetadataService, Collection<StaticMetadata>>() {
              @Override
              public Collection<StaticMetadata> apply(StaticMetadataService s) {
                StaticMetadata md = s.getMetadata(mp);
                return md != null ? Arrays.asList(md) : Collections.<StaticMetadata> emptyList();
              }
            });
  }

  /**
   * Return all media package tags as a space separated string.
   */
  static String tags(MediaPackage mp) {
    StringBuilder sb = new StringBuilder();
    for (MediaPackageElement element : mp.getElements()) {
      for (String tag : element.getTags()) {
        sb.append(tag);
        sb.append(" ");
      }
    }
    return sb.toString();
  }

  /**
   * Return all media package flavors as a space separated string.
   */
  static String flavors(MediaPackage mp) {
    StringBuilder sb = new StringBuilder();
    for (MediaPackageElement element : mp.getElements()) {
      if (element.getFlavor() != null) {
        sb.append(element.getFlavor().toString());
        sb.append(" ");
      }
    }
    return sb.toString();
  }

  /**
   * Returns number of entries in search index, across all organizations.
   *
   * @return number of entries in search index
   * @throws SearchServiceDatabaseException
   *           if count cannot be retrieved
   */
  public long count() throws SearchServiceDatabaseException {
    try {
      QueryResponse response = solrServer.query(new SolrQuery("*:*"));
      return response.getResults().getNumFound();
    } catch (SolrServerException e) {
      throw new SearchServiceDatabaseException(e);
    }
  }
}

Output:
【漏洞类型】权限管理漏洞
【严重性】High
【漏洞摘要】此代码在处理媒体包（MediaPackage）和系列（Series）的添加和删除操作时，存在权限管理问题。具体表现为，在同步添加媒体包（addSynchronously）时，仅使用单个媒体包的ACL（Access Control List）来更新系列的ACL，而未合并所有媒体包的ACL，可能导致公共系列被错误地设置为私有。同样，在同步删除媒体包（deleteSynchronously）时，未更新系列的ACL或在删除最后一个媒体包时删除系列，导致未授权用户可能仍然能够访问空系列。
【关键代码模式】
1. `addSynchronously`函数中，未合并所有媒体包的ACL。
2. `deleteSynchronously`函数中，未更新系列的ACL或在删除最后一个媒体包时删除系列。
3. `AccessControlList`的使用和处理。
4. `setAuthorization`函数中，权限设置的逻辑。
【修复方法说明】修复方法通过合并所有媒体包的ACL来更新系列的ACL，同时在删除最后一个媒体包时删除系列，以确保权限管理的正确性和完整性。