Ejemplo n.º 1
0
  public RDFizer() {
    this.stdout = false;
    this.verbose = false;
    this.dryrun = false;
    this.lang = "TURTLE";
    this.processed = new CopyOnWriteArraySet<UUID>();
    this.context = new Context(Context.READ_ONLY);

    this.configurationService = DSpaceServicesFactory.getInstance().getConfigurationService();
    this.contentServiceFactory = ContentServiceFactory.getInstance();
    this.communityService = contentServiceFactory.getCommunityService();
    this.itemService = contentServiceFactory.getItemService();
    this.handleService = HandleServiceFactory.getInstance().getHandleService();
    this.storage = RDFFactory.getInstance().getRDFStorage();
  }
Ejemplo n.º 2
0
/*
 * This class retrieves items by a constructed metadata query evaluated against a set of Item Filters.
 *
 * @author Terry Brady, Georgetown University
 */
@Path("/filtered-items")
public class FilteredItemsResource extends Resource {
  protected ItemService itemService = ContentServiceFactory.getInstance().getItemService();
  protected MetadataFieldService metadataFieldService =
      ContentServiceFactory.getInstance().getMetadataFieldService();
  protected MetadataSchemaService metadataSchemaService =
      ContentServiceFactory.getInstance().getMetadataSchemaService();
  protected CollectionService collectionService =
      ContentServiceFactory.getInstance().getCollectionService();
  protected SiteService siteService = ContentServiceFactory.getInstance().getSiteService();
  protected ConfigurationService configurationService =
      DSpaceServicesFactory.getInstance().getConfigurationService();

  private static Logger log = Logger.getLogger(FilteredItemsResource.class);

  /**
   * Return instance of collection with passed id. You can add more properties through expand
   * parameter.
   *
   * @param expand String in which is what you want to add to returned instance of collection.
   *     Options are: "all", "parentCommunityList", "parentCommunity", "items", "license" and
   *     "logo". If you want to use multiple options, it must be separated by commas.
   * @param limit Limit value for items in list in collection. Default value is 100.
   * @param offset Offset of start index in list of items of collection. Default value is 0.
   * @param filters Comma separated list of Item Filters to use to evaluate against the items in a
   *     collection
   * @param query_field List of metadata fields to evaluate in a metadata query. Each list value is
   *     used in conjunction with a query_op and query_field.
   * @param query_op List of metadata operators to use in a metadata query. Each list value is used
   *     in conjunction with a query_field and query_field.
   * @param query_val List of metadata values to evaluate in a metadata query. Each list value is
   *     used in conjunction with a query_value and query_op.
   * @param collSel List of collections to query.
   * @param headers If you want to access to collection under logged user into context. In headers
   *     must be set header "rest-dspace-token" with passed token from login method.
   * @return Return instance of collection. It can also return status code NOT_FOUND(404) if id of
   *     collection is incorrect or status code UNATHORIZED(401) if user has no permission to read
   *     collection.
   * @throws WebApplicationException It is thrown when was problem with database reading
   *     (SQLException) or problem with creating context(ContextException). It is thrown by
   *     NOT_FOUND and UNATHORIZED status codes, too.
   */
  @GET
  @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
  public org.dspace.rest.common.ItemFilter getItemQuery(
      @QueryParam("expand") String expand,
      @QueryParam("limit") @DefaultValue("100") Integer limit,
      @QueryParam("offset") @DefaultValue("0") Integer offset,
      @QueryParam("userIP") String user_ip,
      @QueryParam("userAgent") String user_agent,
      @QueryParam("xforwarderfor") String xforwarderfor,
      @QueryParam("filters") @DefaultValue("is_item,all_filters") String filters,
      @QueryParam("query_field[]") @DefaultValue("dc.title") List<String> query_field,
      @QueryParam("query_op[]") @DefaultValue("exists") List<String> query_op,
      @QueryParam("query_val[]") @DefaultValue("") List<String> query_val,
      @QueryParam("collSel[]") @DefaultValue("") List<String> collSel,
      @Context HttpHeaders headers,
      @Context HttpServletRequest request) {
    org.dspace.core.Context context = null;
    ItemFilterSet itemFilterSet = new ItemFilterSet(filters, true);
    ItemFilter result = itemFilterSet.getAllFiltersFilter();
    try {
      context = createContext(getUser(headers));
      if (!configurationService.getBooleanProperty("rest.reporting-authenticate", true)) {
        context.turnOffAuthorisationSystem();
      }

      int index = Math.min(query_field.size(), Math.min(query_op.size(), query_val.size()));
      List<ItemFilterQuery> itemFilterQueries = new ArrayList<ItemFilterQuery>();
      for (int i = 0; i < index; i++) {
        itemFilterQueries.add(
            new ItemFilterQuery(query_field.get(i), query_op.get(i), query_val.get(i)));
      }

      String regexClause = configurationService.getProperty("rest.regex-clause");
      if (regexClause == null) {
        regexClause = "";
      }

      List<UUID> uuids = getUuidsFromStrings(collSel);
      List<List<MetadataField>> listFieldList = getMetadataFieldsList(context, query_field);

      Iterator<org.dspace.content.Item> childItems =
          itemService.findByMetadataQuery(
              context, listFieldList, query_op, query_val, uuids, regexClause, offset, limit);

      int count = itemFilterSet.processSaveItems(context, childItems, true, expand);
      writeStats(
          siteService.findSite(context),
          UsageEvent.Action.VIEW,
          user_ip,
          user_agent,
          xforwarderfor,
          headers,
          request,
          context);
      result.annotateQuery(query_field, query_op, query_val);
      result.setUnfilteredItemCount(count);
      context.complete();
    } catch (IOException e) {
      processException(e.getMessage(), context);
    } catch (SQLException e) {
      processException(e.getMessage(), context);
    } catch (AuthorizeException e) {
      processException(e.getMessage(), context);
    } catch (ContextException e) {
      processException("Unauthorized filtered item query. " + e.getMessage(), context);
    } finally {
      processFinally(context);
    }
    return result;
  }

