public virtual AtomEntry CreateMedia(Id collectionId, Stream stream, string slug, string contentType)
    {
      LogService.Info("AtomPubService.CreateMedia collectionId={0} slug={1} contentType={2}", collectionId, slug, contentType);
      Auth(collectionId, AuthAction.CreateEntryOrMedia);
      AppCollection coll = AppServiceRepository.GetService().GetCollection(collectionId);
      if (!coll.CanAccept(contentType)) throw new InvalidContentTypeException(contentType);
      AtomEntry entry = new AtomEntry();
      entry.Media = true;
      entry.Title = new AtomText(Atom.AtomNs + "title") { Text = slug != null ? slug : collectionId.Collection };
      entry.Updated = DateTimeOffset.UtcNow;
      entry.Edited = DateTimeOffset.UtcNow;
      if (!entry.Published.HasValue) entry.Published = DateTimeOffset.UtcNow;
      if (coll.Dated)
        entry.Id = new Id(collectionId.Owner, entry.Date.UtcDateTime, collectionId.Collection, entry.BuildPath(null, slug));
      else
        entry.Id = new Id(collectionId.Owner, collectionId.Date, collectionId.Collection, entry.BuildPath(null, slug));

      entry.Content = new AtomContent { Type = contentType };//, Src = UrlHelper.RouteIdUri("AtomPubMedia", entry.Id, AbsoluteMode.Force) };
      SetPerson(entry);
      SetLinks(entry);
      entry.Summary = new AtomText(Atom.AtomNs + "summary") { Text = "" };
      entry.IdChanged += (e) => SetLinks(e);// e.UpdateLinks(RouteFunc);
      
      if (CreatingEntry != null) CreatingEntry(collectionId, entry, slug);
      //OnCreateMedia(entry, collectionId, stream, slug, contentType);
      entry = MediaRepository.CreateMedia(entry, stream);
      if (EntryCreated != null) EntryCreated(entry);
      return entry;
    }
    public virtual AtomEntry Annotate(Id entryId, AtomEntry entry, string slug)
    {
      LogService.Info("AnnotateService.Annotate entryId={0} slug={1}", entryId, slug);

      //authorization
      if (!AuthorizeService.IsAuthorized(GetUser(), entryId.ToScope(), AuthAction.Annotate))
        throw new UserNotAuthorizedException(GetUser().Name, AuthAction.Annotate.ToString());

      AppCollection coll = AppService.GetCollection(entryId);

      //make sure type is accepted
      if (!coll.CanAccept(Atom.ContentTypeEntry))
        throw new InvalidContentTypeException(Atom.ContentTypeEntry);

      entry.SetNamespaces(); //TODO: is there a better place for this?

      //build id onto parent's id
      AtomEntry parent = AtomPubService.GetEntry(entryId);
      entry.Id = new Id(parent.Id.Owner, parent.Id.Date, parent.Id.Collection, entry.BuildPath(parent.Id.EntryPath, slug));
      
      var url = new UrlHelper(Container.GetInstance<RequestContext>());
      //this annotation is a reply to the parent entry, TODO: leave off href for later calc based on id?
      entry.InReplyTo = new ThreadInReplyTo()
      {
        Ref = parent.Id,
        Href = parent.IsExternal ? parent.Content.Src : url.RouteIdUri("AtomPubEntry", entry.Id, AbsoluteMode.Force),
        Type = parent.IsExternal ? parent.Content.Type : Atom.ContentTypeEntry
      };

      if (!entry.Published.HasValue) entry.Published = DateTimeOffset.UtcNow;
      entry.Updated = DateTimeOffset.UtcNow;
      entry.Edited = DateTimeOffset.UtcNow;

      if (entry.Authors.Count() == 0) entry.SetPerson(AuthorizeService, true);

      //entry.IdChanged += (e) => e.UpdateLinks(UrlHelper.RouteIdUri);

      //OnAnnotate(parent, entryId, entry, slug);
      if (AnnotatingEntry != null) AnnotatingEntry(entryId, entry, slug);

      if (entry.Authors.Count() == 0 || entry.Authors.First().Name == null)
        throw new AnnotationNotAllowedException(entry.Id, entry.AnnotationType, "the author cannot be determined");

      entry = AtomEntryRepository.CreateEntry(entry);
      if (EntryAnnotated != null) EntryAnnotated(entry);
      return entry;
    }
    public virtual AtomEntry CreateEntry(Id collectionId, AtomEntry entry, string slug)
    {
      LogService.Info("AtomPubService.CreateEntry collectionId={0} slug={1}", collectionId, slug);
      Auth(collectionId, AuthAction.CreateEntryOrMedia);
      AppCollection coll = AppServiceRepository.GetService().GetCollection(collectionId);
      if (entry.Control == null) entry.Control = new AppControl();

      //is acceptable type?
      if (!coll.CanAccept(Atom.ContentTypeEntry)) throw new InvalidContentTypeException(Atom.ContentTypeEntry);

      //approval based on role action matrix
      if (!AuthorizeService.IsAuthorized(GetUser(), collectionId.ToScope(), AuthAction.ApproveEntryOrMedia))
        entry.Control.Approved = false;

      if (!entry.Draft && !entry.Published.HasValue) entry.Published = DateTimeOffset.UtcNow;
      if (entry.Updated == default(DateTimeOffset)) entry.Updated = DateTimeOffset.UtcNow;
      entry.Edited = DateTimeOffset.UtcNow;
      if (coll.Dated)
        entry.Id = new Id(collectionId.Owner, entry.Date.UtcDateTime, collectionId.Collection, entry.BuildPath(null, slug));
      else
        entry.Id = new Id(collectionId.Owner, collectionId.Date, collectionId.Collection, entry.BuildPath(null, slug));

      SetPerson(entry);
      SetCategories(entry); 
      SetLinks(entry);
      entry.IdChanged += (e) => SetLinks(e);

      if (CreatingEntry != null) CreatingEntry(collectionId, entry, slug);
      entry = AtomEntryRepository.CreateEntry(entry);
      if (EntryCreated != null) EntryCreated(entry);

      return entry;
    }