  private List<List<MetadataField>> getMetadataFieldsList(
      org.dspace.core.Context context, List<String> query_field) throws SQLException {
    List<List<MetadataField>> listFieldList = new ArrayList<List<MetadataField>>();
    for (String s : query_field) {
      ArrayList<MetadataField> fields = new ArrayList<MetadataField>();
      listFieldList.add(fields);
      if (s.equals("*")) {
        continue;
      }
      String schema = "";
      String element = "";
      String qualifier = null;
      String[] parts = s.split("\\.");
      if (parts.length > 0) {
        schema = parts[0];
      }
      if (parts.length > 1) {
        element = parts[1];
      }
      if (parts.length > 2) {
        qualifier = parts[2];
      }

      if (Item.ANY.equals(qualifier)) {
        for (MetadataField mf :
            metadataFieldService.findFieldsByElementNameUnqualified(context, schema, element)) {
          fields.add(mf);
        }
      } else {
        MetadataField mf = metadataFieldService.findByElement(context, schema, element, qualifier);
        if (mf != null) {
          fields.add(mf);
        }
      }
    }
    return listFieldList;
  }

  private List<UUID> getUuidsFromStrings(List<String> collSel) {
    List<UUID> uuids = new ArrayList<UUID>();
    for (String s : collSel) {
      try {
        uuids.add(UUID.fromString(s));
      } catch (IllegalArgumentException e) {
        log.warn("Invalid collection UUID: " + s);
      }
    }
    return uuids;
  }
}
Ejemplo n.º 3
0
  protected void logWorkflowEvent(
      Context c,
      String workflowId,
      String previousStepId,
      String previousActionConfigId,
      XmlWorkflowItem wfi,
      EPerson actor,
      Step newStep,
      WorkflowActionConfig newActionConfig)
      throws SQLException {
    try {
      // Fire an event so we can log our action !
      Item item = wfi.getItem();
      Collection myCollection = wfi.getCollection();
      String workflowStepString = null;

      List<EPerson> currentEpersonOwners = new ArrayList<EPerson>();
      List<Group> currentGroupOwners = new ArrayList<Group>();
      // These are only null if our item is sent back to the submission
      if (newStep != null && newActionConfig != null) {
        workflowStepString = workflowId + "." + newStep.getId() + "." + newActionConfig.getId();

        // Retrieve the current owners of the task
        List<ClaimedTask> claimedTasks = claimedTaskService.find(c, wfi, newStep.getId());
        List<PoolTask> pooledTasks = poolTaskService.find(c, wfi);
        for (PoolTask poolTask : pooledTasks) {
          if (poolTask.getEperson() != null) {
            currentEpersonOwners.add(poolTask.getEperson());
          } else {
            currentGroupOwners.add(poolTask.getGroup());
          }
        }
        for (ClaimedTask claimedTask : claimedTasks) {
          currentEpersonOwners.add(claimedTask.getOwner());
        }
      }
      String previousWorkflowStepString = null;
      if (previousStepId != null && previousActionConfigId != null) {
        previousWorkflowStepString =
            workflowId + "." + previousStepId + "." + previousActionConfigId;
      }

      // Fire our usage event !
      UsageWorkflowEvent usageWorkflowEvent =
          new UsageWorkflowEvent(
              c, item, wfi, workflowStepString, previousWorkflowStepString, myCollection, actor);

      usageWorkflowEvent.setEpersonOwners(
          currentEpersonOwners.toArray(new EPerson[currentEpersonOwners.size()]));
      usageWorkflowEvent.setGroupOwners(
          currentGroupOwners.toArray(new Group[currentGroupOwners.size()]));

      DSpaceServicesFactory.getInstance().getEventService().fireEvent(usageWorkflowEvent);
    } catch (Exception e) {
      // Catch all errors we do not want our workflow to crash because the logging threw an
      // exception
      log.error(
          LogManager.getHeader(
              c, "Error while logging workflow event", "Workflow Item: " + wfi.getID()),
          e);
    }
  }
Ejemplo n.º 4
0
  public static void main(String[] argv) throws IOException {
    InputStream propStream;

    Properties sys = System.getProperties();

    // DSpace version
    System.out.printf("DSpace version:  %s\n", Util.getSourceVersion());

    // SCM revision
    Properties scm = new Properties();
    propStream = Version.class.getResourceAsStream("/scm.properties");
    if (null != propStream) {
      scm.load(propStream);
    }
    System.out.printf("  SCM revision:  %s\n", scm.get("revision"));
    System.out.printf("    SCM branch:  %s\n", scm.get("branch"));

    // OS version
    System.out.printf(
        "            OS:  %s(%s) version %s\n",
        sys.get("os.name"), sys.get("os.arch"), sys.get("os.version"));

    // UIs used
    List<WebApp> apps = UtilServiceFactory.getInstance().getWebAppService().getApps();
    System.out.println("  Applications:");
    for (WebApp app : apps) {
      System.out.printf("                %s at %s\n", app.getAppName(), app.getUrl());
    }

    // Is Discovery available?
    ConfigurationService config = DSpaceServicesFactory.getInstance().getConfigurationService();
    String[] consumers = config.getArrayProperty("event.dispatcher.default.consumers");
    String discoveryStatus = "not enabled.";
    for (String consumer : consumers) {
      if (consumer.equals("discovery")) {
        discoveryStatus = "enabled.";
        break;
      }
    }
    System.out.println("     Discovery:  " + discoveryStatus);

    // Java version
    System.out.printf(
        "           JRE:  %s version %s\n", sys.get("java.vendor"), sys.get("java.version"));

    // ant version
    Properties ant = new Properties();
    propStream = Version.class.getResourceAsStream("/ant.properties");
    if (null != propStream) {
      ant.load(propStream);
    }
    System.out.printf("   Ant version:  %s\n", ant.get("ant.version"));

    // maven version
    Properties maven = new Properties();
    propStream = Version.class.getResourceAsStream("/maven.properties");
    if (null != propStream) {
      maven.load(propStream);
    }
    System.out.printf(" Maven version:  %s\n", maven.get("maven.version"));

    // DSpace directory path
    System.out.printf("   DSpace home:  %s\n", config.getProperty("dspace.dir"));
  }
Ejemplo n.º 5
0
/**
 * Invoke ROME library to assemble a generic model of a syndication for the given list of Items and
 * scope. Consults configuration for the metadata bindings to feed elements. Uses ROME's output
 * drivers to return any of the implemented formats, e.g. RSS 1.0, RSS 2.0, ATOM 1.0.
 *
 * <p>The feed generator and OpenSearch call on this class so feed contents are uniform for both.
 *
 * @author Larry Stone
 */
public class SyndicationFeed {
  protected final Logger log = Logger.getLogger(SyndicationFeed.class);

  /** i18n key values */
  public static final String MSG_UNTITLED = "notitle";

  public static final String MSG_LOGO_TITLE = "logo.title";
  public static final String MSG_FEED_TITLE = "feed.title";
  public static final String MSG_FEED_DESCRIPTION = "general-feed.description";
  public static final String MSG_METADATA = "metadata.";
  public static final String MSG_UITYPE = "ui.type";

  // UI keywords
  public static final String UITYPE_XMLUI = "xmlui";
  public static final String UITYPE_JSPUI = "jspui";

  // default DC fields for entry
  protected String defaultTitleField = "dc.title";
  protected String defaultAuthorField = "dc.contributor.author";
  protected String defaultDateField = "dc.date.issued";
  private static final String[] defaultDescriptionFields =
      new String[] {
        "dc.description.abstract", "dc.description", "dc.title.alternative", "dc.title"
      };
  protected String defaultExternalMedia = "dc.source.uri";

  private final ConfigurationService configurationService =
      DSpaceServicesFactory.getInstance().getConfigurationService();

  // metadata field for Item title in entry:
  protected String titleField =
      configurationService.getProperty("webui.feed.item.title", defaultTitleField);

  // metadata field for Item publication date in entry:
  protected String dateField =
      configurationService.getProperty("webui.feed.item.date", defaultDateField);

  // metadata field for Item description in entry:
  private static final String descriptionFields[] =
      DSpaceServicesFactory.getInstance()
          .getConfigurationService()
          .getArrayProperty("webui.feed.item.description", defaultDescriptionFields);

  protected String authorField =
      configurationService.getProperty("webui.feed.item.author", defaultAuthorField);

  // metadata field for Podcast external media source url
  protected String externalSourceField =
      configurationService.getProperty("webui.feed.podcast.sourceuri", defaultExternalMedia);

  // metadata field for Item dc:creator field in entry's DCModule (no default)
  protected String dcCreatorField = configurationService.getProperty("webui.feed.item.dc.creator");

  // metadata field for Item dc:date field in entry's DCModule (no default)
  protected String dcDateField = configurationService.getProperty("webui.feed.item.dc.date");

  // metadata field for Item dc:author field in entry's DCModule (no default)
  protected String dcDescriptionField =
      configurationService.getProperty("webui.feed.item.dc.description");

  // List of available mimetypes that we'll add to podcast feed. Multiple values separated by commas
  protected String[] podcastableMIMETypes =
      configurationService.getArrayProperty(
          "webui.feed.podcast.mimetypes", new String[] {"audio/x-mpeg"});

  // -------- Instance variables:

  // the feed object we are building
  protected SyndFeed feed = null;

  // memory of UI that called us, "xmlui" or "jspui"
  // affects Bitstream retrieval URL and I18N keys
  protected String uiType = null;

  protected HttpServletRequest request = null;

  protected CollectionService collectionService;
  protected CommunityService communityService;
  protected ItemService itemService;

  /**
   * Constructor.
   *
   * @param ui either "xmlui" or "jspui"
   */
  public SyndicationFeed(String ui) {
    feed = new SyndFeedImpl();
    uiType = ui;
    ContentServiceFactory contentServiceFactory = ContentServiceFactory.getInstance();
    itemService = contentServiceFactory.getItemService();
    collectionService = contentServiceFactory.getCollectionService();
    communityService = contentServiceFactory.getCommunityService();
  }

  /**
   * Returns list of metadata selectors used to compose the description element
   *
   * @return selector list - format 'schema.element[.qualifier]'
   */
  public static String[] getDescriptionSelectors() {
    return (String[]) ArrayUtils.clone(descriptionFields);
  }

  /**
   * Fills in the feed and entry-level metadata from DSpace objects.
   *
   * @param request request
   * @param context context
   * @param dso DSpaceObject
   * @param items array of objects
   * @param labels label map
   */
  public void populate(
      HttpServletRequest request,
      Context context,
      DSpaceObject dso,
      List<? extends DSpaceObject> items,
      Map<String, String> labels) {
    String logoURL = null;
    String objectURL = null;
    String defaultTitle = null;
    boolean podcastFeed = false;
    this.request = request;

    // dso is null for the whole site, or a search without scope
    if (dso == null) {
      defaultTitle = ConfigurationManager.getProperty("dspace.name");
      feed.setDescription(localize(labels, MSG_FEED_DESCRIPTION));
      objectURL = resolveURL(request, null);
      logoURL = ConfigurationManager.getProperty("webui.feed.logo.url");
    } else {
      Bitstream logo = null;
      if (dso.getType() == Constants.COLLECTION) {
        Collection col = (Collection) dso;
        defaultTitle = col.getName();
        feed.setDescription(collectionService.getMetadata(col, "short_description"));
        logo = col.getLogo();
        String cols = ConfigurationManager.getProperty("webui.feed.podcast.collections");
        if (cols != null && cols.length() > 1 && cols.contains(col.getHandle())) {
          podcastFeed = true;
        }
      } else if (dso.getType() == Constants.COMMUNITY) {
        Community comm = (Community) dso;
        defaultTitle = comm.getName();
        feed.setDescription(communityService.getMetadata(comm, "short_description"));
        logo = comm.getLogo();
        String comms = ConfigurationManager.getProperty("webui.feed.podcast.communities");
        if (comms != null && comms.length() > 1 && comms.contains(comm.getHandle())) {
          podcastFeed = true;
        }
      }
      objectURL = resolveURL(request, dso);
      if (logo != null) {
        logoURL = urlOfBitstream(request, logo);
      }
    }
    feed.setTitle(
        labels.containsKey(MSG_FEED_TITLE) ? localize(labels, MSG_FEED_TITLE) : defaultTitle);
    feed.setLink(objectURL);
    feed.setPublishedDate(new Date());
    feed.setUri(objectURL);

    // add logo if we found one:
    if (logoURL != null) {
      // we use the path to the logo for this, the logo itself cannot
      // be contained in the rdf. Not all RSS-viewers show this logo.
      SyndImage image = new SyndImageImpl();
      image.setLink(objectURL);
      if (StringUtils.isNotBlank(feed.getTitle())) {
        image.setTitle(feed.getTitle());
      } else {
        image.setTitle(localize(labels, MSG_LOGO_TITLE));
      }
      image.setUrl(logoURL);
      feed.setImage(image);
    }

    // add entries for items
    if (items != null) {
      List<SyndEntry> entries = new ArrayList<SyndEntry>();
      for (DSpaceObject itemDSO : items) {
        if (itemDSO.getType() != Constants.ITEM) {
          continue;
        }
        Item item = (Item) itemDSO;
        boolean hasDate = false;
        SyndEntry entry = new SyndEntryImpl();
        entries.add(entry);

        String entryURL = resolveURL(request, item);
        entry.setLink(entryURL);
        entry.setUri(entryURL);

        String title = getOneDC(item, titleField);
        entry.setTitle(title == null ? localize(labels, MSG_UNTITLED) : title);

        // "published" date -- should be dc.date.issued
        String pubDate = getOneDC(item, dateField);
        if (pubDate != null) {
          entry.setPublishedDate((new DCDate(pubDate)).toDate());
          hasDate = true;
        }
        // date of last change to Item
        entry.setUpdatedDate(item.getLastModified());

        StringBuffer db = new StringBuffer();
        for (String df : descriptionFields) {
          // Special Case: "(date)" in field name means render as date
          boolean isDate = df.indexOf("(date)") > 0;
          if (isDate) {
            df = df.replaceAll("\\(date\\)", "");
          }

          List<MetadataValue> dcv = itemService.getMetadataByMetadataString(item, df);
          if (dcv.size() > 0) {
            String fieldLabel = labels.get(MSG_METADATA + df);
            if (fieldLabel != null && fieldLabel.length() > 0) {
              db.append(fieldLabel).append(": ");
            }
            boolean first = true;
            for (MetadataValue v : dcv) {
              if (first) {
                first = false;
              } else {
                db.append("; ");
              }
              db.append(isDate ? new DCDate(v.getValue()).toString() : v.getValue());
            }
            db.append("\n");
          }
        }
        if (db.length() > 0) {
          SyndContent desc = new SyndContentImpl();
          desc.setType("text/plain");
          desc.setValue(db.toString());
          entry.setDescription(desc);
        }

        // This gets the authors into an ATOM feed
        List<MetadataValue> authors = itemService.getMetadataByMetadataString(item, authorField);
        if (authors.size() > 0) {
          List<SyndPerson> creators = new ArrayList<SyndPerson>();
          for (MetadataValue author : authors) {
            SyndPerson sp = new SyndPersonImpl();
            sp.setName(author.getValue());
            creators.add(sp);
          }
          entry.setAuthors(creators);
        }

        // only add DC module if any DC fields are configured
        if (dcCreatorField != null || dcDateField != null || dcDescriptionField != null) {
          DCModule dc = new DCModuleImpl();
          if (dcCreatorField != null) {
            List<MetadataValue> dcAuthors =
                itemService.getMetadataByMetadataString(item, dcCreatorField);
            if (dcAuthors.size() > 0) {
              List<String> creators = new ArrayList<String>();
              for (MetadataValue author : dcAuthors) {
                creators.add(author.getValue());
              }
              dc.setCreators(creators);
            }
          }
          if (dcDateField != null && !hasDate) {
            List<MetadataValue> v = itemService.getMetadataByMetadataString(item, dcDateField);
            if (v.size() > 0) {
              dc.setDate((new DCDate(v.get(0).getValue())).toDate());
            }
          }
          if (dcDescriptionField != null) {
            List<MetadataValue> v =
                itemService.getMetadataByMetadataString(item, dcDescriptionField);
            if (v.size() > 0) {
              StringBuffer descs = new StringBuffer();
              for (MetadataValue d : v) {
                if (descs.length() > 0) {
                  descs.append("\n\n");
                }
                descs.append(d.getValue());
              }
              dc.setDescription(descs.toString());
            }
          }
          entry.getModules().add(dc);
        }

        // iTunes Podcast Support - START
        if (podcastFeed) {
          // Add enclosure(s)
          List<SyndEnclosure> enclosures = new ArrayList();
          try {
            List<Bundle> bunds = itemService.getBundles(item, "ORIGINAL");
            if (bunds.get(0) != null) {
              List<Bitstream> bits = bunds.get(0).getBitstreams();
              for (Bitstream bit : bits) {
                String mime = bit.getFormat(context).getMIMEType();
                if (ArrayUtils.contains(podcastableMIMETypes, mime)) {
                  SyndEnclosure enc = new SyndEnclosureImpl();
                  enc.setType(bit.getFormat(context).getMIMEType());
                  enc.setLength(bit.getSize());
                  enc.setUrl(urlOfBitstream(request, bit));
                  enclosures.add(enc);
                } else {
                  continue;
                }
              }
            }
            // Also try to add an external value from dc.identifier.other
            // We are assuming that if this is set, then it is a media file
            List<MetadataValue> externalMedia =
                itemService.getMetadataByMetadataString(item, externalSourceField);
            if (externalMedia.size() > 0) {
              for (MetadataValue anExternalMedia : externalMedia) {
                SyndEnclosure enc = new SyndEnclosureImpl();
                enc.setType(
                    "audio/x-mpeg"); // We can't determine MIME of external file, so just picking
                                     // one.
                enc.setLength(1);
                enc.setUrl(anExternalMedia.getValue());
                enclosures.add(enc);
              }
            }

          } catch (Exception e) {
            System.out.println(e.getMessage());
          }
          entry.setEnclosures(enclosures);

          // Get iTunes specific fields: author, subtitle, summary, duration, keywords
          EntryInformation itunes = new EntryInformationImpl();

          String author = getOneDC(item, authorField);
          if (author != null && author.length() > 0) {
            itunes.setAuthor(author); // <itunes:author>
          }

          itunes.setSubtitle(
              title == null ? localize(labels, MSG_UNTITLED) : title); // <itunes:subtitle>

          if (db.length() > 0) {
            itunes.setSummary(db.toString()); // <itunes:summary>
          }

          String extent =
              getOneDC(
                  item,
                  "dc.format.extent"); // assumed that user will enter this field with length of
                                       // song in seconds
          if (extent != null && extent.length() > 0) {
            extent = extent.split(" ")[0];
            Integer duration = Integer.parseInt(extent);
            itunes.setDuration(new Duration(duration)); // <itunes:duration>
          }

          String subject = getOneDC(item, "dc.subject");
          if (subject != null && subject.length() > 0) {
            String[] subjects = new String[1];
            subjects[0] = subject;
            itunes.setKeywords(subjects); // <itunes:keywords>
          }

          entry.getModules().add(itunes);
        }
      }
      feed.setEntries(entries);
    }
  }

  /**
   * Sets the feed type for XML delivery, e.g. "rss_1.0", "atom_1.0" Must match one of ROME's
   * configured generators, see rome.properties (currently rss_1.0, rss_2.0, atom_1.0, atom_0.3)
   *
   * @param feedType feed type
   */
  public void setType(String feedType) {
    feed.setFeedType(feedType);
    // XXX FIXME: workaround ROME 1.0 bug, it puts invalid image element in rss1.0
    if ("rss_1.0".equals(feedType)) {
      feed.setImage(null);
    }
  }

  /**
   * @return the feed we built as DOM Document
   * @throws FeedException if feed error
   */
  public Document outputW3CDom() throws FeedException {
    try {
      SyndFeedOutput feedWriter = new SyndFeedOutput();
      return feedWriter.outputW3CDom(feed);
    } catch (FeedException e) {
      log.error(e);
      throw e;
    }
  }

  /**
   * @return the feed we built as serialized XML string
   * @throws FeedException if feed error
   */
  public String outputString() throws FeedException {
    SyndFeedOutput feedWriter = new SyndFeedOutput();
    return feedWriter.outputString(feed);
  }

  /**
   * send the output to designated Writer
   *
   * @param writer Writer
   * @throws FeedException if feed error
   * @throws IOException if IO error
   */
  public void output(java.io.Writer writer) throws FeedException, IOException {
    SyndFeedOutput feedWriter = new SyndFeedOutput();
    feedWriter.output(feed, writer);
  }

  /**
   * Add a ROME plugin module (e.g. for OpenSearch) at the feed level.
   *
   * @param m module
   */
  public void addModule(Module m) {
    feed.getModules().add(m);
  }

  // utility to get config property with default value when not set.
  protected static String getDefaultedConfiguration(String key, String dfl) {
    String result = ConfigurationManager.getProperty(key);
    return (result == null) ? dfl : result;
  }

  // returns absolute URL to download content of bitstream (which might not belong to any Item)
  protected String urlOfBitstream(HttpServletRequest request, Bitstream logo) {
    String name = logo.getName();
    return resolveURL(request, null)
        + (uiType.equalsIgnoreCase(UITYPE_XMLUI) ? "/bitstream/id/" : "/retrieve/")
        + logo.getID()
        + "/"
        + (name == null ? "" : name);
  }

  protected String baseURL = null; // cache the result for null
  /**
   * Return a url to the DSpace object, either use the official handle for the item or build a url
   * based upon the current server.
   *
   * <p>If the dspaceobject is null then a local url to the repository is generated.
   *
   * @param request current servlet request
   * @param dso The object to reference, null if to the repository.
   * @return URL
   */
  protected String resolveURL(HttpServletRequest request, DSpaceObject dso) {
    // If no object given then just link to the whole repository,
    // since no offical handle exists so we have to use local resolution.
    if (dso == null) {
      if (baseURL == null) {
        if (request == null) {
          baseURL = ConfigurationManager.getProperty("dspace.url");
        } else {
          baseURL = (request.isSecure()) ? "https://" : "http://";
          baseURL += ConfigurationManager.getProperty("dspace.hostname");
          baseURL += ":" + request.getServerPort();
          baseURL += request.getContextPath();
        }
      }
      return baseURL;
    }

    // return a link to handle in repository
    else if (ConfigurationManager.getBooleanProperty("webui.feed.localresolve")) {
      return resolveURL(request, null) + "/handle/" + dso.getHandle();
    }

    // link to the Handle server or other persistent URL source
    else {
      return HandleServiceFactory.getInstance()
          .getHandleService()
          .getCanonicalForm(dso.getHandle());
    }
  }

  // retrieve text for localization key, or mark untranslated
  protected String localize(Map<String, String> labels, String s) {
    return labels.containsKey(s) ? labels.get(s) : ("Untranslated:" + s);
  }

  // spoonful of syntactic sugar when we only need first value
  protected String getOneDC(Item item, String field) {
    List<MetadataValue> dcv = itemService.getMetadataByMetadataString(item, field);
    return (dcv.size() > 0) ? dcv.get(0).getValue() : null;
  }
}