Changeset 7613

Show
Ignore:
Timestamp:
04/17/09 16:42:43 (16 months ago)
Author:
dragisak
Message:

Redesign annotation feeds to include replies.

  • Cache annotation feeds in feedCache
  • Invalidate results of reply query separately
  • Add anchors to annotation display and link to them from reply feed
  • Rename Blob and UnmanagedBlob? classes
  • Move feed action and service classes into their own package

Addresses #814

Location:
head/ambra
Files:
4 added
15 modified
5 moved

Legend:

Unmodified
Added
Removed
  • head/ambra/libs/otm-models/src/main/java/org/topazproject/ambra/models/AnnotationBlob.java

    r7611 r7613  
    3030 */ 
    3131@Entity() 
    32 public class AnnotationBlob extends UnmanagedBlob implements CompetingInterest { 
     32public class AnnotationBlob extends ByteArrayBlob implements CompetingInterest { 
    3333  private static final long serialVersionUID = 6834878451274759551L; 
    3434  private String id; 
  • head/ambra/libs/otm-models/src/main/java/org/topazproject/ambra/models/ByteArrayBlob.java

    r7611 r7613  
    3232 */ 
    3333@Entity() 
    34 public abstract class UnmanagedBlob implements Serializable { 
     34public abstract class ByteArrayBlob implements Serializable { 
    3535  private static final long serialVersionUID = -783693796375733488L; 
    3636 
     
    4040   * Creates a new Blob object. 
    4141   */ 
    42   public UnmanagedBlob() { 
     42  public ByteArrayBlob() { 
    4343  } 
    4444 
     
    4848   * @param contentType the content type 
    4949   */ 
    50   public UnmanagedBlob(String contentType) { 
     50  public ByteArrayBlob(String contentType) { 
    5151    // FIXME: contentType 
    5252  } 
  • head/ambra/libs/otm-models/src/main/java/org/topazproject/ambra/models/ReplyBlob.java

    r7611 r7613  
    3030 */ 
    3131@Entity() 
    32 public class ReplyBlob extends UnmanagedBlob implements CompetingInterest { 
     32public class ReplyBlob extends ByteArrayBlob implements CompetingInterest { 
    3333  private static final long serialVersionUID = -1561876836328885263L; 
    3434  private String id; 
  • head/ambra/libs/otm-models/src/main/java/org/topazproject/ambra/models/Representation.java

    r7193 r7613  
    22 * $Id$ 
    33 * 
    4  * Copyright (c) 2006-2008 by Topaz, Inc. 
     4 * Copyright (c) 2006-2009 by Topaz, Inc. 
    55 * http://topazproject.org 
    66 * 
     
    3737 */ 
    3838@Entity(graph = "ri", types = {"topaz:Representation"}) 
    39 public class Representation extends Blob implements PostLoadEventListener, PreInsertEventListener { 
     39public class Representation extends StreamedBlob 
     40    implements PostLoadEventListener, PreInsertEventListener { 
    4041  private static final long serialVersionUID = 8927830952382002736L; 
    4142 
  • head/ambra/libs/otm-models/src/main/java/org/topazproject/ambra/models/StreamedBlob.java

    r7193 r7613  
    22 * $Id$ 
    33 * 
    4  * Copyright (c) 2007-2008 by Topaz, Inc. 
     4 * Copyright (c) 2007-2009 by Topaz, Inc. 
    55 * http://topazproject.org 
    66 * 
     
    2424 
    2525/** 
    26  * Base class for blobs loaded from the BlobStore. 
     26 * Base class for blobs loaded from the BlobStore. Blob content is not loaded but accessed through 
     27 * input and output streams. 
    2728 * 
    2829 * @author Pradeep Krishnan 
    2930 */ 
    3031@Entity() 
    31 public abstract class Blob implements Serializable { 
     32public abstract class StreamedBlob implements Serializable { 
    3233  private static final long serialVersionUID = -783693796375733488L; 
    3334 
     
    3738   * Creates a new Blob object. 
    3839   */ 
    39   public Blob() { 
     40  public StreamedBlob() { 
    4041  } 
    4142 
     
    4546   * @param contentType the content type 
    4647   */ 
    47   public Blob(String contentType) { 
     48  public StreamedBlob(String contentType) { 
    4849    // FIXME: contentType 
    4950  } 
  • head/ambra/libs/otm-models/src/test/java/org/topazproject/ambra/models/ModelsTest.java

    r7298 r7613  
    5656                  UserPreferences.class, UserProfile.class, UserRole.class, Journal.class, Issue.class, 
    5757                  Aggregation.class, EditorialBoard.class, Correction.class, FormalCorrection.class, 
    58                   Retraction.class, Blob.class, ReplyBlob.class, AnnotationBlob.class 
     58                  Retraction.class, StreamedBlob.class, ReplyBlob.class, AnnotationBlob.class 
    5959    }; 
    6060 
  • head/ambra/webapp/src/main/java/org/topazproject/ambra/annotation/service/AnnotationConverter.java

    r7611 r7613  
    2929 
    3030import org.springframework.transaction.annotation.Transactional; 
     31import org.springframework.beans.factory.annotation.Required; 
    3132 
    3233import org.topazproject.ambra.annotation.Commentary; 
    3334import org.topazproject.ambra.models.Annotea; 
    3435import org.topazproject.ambra.models.ArticleAnnotation; 
    35 import org.topazproject.ambra.models.UnmanagedBlob; 
     36import org.topazproject.ambra.models.ByteArrayBlob; 
    3637import org.topazproject.ambra.models.Reply; 
    3738import org.topazproject.ambra.user.service.UserService; 
     
    4041 
    4142/** 
    42  * A kind of utility class to convert types between topaz and ambra types for 
     43 * A utility class to convert types between topaz and ambra types for 
    4344 * Annotations and Replies 
    4445 */ 
     
    6667 
    6768  /** 
    68    * Converts and <code>ArticleAnnotation</code> to <code>List&lt;Webannotation&gt;</code> 
     69   * Converts and <code>ArticleAnnotation</code> to <code>List&lt;WebAnnotation&gt;</code> 
    6970   * 
    7071   * @param annotations an list of annotations 
     
    8485 
    8586    return wa; 
     87  } 
     88 
     89  /** 
     90   * Converts and <code>Reply</code> to <code>List&lt;WebReply&gt;</code> 
     91   * 
     92   * @param replies an list of replies 
     93   * @param needCreatorName indicates if a display-name of the creator needs to be fetched 
     94   * @param needBody indicates if the annotation body is required 
     95   * @return an array of Annotation objects as required by the web layer 
     96   */ 
     97  @Transactional(readOnly = true) 
     98  public List<WebReply> convertReplies(final List<Reply> replies, 
     99                                boolean needCreatorName, 
     100                                boolean needBody) { 
     101    final List<WebReply> wr  = new ArrayList<WebReply>(); 
     102 
     103    for (Reply reply : replies) { 
     104      if (reply != null) 
     105        wr.add(convert(reply, needCreatorName, needBody)); 
     106    } 
     107 
     108    return wr; 
    86109  } 
    87110 
     
    117140    return new WebAnnotation(annotation, creator, body); 
    118141  } 
     142 
    119143  /** 
    120144   * Creates a hierarchical array of replies based on the flat array passed in. 
     
    208232 
    209233  /** 
    210    * @return Returns the userService. 
    211    */ 
    212   protected UserService getUserService() { 
    213     return userService; 
    214   } 
    215  
    216   /** 
    217234   * @param userService The userService to set. 
    218235   */ 
     236  @Required 
    219237  public void setUserService(UserService userService) { 
    220238    this.userService = userService; 
    221239  } 
    222240 
    223   private String loadBody(final Annotea<? extends UnmanagedBlob> annotea) 
     241  private String loadBody(final Annotea<? extends ByteArrayBlob> annotea) 
    224242      throws OtmException, Error { 
    225243 
    226     UnmanagedBlob blob = annotea.getBody(); 
     244    ByteArrayBlob blob = annotea.getBody(); 
    227245    try { 
    228246      return  (blob == null || blob.getBody() == null) ? "" : new String(blob.getBody(), "UTF-8"); 
  • head/ambra/webapp/src/main/java/org/topazproject/ambra/annotation/service/AnnotationService.java

    r7611 r7613  
    140140    AnnotationBlob blob = new AnnotationBlob(contentType); 
    141141    blob.setCIStatement(ciStatement); 
     142    blob.setBody(body.getBytes(getEncodingCharset())); 
    142143 
    143144    final ArticleAnnotation annotation = annotationClass.newInstance(); 
     
    153154    String newId = session.saveOrUpdate(annotation); 
    154155    // now that the blob is created by OTM, write to it 
    155     blob.setBody(body.getBytes(getEncodingCharset())); 
    156156 
    157157    if (log.isDebugEnabled()) 
     
    316316 
    317317  /** 
    318    * Get all annotations satisfying the criteria. 
    319    * 
    320    * @param articleId  limits the list to annotations from a particular article. 
    321    * @param startDate  is the date to start searching from. If null, start from begining of time. 
    322    *                   Can be iso8601 formatted or string representation of Date object. 
    323    * @param endDate    is the date to search until. If null, search until present date 
    324    * @param mediator   the mediator of the annotation. 
    325    * @param annotType  a filter list of rdf types for the annotations. 
    326    * @param states     array of states to filter on 
    327    * @param ascending  controls the sort order (by date). 
    328    * @param maxResults the maximum number of results to return, or 0 for no limit 
    329    * 
    330    * @return the (possibly empty) list of articles. 
    331    * 
    332    * @throws ParseException if any of the dates or query could not be parsed 
    333    * @throws URISyntaxException if an element of annotType cannot be parsed as a URI 
    334    */ 
    335   @Transactional(readOnly = true) 
    336   public List<ArticleAnnotation> getAnnotations(String articleId, Date startDate, Date endDate, 
    337                                                 String mediator, List<String> annotType, 
    338                                                 int[] states, boolean ascending, int maxResults) 
    339   throws ParseException, URISyntaxException { 
    340     List<String> annotationIds = getAnnotationIds(articleId, startDate, endDate, mediator, 
    341                                                   annotType, states, ascending, maxResults); 
    342  
    343     return getAnnotations(annotationIds); 
    344   } 
    345  
    346   /** 
    347318   * Get a list of all annotation Ids satifying the given criteria. All caching is done 
    348319   * at the object level by the session. 
    349320   * 
    350    * @param targetId  limits annotations to a particlualr target id. 
    351321   * @param startDate  search for annotation after start date. 
    352322   * @param endDate    is the date to search until. If null, search until present date 
    353    * @param mediator   annotation mediator 
    354    * @param annotType  a filter list of rdf types for the annotations. 
    355    * @param states     the list of annotation states to search for (all states if null or empty) 
    356    * @param ascending  controls the sort order (by date). 
     323   * @param annotTypes List of annotation types 
    357324   * @param maxResults the maximum number of results to return, or 0 for no limit 
    358325   * 
     
    363330   */ 
    364331  @Transactional(readOnly = true) 
    365   public List<String> getAnnotationIds(String targetId, Date startDate, Date endDate, 
    366                                        String mediator, List<String> annotType, int[] states, 
    367                                        boolean ascending, int maxResults) 
     332  public List<String> getAnnotationIds(Date startDate, Date endDate, Set<String> annotTypes, 
     333                                       int maxResults) 
    368334  throws ParseException, URISyntaxException { 
     335 
     336    // create the query, applying parameters 
     337    Query annotationQuery = getAnnotationQuery(startDate, endDate, annotTypes, maxResults); 
     338 
     339    Results annotationResults = annotationQuery.execute(); 
     340    List<String> res = new ArrayList<String>(); 
     341 
     342    while (annotationResults.next()) { 
     343      URI id = annotationResults.getURI(0); 
     344      try { 
     345        // apply access-controls 
     346        pep.checkAccess(AnnotationsPEP.LIST_ANNOTATIONS, id); 
     347        res.add(id.toString()); 
     348      } catch (SecurityException se) { 
     349        if (log.isDebugEnabled()) 
     350          log.debug("Filtering URI " + id + " from Annotation list due to PEP SecurityException", se); 
     351      } 
     352    } 
     353 
     354    return res; 
     355  } 
     356 
     357 
     358  /** 
     359   * Get a list of all reply Ids satifying the given criteria. All caching is done 
     360   * at the object level by the session. 
     361   * 
     362   * @param startDate  search for replies after start date. 
     363   * @param endDate    is the date to search until. If null, search until present date 
     364   * @param annotTypes List of annotation types 
     365   * @param maxResults the maximum number of results to return, or 0 for no limit 
     366   * 
     367   * @return the (possibly empty) list of article ids. 
     368   * 
     369   * @throws ParseException   if any of the dates or query could not be parsed 
     370   * @throws URISyntaxException  if an element of annotType cannot be parsed as a URI 
     371   */ 
     372  @Transactional(readOnly = true) 
     373  public List<String> getReplyIds(Date startDate, Date endDate, Set<String> annotTypes, int maxResults) 
     374  throws ParseException, URISyntaxException { 
     375 
     376    // create the query, applying parameters 
     377    Query query = getReplyQuery(startDate, endDate, annotTypes, maxResults); 
     378 
     379    Results results = query.execute(); 
     380    List<String> res = new ArrayList<String>(); 
     381 
     382    while (results.next()) { 
     383      URI id = results.getURI(0); 
     384      URI annotationId = results.getURI(2); 
     385      try { 
     386        // apply access-controls 
     387        pep.checkAccess(AnnotationsPEP.LIST_ANNOTATIONS, annotationId); 
     388        res.add(id.toString()); 
     389      } catch (SecurityException se) { 
     390        if (log.isDebugEnabled()) 
     391          log.debug("Filtering reply " + id + " from Annotation list due to PEP SecurityException", se); 
     392      } 
     393    } 
     394 
     395    return res; 
     396  } 
     397 
     398  private Query getReplyQuery(Date startDate, Date endDate, Set<String> annotTypes, 
     399                              int maxResults) 
     400      throws URISyntaxException { 
     401 
     402    String queryString = createReplyQueryString(startDate, endDate, annotTypes, maxResults); 
     403    Query query = session.createQuery(queryString); 
     404 
     405    bindCommonParams(startDate, endDate, annotTypes, query); 
     406    return query; 
     407  } 
     408 
     409  private Query getAnnotationQuery(Date startDate, Date endDate, Set<String> annotTypes, 
     410                                   int maxResults) 
     411      throws URISyntaxException { 
     412    String queryString = createAnnotationQueryString(startDate, endDate, annotTypes, maxResults); 
     413    Query query = session.createQuery(queryString); 
     414 
     415    bindCommonParams(startDate, endDate, annotTypes, query); 
     416    return query; 
     417  } 
     418 
     419  private void bindCommonParams(Date startDate, Date endDate, Set<String> annotTypes, Query query) 
     420      throws URISyntaxException { 
     421 
     422    if (startDate != null) 
     423      query.setParameter("sd",  startDate); 
     424    if (endDate != null) 
     425      query.setParameter("ed",  endDate); 
     426 
     427    if (annotTypes != null) { 
     428      int i = 0; 
     429      for (String type : annotTypes) { 
     430        query.setUri("type" + i++, URI.create(type)); 
     431      } 
     432    } 
     433  } 
     434 
     435  private String createAnnotationQueryString(Date startDate, Date endDate, Set<String> annotTypes, 
     436                                             int maxResults) { 
     437 
    369438    StringBuilder qry = new StringBuilder(); 
    370439 
    371     if (targetId != null) { 
    372       qry.append("select a.id id, cr from Annotation a "); 
    373       qry.append("where cr := a.created and "); 
    374       qry.append("a.annotates = :targ "); 
    375     } else { 
    376       qry.append("select a.id id, cr from ArticleAnnotation a, Article ar "); 
    377       qry.append("where a.annotates = ar and cr := a.created "); 
    378     } 
    379  
    380     if (mediator != null) 
    381       qry.append("and a.mediator = :med "); 
    382  
     440    qry.append("select a.id id, cr from ArticleAnnotation a, Article ar "); 
     441    qry.append("where a.annotates = ar and cr := a.created "); 
     442 
     443    appendCommonCriteria(startDate, endDate, annotTypes, maxResults, qry); 
     444 
     445    qry.append(';'); 
     446 
     447    return qry.toString(); 
     448  } 
     449 
     450  // For now only fetches all Replys 
     451  private String createReplyQueryString(Date startDate, Date endDate, Set<String> annotTypes, 
     452                                        int maxResults) { 
     453 
     454    StringBuilder qry = new StringBuilder(); 
     455 
     456    qry.append("select r.id id, cr, a.id annid from Reply r, ArticleAnnotation a, Article ar ") 
     457       .append("where cr := r.created and r.root = a and a.annotates = ar "); 
     458 
     459    appendCommonCriteria(startDate, endDate, annotTypes, maxResults, qry); 
     460 
     461    qry.append(';'); 
     462 
     463    return qry.toString(); 
     464  } 
     465 
     466  private void appendCommonCriteria(Date startDate, Date endDate, Set<String> annotTypes, 
     467                                    int maxResults, StringBuilder qry) { 
    383468    // apply date constraints 
    384469    if (startDate != null) 
     
    387472      qry.append("and le(cr, :ed) "); 
    388473 
    389     // match all states 
    390     if (states != null && states.length > 0) { 
     474 
     475    // match all types 
     476    if (annotTypes != null && annotTypes.size() > 0) { 
    391477      qry.append("and ("); 
    392       for (int idx = 0; idx < states.length; idx++) 
    393         qry.append("art.state = :st").append(idx).append(" or "); 
     478      for (int i = 0; i < annotTypes.size(); i++) 
     479        qry.append("a.<rdf:type> = :type").append(i).append(" or "); 
    394480      qry.setLength(qry.length() - 4); 
    395481      qry.append(") "); 
    396482    } 
    397  
    398     // match all types 
    399     if (annotType != null && annotType.size() > 0) { 
    400       qry.append("and ("); 
    401       for (int idx = 0; idx < annotType.size(); idx++) 
    402         qry.append("a.<rdf:type> = :type").append(idx).append(" or "); 
    403       qry.setLength(qry.length() - 4); 
    404       qry.append(") "); 
    405     } 
    406  
    407483    // add ordering and limit 
    408     qry.append("order by cr ").append(ascending ? "asc" : "desc").append(", id asc"); 
     484    qry.append("order by cr asc, id asc"); 
    409485 
    410486    if (maxResults > 0) 
    411487      qry.append(" limit ").append(maxResults); 
    412  
    413     qry.append(";"); 
    414  
    415     // create the query, applying parameters 
    416     Query q = session.createQuery(qry.toString()); 
    417  
    418     if (targetId != null) 
    419       q.setParameter("targ", targetId); 
    420     if (mediator != null) 
    421       q.setParameter("med", mediator); 
    422     if (startDate != null) 
    423       q.setParameter("sd",  startDate); 
    424     if (endDate != null) 
    425       q.setParameter("ed",  endDate); 
    426     for (int idx = 0; states != null && idx < states.length; idx++) 
    427       q.setParameter("st" + idx, states[idx]); 
    428     for (int idx = 0; annotType != null && idx < annotType.size(); idx++) 
    429       q.setUri("type" + idx, new URI(annotType.get(idx))); 
    430  
    431     Results r = q.execute(); 
    432     // apply access-controls 
    433     List<String> res = new ArrayList<String>(); 
    434     while (r.next()) { 
    435       URI id = r.getURI(0); 
    436       try { 
    437         pep.checkAccess(AnnotationsPEP.LIST_ANNOTATIONS, id); 
    438         res.add(id.toString()); 
    439       } catch (SecurityException se) { 
    440         if (log.isDebugEnabled()) 
    441           log.debug("Filtering URI " + id + " from Article list due to PEP SecurityException", se); 
    442       } 
    443     } 
    444     return res; 
    445488  } 
    446489 
     
    515558    ArticleAnnotation   a = session.get(ArticleAnnotation.class, annotationId); 
    516559    if (a == null) 
    517       throw new IllegalArgumentException("invalid annoation id: " + annotationId); 
     560      throw new IllegalArgumentException("invalid annotation id: " + annotationId); 
    518561 
    519562    return a; 
     
    644687   * @param mimeType mimeType 
    645688   * @param body body 
    646    * @parem ciStatement competing interesting statement 
     689   * @param ciStatement competing interesting statement 
    647690   * @param isPublic isPublic 
    648691   * @param user logged in user 
  • head/ambra/webapp/src/main/java/org/topazproject/ambra/annotation/service/BaseAnnotation.java

    r7611 r7613  
    2222 
    2323import org.topazproject.ambra.models.Annotea; 
    24 import org.topazproject.ambra.models.UnmanagedBlob; 
     24import org.topazproject.ambra.models.ByteArrayBlob; 
    2525import org.topazproject.ambra.util.TextUtils; 
    2626 
     
    3636 * @param <T> the Annotea sub-class being delegated to. 
    3737 */ 
    38 public abstract class BaseAnnotation<T extends Annotea<? extends UnmanagedBlob>> { 
     38public abstract class BaseAnnotation<T extends Annotea<? extends ByteArrayBlob>> { 
    3939  /** An integer constant to indicate a unique value for the  */ 
    4040  private static final int TRUNCATED_COMMENT_LENGTH = 256; 
     
    204204    this.originalBodyContent = originalBodyContent; 
    205205  } 
     206 
     207  public abstract String getCommentTitle(); 
     208 
     209  public abstract String getCIStatement(); 
    206210} 
  • head/ambra/webapp/src/main/java/org/topazproject/ambra/annotation/service/ReplyService.java

    r7611 r7613  
    2020 
    2121import java.net.URI; 
     22import java.net.URISyntaxException; 
    2223import java.util.ArrayList; 
    2324import java.util.Date; 
    2425import java.util.List; 
     26import java.util.Set; 
     27import java.text.ParseException; 
    2528 
    2629import org.apache.commons.logging.Log; 
     
    4952  private static final Log log = LogFactory.getLog(ReplyService.class); 
    5053 
     54  private AnnotationService  annotationService;   // Annotation service Spring injected. 
    5155  private RepliesPEP pep; 
    5256  private String     defaultType; 
     
    6266  public void setRepliesPdp(PDP pdp) { 
    6367    this.pep = new RepliesPEP(pdp); 
     68  } 
     69 
     70  @Required 
     71  public void setAnnotationService(AnnotationService annotationService) { 
     72    this.annotationService = annotationService; 
    6473  } 
    6574 
     
    91100    ReplyBlob blob = new ReplyBlob(contentType); 
    92101    blob.setCIStatement(ciStatement); 
     102    blob.setBody(body.getBytes(getEncodingCharset())); 
    93103 
    94104    final Reply r = new ReplyThread(); 
     
    103113 
    104114    String  newId = session.saveOrUpdate(r); 
    105     blob.setBody(body.getBytes(getEncodingCharset())); 
    106115 
    107116    permissionsService.propagatePermissions(newId, new String[] { blob.getId() }); 
     
    175184      remove(all, t); 
    176185  } 
     186 
     187  /** 
     188   * Get all replies specified by the list of reply ids. 
     189   * 
     190   * @param replyIds a list of annotations Ids to retrieve. 
     191   * 
     192   * @return the (possibly empty) list of replies. 
     193   * 
     194   */ 
     195  @Transactional(readOnly = true) 
     196  public List<Reply> getReplies(List<String> replyIds) { 
     197    List<Reply> replies = new ArrayList<Reply>(); 
     198 
     199    for (String id : replyIds)  { 
     200      try { 
     201        replies.add(getReply(id)); 
     202      } catch (IllegalArgumentException iae) { 
     203        if (log.isDebugEnabled()) 
     204          log.debug("Ignored illegal reply id:" + id); 
     205      } catch (SecurityException se) { 
     206        if (log.isDebugEnabled()) 
     207          log.debug("Filtering URI " + id + " from Reply list due to PEP SecurityException", se); 
     208      } 
     209    } 
     210    return replies; 
     211  } 
     212 
     213  /** 
     214   * Get all replies satisfying the criteria. 
     215   * 
     216   * @param startDate  is the date to start searching from. If null, start from begining of time. 
     217   *                   Can be iso8601 formatted or string representation of Date object. 
     218   * @param endDate    is the date to search until. If null, search until present date 
     219   * @param annotType  a filter list of rdf types for the annotations. 
     220   * @param maxResults the maximum number of results to return, or 0 for no limit 
     221   * 
     222   * @return the (possibly empty) list of replies. 
     223   * 
     224   * @throws java.text.ParseException if any of the dates or query could not be parsed 
     225   * @throws java.net.URISyntaxException if an element of annotType cannot be parsed as a URI 
     226   */ 
     227  @Transactional(readOnly = true) 
     228  public List<Reply> getReplies(Date startDate, Date endDate, Set<String> annotType, int maxResults) 
     229  throws ParseException, URISyntaxException { 
     230    List<String> replyIds = annotationService.getReplyIds(startDate, endDate, annotType, maxResults); 
     231    return getReplies(replyIds); 
     232  } 
     233 
     234 
    177235 
    178236  /** 
  • head/ambra/webapp/src/main/java/org/topazproject/ambra/annotation/service/WebAnnotation.java

    r7420 r7613  
    7575  * @return title as String. 
    7676  */ 
     77  @Override 
    7778  public String getCommentTitle() { 
    7879    String title; 
     
    124125  * @return CIStatement as String. 
    125126  */ 
     127  @Override 
    126128  public String getCIStatement() { 
    127129    AnnotationBlob b = annotea.getBody(); 
  • head/ambra/webapp/src/main/java/org/topazproject/ambra/annotation/service/WebReply.java

    r7368 r7613  
    6767   * @return title as String. 
    6868   */ 
     69  @Override 
    6970  public String getCommentTitle() { 
    7071    return escapeText(annotea.getTitle()); 
     
    9091  * @return CIStatement as String. 
    9192  */ 
     93  @Override 
    9294  public String getCIStatement() { 
    9395    ReplyBlob r = annotea.getBody(); 
  • head/ambra/webapp/src/main/java/org/topazproject/ambra/feed/action/FeedAction.java

    r7603 r7613  
    22 * $Id$ 
    33 * 
    4  * Copyright (c) 2006-2007 by Topaz, Inc. 
     4 * Copyright (c) 2006-2009 by Topaz, Inc. 
    55 * http://topazproject.org 
    66 * 
     
    1818 */ 
    1919 
    20 package org.topazproject.ambra.article.action; 
     20package org.topazproject.ambra.feed.action; 
    2121 
    2222import java.util.List; 
    23 import java.util.ArrayList; 
    2423 
    2524import org.apache.commons.logging.Log; 
     
    2726 
    2827import org.springframework.transaction.annotation.Transactional; 
     28import org.springframework.beans.factory.annotation.Required; 
    2929 
    3030import org.topazproject.ambra.action.BaseActionSupport; 
    31 import org.topazproject.ambra.article.service.ArticleFeedService; 
    32 import org.topazproject.ambra.article.service.FeedCacheKey; 
    33 import org.topazproject.ambra.article.service.ArticleFeedService.FEED_TYPES; 
     31import org.topazproject.ambra.feed.service.FeedService; 
     32import org.topazproject.ambra.feed.service.ArticleFeedCacheKey; 
     33import org.topazproject.ambra.feed.service.FeedService.FEED_TYPES; 
     34import org.topazproject.ambra.feed.service.AnnotationFeedCacheKey; 
    3435 
    3536import com.opensymphony.xwork2.ModelDriven; 
     
    9192 * maxResults  Integer          No         30          The maximun number of result to return. 
    9293 * type        String           No         Article     Article,Annotation,FormalCorrectionAnnot 
    93  *                                                     MinorCorrectionAnnot,CommentAnnot,Issue 
     94 *                                                     MinorCorrectionAnnot,RetractionAnnot, 
     95 *                                                     CommentAnnot,Issue 
    9496 * </pre> 
    9597 * 
    96  * @see       org.topazproject.ambra.article.service.FeedCacheKey 
     98 * @see       org.topazproject.ambra.feed.service.ArticleFeedCacheKey 
    9799 * @see       org.topazproject.ambra.struts2.AmbraFeedResult 
    98100 * 
     
    101103 */ 
    102104 @SuppressWarnings("UnusedDeclaration") 
    103 public class ArticleFeed extends BaseActionSupport implements ModelDriven { 
    104   private static final Log log = LogFactory.getLog(ArticleFeed.class); 
    105  
    106   //TODO: move these to BaseAction support and standardize on result dispatching. 
    107   private static final String ATOM_RESULT = "ATOM1_0"; 
    108   private static final String JSON_RESULT = "json"; 
    109  
    110   private        ArticleFeedService  articleFeedService; // Feed Service Spring injected. 
    111   private        FeedCacheKey        cacheKey;           // The cache key and action data model 
    112   private        List<String>        articleIds;         // List of Article IDs; result of search 
     105public class FeedAction extends BaseActionSupport implements ModelDriven { 
     106  private static final Log log = LogFactory.getLog(FeedAction.class); 
     107 
     108  private FeedService         feedService; // Feed Service Spring injected. 
     109  private ArticleFeedCacheKey cacheKey;    // The cache key and action data model 
     110  private List<String>        articleIds;  // List of Article or Annotation IDs; result of search 
     111  private List<String>        replyIds;    // List of Reply IDs; result of search 
    113112 
    114113  /** 
     
    120119  @Transactional(readOnly = true) 
    121120  public String execute() throws Exception { 
    122     List<String> annotTypes = new ArrayList<String>(); 
    123121    FEED_TYPES t = cacheKey.feedType(); 
    124122 
    125123    switch (t) { 
    126124      case Annotation : 
    127         articleIds = articleFeedService.getAnnotationIds(cacheKey, null); 
    128         break; 
    129125      case FormalCorrectionAnnot : 
    130         annotTypes.add(t.rdfType()) ; 
    131         articleIds = articleFeedService.getAnnotationIds(cacheKey,annotTypes); 
    132         break; 
    133126      case MinorCorrectionAnnot : 
    134         annotTypes.add(t.rdfType()) ; 
    135         articleIds = articleFeedService.getAnnotationIds(cacheKey,annotTypes); 
    136         break; 
    137      case CommentAnnot : 
    138         annotTypes.add(t.rdfType()) ; 
    139         articleIds = articleFeedService.getAnnotationIds(cacheKey,annotTypes); 
     127      case RetractionAnnot: 
     128      case CommentAnnot : 
     129        articleIds = feedService.getAnnotationIds( 
     130            new AnnotationFeedCacheKey(AnnotationFeedCacheKey.Type.ANNOTATIONS, cacheKey)); 
     131        replyIds = feedService.getReplyIds( 
     132            new AnnotationFeedCacheKey(AnnotationFeedCacheKey.Type.REPLIES, cacheKey)); 
    140133        break; 
    141134      case Article : 
    142         articleIds = articleFeedService.getArticleIds(cacheKey); 
     135        articleIds = feedService.getArticleIds(cacheKey); 
    143136        break; 
    144137      case Issue : 
    145         articleIds = articleFeedService.getIssueArticleIds(cacheKey, getCurrentJournal()); 
     138        articleIds = feedService.getIssueArticleIds(cacheKey, getCurrentJournal()); 
    146139        break; 
    147140    } 
     
    164157    cacheKey.setJournal(getCurrentJournal()); 
    165158    cacheKey.validate(this); 
    166   } 
    167  
    168   /** 
    169    * Set <code>articleFeedService</code> field to the article Feed service singleton. 
    170    * 
    171    * @param  articleFeedService  the object transaction model reference 
    172    */ 
    173   public void setArticleFeedService(final ArticleFeedService articleFeedService) { 
    174     this.articleFeedService = articleFeedService; 
    175   } 
    176  
    177   /** 
    178    * This is the results of the query which consist of a list of article ID's. 
    179    * 
    180    * @return the list of article ID's returned from the query. 
     159    if (log.isErrorEnabled()) { 
     160 
     161      for (Object key : getFieldErrors().keySet()) { 
     162        log.error("Validate error: " + getFieldErrors().get(key) + " on " + key + 
     163            " for cache key: " + cacheKey); 
     164      } 
     165    } 
     166  } 
     167 
     168  /** 
     169   * Set <code>feedService</code> field to the article Feed service singleton. 
     170   * 
     171   * @param  feedService  the object transaction model reference 
     172   */ 
     173  @Required 
     174  public void setFeedService(final FeedService feedService) { 
     175    this.feedService = feedService; 
     176  } 
     177 
     178  /** 
     179   * This is the results of the query which consist of a list of article or annotation ID's. 
     180   * 
     181   * @return the list of article/annotation ID's returned from the query. 
    181182   */ 
    182183  public List<String> getIds() { 
     
    185186 
    186187  /** 
     188   * This is the results of the query which consist of a list of reply ID's. 
     189   * 
     190   * @return the list of reply ID's returned from the query. 
     191   */ 
     192  public List<String> getReplyIds() { 
     193    return replyIds; 
     194  } 
     195 
     196  /** 
    187197   * Return the cache key being used by this action. 
    188198   * 
    189199   * @return  Key to the cache which is also the data model of the action 
    190200   */ 
    191   public FeedCacheKey getCacheKey() { 
     201  public ArticleFeedCacheKey getCacheKey() { 
    192202    return this.cacheKey; 
    193203  } 
     
    203213     * Make sure caheKey is not created twice. 
    204214     */ 
    205     return (cacheKey == null) ? cacheKey = articleFeedService.newCacheKey() : cacheKey; 
     215    return (cacheKey == null) ? cacheKey = feedService.newCacheKey() : cacheKey; 
    206216  } 
    207217 
  • head/ambra/webapp/src/main/java/org/topazproject/ambra/feed/service/ArticleFeedCacheKey.java

    r7483 r7613  
    1818 */ 
    1919 
    20 package org.topazproject.ambra.article.service; 
     20package org.topazproject.ambra.feed.service; 
    2121 
    2222import org.topazproject.ambra.action.BaseActionSupport; 
     
    2727import java.text.ParseException; 
    2828import java.net.URI; 
    29 import org.topazproject.ambra.article.service.ArticleFeedService.FEED_TYPES; 
    3029 
    3130/** 
     
    4241 * affect the output. 
    4342 * 
    44  * @see       org.topazproject.ambra.article.service.ArticleFeedService 
    45  * @see       org.topazproject.ambra.article.service.ArticleFeedService.Invalidator 
    46  * @see       org.topazproject.ambra.article.service.ArticleFeedService.FEED_TYPES 
     43 * @see       FeedService 
     44 * @see       FeedService.Invalidator 
     45 * @see       FeedService.FEED_TYPES 
    4746 * @see       org.topazproject.ambra.struts2.AmbraFeedResult 
    4847 */ 
    49 public class FeedCacheKey implements Serializable, Comparable { 
     48public class ArticleFeedCacheKey implements Serializable, Comparable { 
    5049  private static final long serialVersionUID = 1L; 
    5150  private String journal; 
     
    6463  private int maxResults; 
    6564  private URI issueURI; 
    66   private String type = FEED_TYPES.Article.toString(); 
     65  private String type; 
    6766 
    6867  final SimpleDateFormat dateFrmt = new SimpleDateFormat("yyyy-MM-dd"); 
     
    7271   * Key Constructor - currently does nothing. 
    7372   */ 
    74   public FeedCacheKey() { 
     73  public ArticleFeedCacheKey() { 
     74    type = FeedService.FEED_TYPES.Article.toString(); 
    7575  } 
    7676 
     
    120120  @Override 
    121121  public boolean equals(Object o) { 
    122     if (o == null || !(o instanceof FeedCacheKey)) 
     122    if (o == null || !(o instanceof ArticleFeedCacheKey)) 
    123123      return false; 
    124     FeedCacheKey key = (FeedCacheKey) o; 
     124 
     125    if (o == this) 
     126      return true; 
     127 
     128    ArticleFeedCacheKey key = (ArticleFeedCacheKey) o; 
    125129    return ( 
    126130        key.hashCode == this.hashCode 
     
    160164  public String toString() { 
    161165    StringBuilder builder = new StringBuilder(); 
    162  
    163     builder.append("journal=").append(journal); 
    164     builder.append("; type=").append(type); 
     166    builder.append("ArticleFeedCacheKey{") 
     167           .append("journal=").append(journal) 
     168           .append(", type=").append(type); 
    165169 
    166170    if (sDate != null) 
    167       builder.append("; startDate=").append(sDate); 
     171      builder.append(", startDate=").append(sDate); 
    168172 
    169173    if (eDate != null) 
    170       builder.append("; endDate=").append(eDate); 
     174      builder.append(", endDate=").append(eDate); 
    171175 
    172176    if (category != null) 
    173       builder.append("; category=").append(category); 
     177      builder.append(", category=").append(category); 
    174178 
    175179    if (author != null) 
    176       builder.append("; author=").append(author); 
     180      builder.append(", author=").append(author); 
    177181 
    178182    if (issueURI != null) 
    179       builder.append("; issueURIr=").append(issueURI); 
    180  
    181     builder.append("; maxResults=").append(maxResults); 
     183      builder.append(", issueURIr=").append(issueURI); 
     184 
     185    builder.append(", maxResults=").append(maxResults) 
     186           .append('}'); 
    182187 
    183188    return builder.toString(); 
     
    210215   * @param action - the BaseSupportAction allows reporting of field errors. Pass in a reference 
    211216   *               incase we want to report them. 
    212    * @see ArticleFeedService 
     217   * @see FeedService 
    213218   */ 
    214219  @SuppressWarnings("UnusedDeclaration") 
     
    232237 
    233238    // If there is garbage in the type default to Article 
    234     if (feedType() == FEED_TYPES.Invalid) 
    235       type = FEED_TYPES.Article.toString(); 
     239    if (feedType() == FeedService.FEED_TYPES.Invalid) 
     240      type = FeedService.FEED_TYPES.Article.toString(); 
    236241 
    237242    // Need a positive non-zero number of results 
     
    253258   *         any of the types) 
    254259   */ 
    255   public FEED_TYPES feedType() { 
    256     FEED_TYPES t; 
     260  public FeedService.FEED_TYPES feedType() { 
     261    FeedService.FEED_TYPES t; 
    257262    try { 
    258       t = FEED_TYPES.valueOf(type); 
     263      t = FeedService.FEED_TYPES.valueOf(type); 
    259264    } catch (Exception e) { 
    260265      // It's ok just return invalid. 
    261       t = FEED_TYPES.Invalid; 
     266      t = FeedService.FEED_TYPES.Invalid; 
    262267    } 
    263268    return t; 
  • head/ambra/webapp/src/main/java/org/topazproject/ambra/feed/service/FeedService.java

    r7603 r7613  
    1717 * limitations under the License. 
    1818 */ 
    19 package org.topazproject.ambra.article.service; 
     19package org.topazproject.ambra.feed.service; 
    2020 
    2121import java.net.URI; 
     
    2525import java.util.Date; 
    2626import java.util.List; 
    27 import java.util.Set; 
    2827 
    2928import org.apache.commons.logging.Log; 
     
    4443import org.topazproject.ambra.models.RatingSummary; 
    4544import org.topazproject.ambra.models.Reply; 
     45import org.topazproject.ambra.models.Retraction; 
    4646import org.topazproject.ambra.annotation.service.AnnotationService; 
    4747import org.topazproject.ambra.annotation.service.AnnotationConverter; 
    4848import org.topazproject.ambra.annotation.service.WebAnnotation; 
     49import org.topazproject.ambra.annotation.service.ReplyService; 
     50import org.topazproject.ambra.annotation.service.WebReply; 
    4951import org.topazproject.ambra.cache.AbstractObjectListener; 
    5052import org.topazproject.ambra.cache.Cache; 
     
    5355import org.topazproject.ambra.model.article.ArticleInfo; 
    5456import org.topazproject.ambra.article.action.TOCArticleGroup; 
     57import org.topazproject.ambra.article.service.ArticleOtmService; 
     58import org.topazproject.ambra.article.service.BrowseService; 
     59import org.topazproject.ambra.article.service.NoSuchArticleIdException; 
    5560 
    5661import org.topazproject.otm.Session; 
     
    5964 
    6065/** 
    61  * The <code>ArticleFeedService</code> supplies the API for querying and caching 
    62  * feed request. <code>ArticleFeedService</code> is a Spring injected singleton 
     66 * The <code>FeedService</code> supplies the API for querying and caching 
     67 * feed request. <code>FeedService</code> is a Spring injected singleton 
    6368 * which coordinates access to the <code>annotationService, articleOtmService</code> 
    6469 * and <code>feedCache</code>. 
    6570 */ 
    66 public class ArticleFeedService { 
    67   private static final Log log = LogFactory.getLog(ArticleFeedService.class); 
    68  
    69   private AnnotationService  annotationService;   // Annotation service Spring injected. 
    70   private ArticleOtmService  articleOtmService;   // Article Otm service Spring injected 
    71   private BrowseService      browseService;       // Browse Article Servcie Spring Injected 
    72   private JournalService     journalService;      // Journal service Spring injected. 
    73   private Cache              feedCache;           // Feed Cache Spring injected 
    74   private Invalidator        invalidator;         // Cache invalidator 
    75   private Session            session; 
     71public class FeedService { 
     72  private static final Log log = LogFactory.getLog(FeedService.class); 
     73 
     74  private AnnotationService   annotationService;    // Annotation service Spring injected. 
     75  private ReplyService        replyService;         // Reply service Spring injected. 
     76  private ArticleOtmService   articleOtmService;    // Article Otm service Spring injected 
     77  private BrowseService       browseService;        // Browse Article Servcie Spring Injected 
     78  private JournalService      journalService;       // Journal service Spring injected. 
     79  private AnnotationConverter annotationConverter; // Annotation converter 
     80  private Cache               feedCache;            // Feed Cache Spring injected 
     81  private Invalidator         invalidator;          // Cache invalidator 
     82  private Session             session; 
    7683 
    7784  /** 
     
    9299    MinorCorrectionAnnot   { public String rdfType() { return MinorCorrection.RDF_TYPE;   } 
    93100                             public Class  isClass() { return MinorCorrection.class;      } }, 
     101    RetractionAnnot        { public String rdfType() { return Retraction.RDF_TYPE;        } 
     102                             public Class  isClass() { return Retraction.class;           } }, 
    94103    RatingAnnot            { public String rdfType() { return Rating.RDF_TYPE;            } 
    95104                             public Class  isClass() { return Rating.class;               } }, 
     
    111120   * Constructor - currently does nothing. 
    112121   */ 
    113   public ArticleFeedService(){ 
     122  public FeedService(){ 
    114123  } 
    115124 
     
    119128   * @return Key a new cache key to be used as a data model for the FeedAction. 
    120129   */ 
    121   public FeedCacheKey newCacheKey() { 
    122     return new FeedCacheKey(); 
     130  public ArticleFeedCacheKey newCacheKey() { 
     131    return new ArticleFeedCacheKey(); 
    123132  } 
    124133 
     
    132141   * @throws ApplicationException ApplicationException 
    133142   */ 
    134   public List<String> getArticleIds(final FeedCacheKey cacheKey) throws ApplicationException { 
     143  public List<String> getArticleIds(final ArticleFeedCacheKey cacheKey) throws ApplicationException { 
    135144    // Create a local lookup based on the feed URI. 
    136145    Cache.Lookup<List<String>, ApplicationException> lookUp = 
     
    147156   * 
    148157   * @param cacheKey is both the feedAction data model and cache key. 
     158   * @param journal Current journal 
    149159   * @return List&lt;String&gt; if article Ids. 
    150160   * @throws ApplicationException ApplicationException 
    151    */ 
    152   public List<String> getIssueArticleIds(final FeedCacheKey cacheKey, String journal) throws 
     161   * @throws URISyntaxException URISyntaxException 
     162   */ 
     163  public List<String> getIssueArticleIds(final ArticleFeedCacheKey cacheKey, String journal) throws 
    153164      URISyntaxException, ApplicationException { 
    154165    List<String> articleList  = new ArrayList<String>(); 
     
    181192 
    182193  /** 
    183    * Returns a list of annotations associated with a particular article. 
    184    * 
    185    * @param targetId  limits annotationIds to a particlular target 
    186    * @param annotType  a filter list of rdf types for the annotations. 
    187    * @param maxResult maximum number of annotations to return 
    188    * @param needBody   boolean specifying whether the body of the annotation is needed 
    189    * @return <code>List&lt;WebAnnotation&gt;</code> a list of webannotations that related to the 
    190    *         specified article 
    191    * @throws ApplicationException  Converts all exceptions to ApplicationException 
    192    */ 
    193   public List<WebAnnotation> getAnnotations(final String targetId, List<String> annotType, 
    194       int maxResult, boolean needBody) throws ApplicationException { 
    195     AnnotationConverter converter = new AnnotationConverter(); 
    196     List<WebAnnotation> webAnnot; 
    197  
    198     try { 
    199       List<ArticleAnnotation> annotations = annotationService.getAnnotations( 
    200                                               targetId, null, null, null, 
    201                                               annotType, null, true,  maxResult); 
    202       webAnnot = converter.convert(annotations,true, needBody); 
    203     } catch (Exception ex) { 
    204       throw new ApplicationException(ex); 
    205     } 
    206     return webAnnot; 
    207   } 
    208  
    209   /** 
    210194   * Returns a list of annotation Ids based on parameters contained in 
    211    * the cache key. If a start date is not specified tjen a default 
     195   * the cache key. If a start date is not specified then a default 
    212196   * date is used but not stored in the key. 
    213197   * 
    214    * @param cacheKey is both the feedAction data model and cache key. 
    215    * @param annotType  a filter list of rdf types for the annotations. 
     198   * @param cacheKey cache key. 
    216199   * @return <code>List&lt;String&gt;</code> a list of annotation Ids 
    217200   * @throws ApplicationException   Converts all exceptions to ApplicationException 
    218201   */ 
    219   public List<String> getAnnotationIds(final FeedCacheKey cacheKey, List<String> annotType) 
     202  public List<String> getAnnotationIds(final AnnotationFeedCacheKey cacheKey) 
    220203      throws ApplicationException { 
     204 
     205    // Create a local lookup based on the feed URI. 
     206    Cache.Lookup<List<String>, ApplicationException> lookUp = 
     207      new Cache.SynchronizedLookup<List<String>, ApplicationException>(cacheKey) { 
     208        public List<String> lookup() throws ApplicationException { 
     209          return fetchAnnotationIds(cacheKey); 
     210        } 
     211      }; 
     212    // Get articel ID's from the feed cache or add it 
     213    return feedCache.get(cacheKey, -1, lookUp); 
     214  } 
     215 
     216  /** 
     217   * Returns a list of reply Ids based on parameters contained in 
     218   * the cache key. If a start date is not specified then a default 
     219   * date is used but not stored in the key. 
     220   * 
     221   * @param cacheKey cache key 
     222   * @return <code>List&lt;String&gt;</code> a list of reply Ids 
     223   * @throws ApplicationException   Converts all exceptions to ApplicationException 
     224   */ 
     225  public List<String> getReplyIds(final AnnotationFeedCacheKey cacheKey) 
     226      throws ApplicationException { 
     227 
     228    // Create a local lookup based on the feed URI. 
     229    Cache.Lookup<List<String>, ApplicationException> lookUp = 
     230      new Cache.SynchronizedLookup<List<String>, ApplicationException>(cacheKey) { 
     231        public List<String> lookup() throws ApplicationException { 
     232          return fetchReplyIds(cacheKey); 
     233        } 
     234      }; 
     235    // Get articel ID's from the feed cache or add it 
     236    return feedCache.get(cacheKey, -1, lookUp); 
     237  } 
     238 
     239  private List<String> fetchAnnotationIds(final AnnotationFeedCacheKey cacheKey) 
     240      throws ApplicationException { 
     241     
    221242    List<String> annotIds; 
    222243    try { 
    223244      annotIds = annotationService.getAnnotationIds( 
    224                       null, cacheKey.getSDate(), cacheKey.getEDate(), 
    225                       null, annotType, null, true, cacheKey.getMaxResults()); 
     245                 cacheKey.getStartDate(), cacheKey.getEndDate(), cacheKey.getAnnotationTypes(), 
     246                 cacheKey.getMaxResults()); 
    226247 
    227248    } catch (Exception ex) { 
     
    231252  } 
    232253 
    233   /** 
    234    * Returns a list of annotations which meet the criteria set by parameters in 
    235    * the cache key. 
    236    * 
    237    * @param cacheKey is both the feedAction data model and cache key. 
    238    * @param annotType  a filter list of rdf types for the annotations. 
    239    * @return <code>List&lt;WebAnnotation&gt;</code> a list of webannotations that related to the 
    240    *         specified article 
    241    * @throws ApplicationException   Converts all exceptions to ApplicationException 
    242    */ 
    243   // FIXME: Dead code - Method is not used anywhere 
    244   public List<WebAnnotation> getAnnotations(final FeedCacheKey cacheKey, List<String> annotType) 
     254  private List<String> fetchReplyIds(final AnnotationFeedCacheKey cacheKey) 
    245255      throws ApplicationException { 
    246     AnnotationConverter converter = new AnnotationConverter(); 
    247     List<WebAnnotation> webAnnot; 
    248  
     256 
     257    List<String> replyIds; 
    249258    try { 
    250       List<ArticleAnnotation> annotations = annotationService.getAnnotations( 
    251                                 null, cacheKey.getSDate(), cacheKey.getEDate(), 
    252                                 null, annotType, null, true, cacheKey.getMaxResults()); 
    253       webAnnot = converter.convert(annotations,true,true); 
     259      replyIds = annotationService.getReplyIds( 
     260                 cacheKey.getStartDate(), cacheKey.getEndDate(), cacheKey.getAnnotationTypes(), 
     261                 cacheKey.getMaxResults()); 
     262 
    254263    } catch (Exception ex) { 
    255264      throw new ApplicationException(ex); 
    256265    } 
    257     return webAnnot; 
     266    return  replyIds; 
    258267  } 
    259268 
     
    269278  public List<WebAnnotation> getAnnotations(final List<String> annotIds) 
    270279      throws ApplicationException { 
    271     AnnotationConverter converter = new AnnotationConverter(); 
    272280    List<WebAnnotation> webAnnot; 
    273281 
    274282    try { 
    275283      List<ArticleAnnotation> annotations = annotationService.getAnnotations(annotIds); 
    276       webAnnot = converter.convert(annotations,true,true); 
     284      webAnnot = annotationConverter.convert(annotations,true,true); 
    277285    } catch (Exception ex) { 
    278286      throw new ApplicationException(ex); 
    279287    } 
    280288    return webAnnot; 
     289  } 
     290 
     291  /** 
     292   * Returns a list of replies associated with a particular list 
     293   * annotation Ids. 
     294   * 
     295   * @param replyIds a list of reply Ids to retrieve. 
     296   * @return <code>List&lt;WebReply&gt;</code> a list of webareplies 
     297   *         with the specified Ids. 
     298   * @throws ApplicationException   Converts all exceptions to ApplicationException 
     299   */ 
     300  public List<WebReply> getReplies(final List<String> replyIds) 
     301      throws ApplicationException { 
     302 
     303    List<WebReply> webReplies; 
     304 
     305    try { 
     306      List<Reply> replies = replyService.getReplies(replyIds); 
     307      webReplies = annotationConverter.convertReplies(replies,true,true); 
     308    } catch (Exception ex) { 
     309      throw new ApplicationException(ex); 
     310    } 
     311    return webReplies; 
    281312  } 
    282313 
     
    289320   * @throws  ApplicationException Converts all exceptions to ApplicationException 
    290321   */ 
    291   private List<String> fetchArticleIds(final FeedCacheKey cacheKey) throws ApplicationException { 
     322  private List<String> fetchArticleIds(final ArticleFeedCacheKey cacheKey) throws ApplicationException { 
    292323    List<String> categoriesList = new ArrayList<String>(); 
    293324    if (cacheKey.getCategory() != null && cacheKey.getCategory().length() > 0) { 
     
    348379 
    349380  /** 
     381   * @param replyService Reply Service 
     382   */ 
     383  @Required 
     384  public void setReplyService(ReplyService replyService) { 
     385    this.replyService = replyService; 
     386  } 
     387 
     388  /** 
    350389   * @param browseService   Browse Service 
    351390   */ 
     
    355394    this.browseService = browseService; 
    356395  } 
     396 
     397  /** 
     398   * @param annotationConverter  Annotation converter 
     399   */ 
     400  @Required 
     401  public void setAnnotationConverter(AnnotationConverter annotationConverter) { 
     402    this.annotationConverter = annotationConverter; 
     403  } 
     404 
     405 
    357406 
    358407  /** 
     
    405454   * then remove that cache entry. 
    406455   * 
    407    * @see ArticleFeedService 
     456   * @see FeedService 
    408457   */ 
    409458  public class Invalidator extends AbstractObjectListener { 
     
    427476      } else if (object instanceof Journal) { 
    428477        invalidateFeedCacheForJournal((Journal)object, updates); 
     478      } else if (object instanceof ArticleAnnotation) { 
     479        invalidateFeedCacheForAnnotation((ArticleAnnotation)object); 
     480      } else if (object instanceof Reply) { 
     481        invalidateFeedCacheForReply((Reply)object); 
    429482      } 
    430483    } 
     
    444497        throws Exception { 
    445498      // If this is an Active Article check to see if it invalidates the feed cache 
    446       if (object instanceof Article && ((Article) object).getState() == Article.STATE_ACTIVE) 
    447         invalidateFeedCacheForArticle((Article)object); 
     499      if (object instanceof Article && ((Article) object).getState() == Article.STATE_ACTIVE) { 
     500        invalidateFeedCacheForArticle((Article) object); 
     501      } else if (object instanceof ArticleAnnotation) { 
     502        invalidateFeedCacheForAnnotation((ArticleAnnotation) object); 
     503      } else if (object instanceof Reply) { 
     504        invalidateFeedCacheForReply((Reply) object); 
     505      } 
    448506    } 
    449507 
     
    474532     * @param article the article which might change the cash. 
    475533     */ 
    476     @SuppressWarnings("unchecked") 
    477534    private void invalidateFeedCacheForArticle(Article article) { 
    478       for (FeedCacheKey key : (Set<FeedCacheKey>) feedCache.getKeys()) { 
    479         if (matches(key, article, true)) 
    480           feedCache.remove(key); 
     535 
     536      for (Object key : feedCache.getKeys()) { 
     537        if (key instanceof ArticleFeedCacheKey) { 
     538          if (matchesArticle((ArticleFeedCacheKey) key, article)) 
     539            feedCache.remove(key); 
     540        } else if (key instanceof AnnotationFeedCacheKey) { 
     541          if(matchesJournal(article, ((AnnotationFeedCacheKey)key).getJournal())) 
     542            feedCache.remove(key); 
     543        } 
    481544      } 
    482545    } 
     
    488551     * @param journal  the journal of interest 
    489552     */ 
    490     @SuppressWarnings("unchecked") 
    491553    private void invalidateFeedCacheForJournalArticle(Journal journal) { 
    492       for (FeedCacheKey key : (Set<FeedCacheKey>) feedCache.getKeys()) { 
    493         if (key.getJournal().equals(journal.getKey())) { 
    494           feedCache.remove(key); 
    495         } 
    496       } 
    497     } 
     554      for (Object key : feedCache.getKeys()) { 
     555        if (key instanceof ArticleFeedCacheKey) { 
     556          if (((ArticleFeedCacheKey) key).getJournal().equals(journal.getKey())) 
     557            feedCache.remove(key); 
     558        } else if (key instanceof AnnotationFeedCacheKey) { 
     559          if (((AnnotationFeedCacheKey)key).getJournal().equals(journal.getKey())) 
     560            feedCache.remove(key); 
     561        } 
     562      } 
     563    } 
     564 
     565    /** 
     566     * Invalidate the cache entries based on a article annotation. 
     567     * 
     568     * @param annotation The annotation. 
     569     */ 
     570    private void invalidateFeedCacheForAnnotation(ArticleAnnotation annotation) { 
     571      for (Object key : feedCache.getKeys()) { 
     572        if (key instanceof AnnotationFeedCacheKey) { 
     573          if (matchesAnnotation((AnnotationFeedCacheKey)key, annotation)) 
     574            feedCache.remove(key); 
     575        } 
     576      } 
     577    } 
     578 
     579 
     580    /** 
     581     * Invalidate the cache entries based on a annotation reply. 
     582     * 
     583     * @param reply The reply. 
     584     */ 
     585    private void invalidateFeedCacheForReply(Reply reply) { 
     586      for (Object key : feedCache.getKeys()) { 
     587        if (key instanceof AnnotationFeedCacheKey) { 
     588          AnnotationFeedCacheKey annotationKey = (AnnotationFeedCacheKey) key; 
     589          if (matchesReply(annotationKey, reply)) 
     590            feedCache.remove(key); 
     591        } 
     592      } 
     593    } 
     594 
     595 
     596 
    498597 
    499598    /** 
     
    527626     * @param key          the cache key and input parameters 
    528627     * @param article      article that has caused the change 
    529      * @param checkJournal include journal as part of match if true. 
    530628     * @return boolean true if we need to remove this entry from the cache 
    531629     */ 
    532     private boolean matches(FeedCacheKey key, Article article, boolean checkJournal) { 
    533       if (checkJournal && !matchesJournal(key, article)) 
     630    private boolean matchesArticle(ArticleFeedCacheKey key, Article article) { 
     631      if (!matchesJournal(article, key.getJournal())) 
    534632        return false; 
    535633 
    536634      DublinCore dc = article.getDublinCore(); 
    537   
    538       if (!matchesDates(key, dc)) 
     635 
     636      return matchesDates(dc.getDate(), key.getSDate(), key.getEDate()) && 
     637             matchesCategory(key, article) && 
     638             matchesAuthor(key, dc); 
     639 
     640    } 
     641 
     642    private boolean matchesAnnotation(AnnotationFeedCacheKey key, ArticleAnnotation annotation) { 
     643      try { 
     644        Article article = articleOtmService.getArticle(annotation.getAnnotates()); 
     645        if (!matchesJournal(article, key.getJournal())) 
     646          return false; 
     647 
     648      } catch (NoSuchArticleIdException e) { 
     649        log.error("Failed trying to invalidate FeedCache for annotation " + annotation.getId() + 
     650            " and key " + key.toString(), e); 
     651        return true; // remove this cache entry 
     652      } 
     653 
     654      return matchesDates(annotation.getCreated(), key.getStartDate(), key.getEndDate()) && 
     655             matchesAnnotationType(key, annotation); 
     656 
     657    } 
     658 
     659 
     660    private boolean matchesReply(AnnotationFeedCacheKey key, Reply reply) { 
     661 
     662      if (key.getType() != AnnotationFeedCacheKey.Type.REPLIES) 
    539663        return false; 
    540664 
    541       return (!matchesCategory(key, article) && !matchesAuthor(key, dc)); 
    542  
    543     } 
     665      ArticleAnnotation annotation = annotationService.getAnnotation(reply.getRoot()); 
     666      if (annotation != null) { 
     667        try { 
     668          Article article = articleOtmService.getArticle(annotation.getAnnotates()); 
     669          if (!matchesJournal(article, key.getJournal())) 
     670            return false; 
     671        } catch (NoSuchArticleIdException e) { 
     672          log.error("Failed trying to invalidate FeedCache for reply " + reply.getId() + 
     673              " and key " + key.toString(), e); 
     674          return true; // remove this cache entry 
     675        } 
     676 
     677        if (!matchesAnnotationType(key, annotation)) 
     678          return false; 
     679 
     680      } else { 
     681        log.error("Root annotation not found for reply " + reply.getId()); 
     682        return true;  // remove this cache entry 
     683      } 
     684 
     685      return matchesDates(reply.getCreated(), key.getStartDate(), key.getEndDate()); 
     686 
     687    } 
     688 
    544689 
    545690    /** 
     
    553698     * @return  boolean true if there is a match 
    554699     */ 
    555     private boolean matchesAuthor(FeedCacheKey key, DublinCore dc) { 
     700    private boolean matchesAuthor(ArticleFeedCacheKey key, DublinCore dc) { 
    556701      boolean matches = false; 
    557702 
     
    580725     * @return boolean true if the category matches (key.category = null is wildcard) 
    581726     */ 
    582     private boolean matchesCategory(FeedCacheKey key, Article article) { 
     727    private boolean matchesCategory(ArticleFeedCacheKey key, Article article) { 
    583728      boolean matches = false; 
    584729 
     
    596741    } 
    597742 
     743    private boolean matchesAnnotationType(AnnotationFeedCacheKey key, ArticleAnnotation annotation) { 
     744 
     745      return key.getAnnotationTypes() == null || 
     746             key.getAnnotationTypes().size() == 0 || 
     747             key.getAnnotationTypes().contains(FEED_TYPES.Annotation.rdfType()) || 
     748             annotation instanceof FormalCorrection && 
     749                key.getAnnotationTypes().contains(FEED_TYPES.FormalCorrectionAnnot.rdfType()) || 
     750             annotation instanceof MinorCorrection && 
     751                key.getAnnotationTypes().contains(FEED_TYPES.MinorCorrectionAnnot.rdfType()) || 
     752             annotation instanceof Comment && 
     753                key.getAnnotationTypes().contains(FEED_TYPES.CommentAnnot.rdfType()) || 
     754             annotation instanceof Retraction && 
     755                key.getAnnotationTypes().contains(FEED_TYPES.RetractionAnnot.rdfType()); 
     756 
     757    } 
     758 
     759 
    598760    /** 
    599761     * Check to see if the article date is between the start and end date specified in the key. If 
    600762     * it is then return true and the entry for this key should be removed. 
    601763     * 
    602      * @param key cache key 
    603      * @param dc  Dublincore field from the article 
    604      * 
     764     * @param createDate date object is created 
     765     * @param startDate Start date, can be null. 
     766     * @param endDate End date can be null. 
    605767     * @return boolean true if the article date falls between the start and end date 
    606768     */ 
    607     private boolean matchesDates(FeedCacheKey key, DublinCore dc) { 
    608       Date articleDate = dc.getDate(); 
     769    private boolean matchesDates(Date createDate, Date startDate, Date endDate) { 
     770 
    609771      boolean matches = false; 
    610772 
    611773      // If start and end are null then it doesn't matter what the article date is. 
    612       if ((key.getEDate() == null) && (key.getSDate() == null)) { 
     774      if ((endDate == null) && (startDate == null)) { 
    613775        matches = true; 
    614       } else if (articleDate != null) { 
    615         if ((key.getEDate() == null) && articleDate.after(key.getSDate())) { 
     776      } else if (createDate != null) { 
     777        if ((endDate == null) && createDate.after(startDate)) { 
    616778          matches = true; 
    617         } else if ((key.getSDate() == null) && articleDate.before(key.getEDate())) { 
     779        } else if ((startDate == null) && createDate.before(endDate)) { 
    618780          matches = true; 
    619         } else if (articleDate.after(key.getSDate()) && articleDate.before(key.getEDate())) { 
     781        } else if (createDate.after(startDate) && createDate.before(endDate)) { 
    620782          matches = true; 
    621783        } 
     
    628790     * journals the article belongs to. 
    629791     * 
    630      * @param key      a cache key and actiopn data model 
    631792     * @param article  the article 
     793     * @param journalKey Journal 
    632794     * @return boolean true if key.journal matches one of the journals returned 
    633795     *         by the journal service. 
    634796     */ 
    635     private boolean matchesJournal(FeedCacheKey key, Article article) { 
     797    private boolean matchesJournal(Article article, String journalKey) { 
    636798      boolean matches = false; 
    637799 
    638       if (key.getJournal() != null) { 
     800      if (journalKey != null) { 
    639801        for (Journal journal : journalService.getJournalsForObject(article.getId())) { 
    640           if (journal.getKey().equals(key.getJournal())) { 
     802          if (journal.getKey().equals(journalKey)) { 
    641803            matches = true; 
    642804            break; 
  • head/ambra/webapp/src/main/java/org/topazproject/ambra/struts2/AmbraFeedResult.java

    r7609 r7613  
    3232import java.util.Date; 
    3333import java.util.Set; 
     34import java.util.SortedMap; 
     35import java.util.TreeMap; 
    3436 
    3537import java.io.Writer; 
     
    5052import org.topazproject.ambra.models.Representation; 
    5153import org.topazproject.ambra.models.UserProfile; 
    52 import org.topazproject.ambra.article.action.ArticleFeed; 
    53 import org.topazproject.ambra.article.service.ArticleFeedService; 
    54 import org.topazproject.ambra.article.service.FeedCacheKey; 
    55 import org.topazproject.ambra.article.service.ArticleFeedService.FEED_TYPES; 
     54import org.topazproject.ambra.feed.service.FeedService; 
     55import org.topazproject.ambra.feed.service.ArticleFeedCacheKey; 
     56import org.topazproject.ambra.feed.service.FeedService.FEED_TYPES; 
    5657import org.topazproject.ambra.web.VirtualJournalContext; 
    5758import org.topazproject.ambra.configuration.ConfigurationStore; 
     
    5960import org.jdom.Element; 
    6061import org.springframework.transaction.annotation.Transactional; 
     62import org.springframework.beans.factory.annotation.Required; 
    6163import org.topazproject.ambra.models.UserAccount; 
    6264import org.topazproject.ambra.annotation.service.WebAnnotation; 
     65import org.topazproject.ambra.annotation.service.WebReply; 
     66import org.topazproject.ambra.annotation.service.BaseAnnotation; 
    6367import org.topazproject.ambra.ApplicationException; 
    6468import org.topazproject.ambra.model.article.ArticleType; 
     
    8791 * </pre> 
    8892 * 
    89  * @see       org.topazproject.ambra.article.service.FeedCacheKey 
    90  * @see       ArticleFeed 
     93 * @see       org.topazproject.ambra.feed.service.ArticleFeedCacheKey 
     94 * @see       org.topazproject.ambra.feed.action.FeedAction 
    9195 * 
    9296 * @author jsuttor 
    9397 */ 
    9498public class AmbraFeedResult extends Feed implements Result { 
    95   private List<Article>       articles; 
    96   private List<WebAnnotation> annotations; 
    97   private ArticleFeedService  articleFeedService; 
     99  private FeedService feedService; 
    98100  private ArticleXMLUtils     articleXmlUtils; 
    99101 
     
    179181  @SuppressWarnings("unchecked") 
    180182  public void execute(ActionInvocation ai) throws Exception { 
     183 
    181184    HttpServletRequest request = ServletActionContext.getRequest(); 
    182185    String pathInfo = request.getPathInfo(); 
     
    189192 
    190193    // Get the article IDs that were cached by the feed. 
    191     FeedCacheKey cacheKey = (FeedCacheKey)ai.getStack().findValue("cacheKey"); 
    192     List<String> articleIds = (List<String>) ai.getStack().findValue("Ids"); 
    193     FEED_TYPES t =  cacheKey.feedType(); 
    194  
    195     switch (t) { 
    196       case Annotation: 
    197       case FormalCorrectionAnnot: 
    198       case MinorCorrectionAnnot: 
    199       case CommentAnnot: 
    200         annotations = articleFeedService.getAnnotations(articleIds); 
    201         break; 
    202       case Article : 
    203       case Issue : 
    204         articles = articleFeedService.getArticles(articleIds); 
    205         break; 
    206     } 
    207  
    208     String xmlBase = (cacheKey.getRelativeLinks() ? "" : JOURNAL_URI); 
     194    ArticleFeedCacheKey cacheKey = (ArticleFeedCacheKey)ai.getStack().findValue("cacheKey"); 
    209195 
    210196    List<Link> otherLinks = new ArrayList<Link>(); 
     
    217203 
    218204    Content tagline = new Content(); 
    219  
    220205    tagline.setValue(FEED_TAGLINE()); 
    221206    setTagline(tagline); 
     207 
    222208    setUpdated(new Date()); 
    223209    setIcon(FEED_ICON()); 
     
    230216    setAuthors(feedAuthors); 
    231217 
    232     // Add each Article as a Feed Entry 
     218    String xmlBase = (cacheKey.getRelativeLinks() ? "" : JOURNAL_URI); 
     219 
     220 
     221    FEED_TYPES t =  cacheKey.feedType(); 
     222 
     223    List<String> articleIds = (List<String>) ai.getStack().findValue("Ids"); 
     224 
     225    // Add each Article or Annotations as a Feed Entry 
    233226    List<Entry> entries = null; 
    234227 
     
    237230      case FormalCorrectionAnnot: 
    238231      case MinorCorrectionAnnot: 
     232      case RetractionAnnot: 
    239233      case CommentAnnot: 
    240         entries = buildAnnotationFeed(xmlBase); 
     234        List<WebAnnotation> annotations = feedService.getAnnotations(articleIds); 
     235        List<String> replyIds = (List<String>) ai.getStack().findValue("ReplyIds"); 
     236        List<WebReply> replies = feedService.getReplies(replyIds); 
     237        entries = buildAnnotationFeed(xmlBase, annotations, replies, cacheKey.getMaxResults()); 
    241238        break; 
    242239      case Article : 
    243240      case Issue : 
    244         entries = buildArticleFeed(cacheKey, xmlBase); 
     241        List<Article> articles = feedService.getArticles(articleIds); 
     242        entries = buildArticleFeed(cacheKey, xmlBase, articles); 
    245243        break; 
    246244    } 
     
    255253   * 
    256254   * @param xmlBase   xml base url 
     255   * @param annotations list of web annotations 
     256   * @param replies list of web replies 
     257   * @param maxResults maximum number of results to display 
    257258   * @return List of entries for the feed 
    258259   * @throws Exception   Exception 
    259260   */ 
    260   private List<Entry> buildAnnotationFeed(String xmlBase) throws Exception { 
     261  private List<Entry> buildAnnotationFeed(String xmlBase, 
     262                                          List<WebAnnotation> annotations, 
     263                                          List<WebReply> replies, 
     264                                          int maxResults) 
     265      throws Exception { 
     266 
     267    // Combine annotations and replies sorted by date 
     268    SortedMap<Date, BaseAnnotation> map = new TreeMap<Date, BaseAnnotation>(); 
     269 
     270    for (WebAnnotation annotation : annotations) { 
     271      map.put(annotation.getCreatedAsDate(), annotation); 
     272    } 
     273 
     274    for (WebReply reply : replies) { 
     275      map.put(reply.getCreatedAsDate(), reply); 
     276    } 
     277 
    261278    // Add each Article as a Feed Entry 
    262279    List<Entry> entries = new ArrayList<Entry>(); 
    263280 
    264     for (WebAnnotation annot : annotations) { 
     281    int i = 0; 
     282    for (BaseAnnotation annot : map.values()) { 
     283 
    265284      Entry entry = newEntry(annot); 
    266       List<String> ids = new ArrayList<String>(); 
    267       List<Article> art; 
    268       List<Link> altLinks = new ArrayList<Link>(); 
    269  
    270       ids.add(annot.getAnnotates()); 
    271       art = articleFeedService.getArticles(ids); 
     285 
     286      String displayName; 
     287      List<String> articleIds = new ArrayList<String>(); 
     288      if (annot instanceof WebReply) { 
     289        WebReply webReply = (WebReply) annot; 
     290        List<String> rootAnnotationIds = new ArrayList<String>(); 
     291        rootAnnotationIds.add(webReply.getRoot()); 
     292        List<WebAnnotation> rootAnnotations = feedService.getAnnotations(rootAnnotationIds); 
     293        articleIds.add(rootAnnotations.get(0).getAnnotates()); 
     294        displayName = "Reply"; 
     295      } else { 
     296        WebAnnotation webAnnotation = (WebAnnotation) annot; 
     297        articleIds.add(webAnnotation.getAnnotates()); 
     298        displayName = webAnnotation.getDisplayName(); 
     299      } 
     300 
     301      List<Article> art = feedService.getArticles(articleIds); 
    272302 
    273303      // Link to annotation via xmlbase 
    274304      Link selfLink = newSelfLink(annot, xmlBase); 
     305 
     306      List<Link> altLinks = new ArrayList<Link>(); 
    275307      altLinks.add(selfLink); 
    276308 
     
    287319      Person person = new Person(); 
    288320 
    289       UserAccount ua = articleFeedService.getUserAcctFrmID(annot.getCreator()); 
     321      UserAccount ua = feedService.getUserAcctFrmID(annot.getCreator()); 
    290322      if (ua != null) 
    291323        person.setName(ua.getProfile().getDisplayName()); 
     
    296328      entry.setAuthors(authors); 
    297329 
    298       List <Content> contents = newAnnotationsList(annot, selfLink); 
     330      List <Content> contents = newAnnotationsList(selfLink, displayName, annot.getComment()); 
    299331      entry.setContents(contents); 
    300332 
    301       List<com.sun.syndication.feed.atom.Category> categories = newCategoryList(annot); 
     333      List<com.sun.syndication.feed.atom.Category> categories = newCategoryList(displayName); 
    302334      entry.setCategories(categories); 
    303335 
    304336      // Add completed Entry to List 
    305337      entries.add(entry); 
     338 
     339      // i starts with 1, if maxResults=0 this will not interrupt the loop 
     340      if (++i == maxResults) 
     341        break; 
    306342    } 
    307343    return entries; 
     
    312348   * by the query action. 
    313349   * 
    314    * @param cacheKey   cache/data model 
     350   * @param cacheKey    cache/data model 
    315351   * @param xmlBase     xml base url 
     352   * @param articles    list of articles 
    316353   * @return List of entries for feed. 
    317354   */ 
    318   private List<Entry> buildArticleFeed(FeedCacheKey cacheKey, String xmlBase) { 
     355  private List<Entry> buildArticleFeed(ArticleFeedCacheKey cacheKey, String xmlBase, List<Article> articles) { 
    319356    // Add each Article as a Feed Entry 
    320357    List<Entry> entries = new ArrayList<Entry>(); 
     
    456493   * @return List<Content> consisting of HTML descriptions of the article and author 
    457494   */ 
    458   private List<Content>newContentsList(FeedCacheKey cacheKey, Article article, String authorNames, 
     495  private List<Content>newContentsList(ArticleFeedCacheKey cacheKey, Article article, String authorNames, 
    459496      int numAuthors) { 
    460497    List<Content> contents = new ArrayList<Content>(); 
     
    490527   * the Author (or Authors if extended format) and the DublinCore description of the article. 
    491528   * 
    492    * @param annotation  annotation to convert into Content element 
    493529   * @param link        link to the article 
    494530   * 
     531   * @param entryTypeDisplay text to describe type of entry : FormalCorrection, Reply ... 
     532   * @param comment Comment 
    495533   * @return List<Content> consisting of HTML descriptions of the article and author 
    496534   * 
     
    498536   */ 
    499537 
    500   private List<Content>newAnnotationsList(WebAnnotation annotation, Link link) 
    501                          throws ApplicationException { 
     538  private List<Content> newAnnotationsList(Link link, String entryTypeDisplay, String comment) 
     539      throws ApplicationException { 
     540 
    502541    List<Content> contents = new ArrayList<Content>(); 
    503542    Content description = new Content(); 
    504     String displayName = annotation.getDisplayName(); 
    505543    description.setType("html"); 
    506544 
    507545    StringBuilder text = new StringBuilder(); 
    508546    text.append("<p>"); 
    509     if (displayName != null) { 
    510       String d = displayName + " on "; 
    511       text.append(d); 
    512     } 
    513  
    514     { 
    515       String d = " <a href=" + link.getHref() + ">" + link.getTitle() + "</a></p>"; 
    516       text.append(d); 
    517       d = "<p>" + annotation.getComment() + "</p>"; 
    518       text.append(d); 
    519     } 
     547    if (entryTypeDisplay != null) 
     548      text.append(entryTypeDisplay).append(" on "); 
     549 
     550    text.append(" <a href=") 
     551        .append(link.getHref()) 
     552        .append('>') 
     553        .append(link.getTitle()) 
     554        .append("</a></p>") 
     555        .append("<p>") 
     556        .append(comment) 
     557        .append("</p>"); 
    520558    description.setValue(text.toString()); 
    521559    contents.add(description); 
     
    553591   * @return String of authors names. 
    554592   */ 
    555   private String newAuthorsList(FeedCacheKey cacheKey, Article article, List<Person> authors) { 
     593  private String newAuthorsList(ArticleFeedCacheKey cacheKey, Article article, List<Person> authors) { 
    556594    Citation bc = article.getDublinCore().getBibliographicCitation(); 
    557595    StringBuilder authorNames = new StringBuilder(); 
     
    628666   * @return  link to the article 
    629667   */ 
    630   private Link newSelfLink(WebAnnotation annot, String xmlBase) { 
     668  private Link newSelfLink(BaseAnnotation annot, String xmlBase) { 
     669 
     670    String url; 
     671    if (annot instanceof WebReply) { 
     672      url = ((WebReply)annot).getRoot(); 
     673    } else { 
     674      url = doiToUrl(annot.getId()); 
     675    } 
     676 
     677    StringBuilder href = new StringBuilder(xmlBase) 
     678          .append("annotation/listThread.action?inReplyTo=") 
     679          .append(url) 
     680          .append("&root=") 
     681          .append(url); 
     682 
     683    // add anchor 
     684    if (annot instanceof WebReply) { 
     685      href.append('#').append(annot.getId()); 
     686    } 
     687 
    631688    Link link = new Link(); 
    632     String href = xmlBase + "annotation/listThread.action?inReplyTo="; 
    633     String url = doiToUrl(annot.getId()); 
    634  
    635689    link.setRel("alternate"); 
    636     link.setHref(href + url + "&root=" + url); 
     690    link.setHref(href.toString()); 
    637691    link.setTitle(annot.getCommentTitle()); 
    638692    return link; 
     
    691745   * @return Entry  feed entry 
    692746   */ 
    693   private Entry newEntry(WebAnnotation annot) { 
     747  private Entry newEntry(BaseAnnotation annot) { 
    694748    Entry entry = new Entry(); 
    695749 
     
    704758  } 
    705759 
     760 
    706761  /** 
    707762   * Create a default Plos person element. 
     
    728783   * @return <code>Link</code> user provide link. 
    729784   */ 
    730   private Link newLink(FeedCacheKey cacheKey, String uri) { 
     785  private Link newLink(ArticleFeedCacheKey cacheKey, String uri) { 
    731786    if (cacheKey.getSelfLink() == null || cacheKey.getSelfLink().equals("")) { 
    732787      if (uri.startsWith("/")) { 
     
    748803   * Build an atom feed categroy list for for the WebAnnotation. 
    749804   * 
    750    * @param annotation build category element from annotation 
    751    * 
     805   * @param displayName Name of the entry. 
    752806   * @return  List of  atom categories 
    753807   */ 
    754   public List<com.sun.syndication.feed.atom.Category> newCategoryList(WebAnnotation annotation) { 
     808  public List<com.sun.syndication.feed.atom.Category> newCategoryList(String displayName) { 
    755809    List<com.sun.syndication.feed.atom.Category> categories = 
    756810        new ArrayList<com.sun.syndication.feed.atom.Category>(); 
    757811    com.sun.syndication.feed.atom.Category cat = new com.sun.syndication.feed.atom.Category(); 
    758812 
    759     cat.setTerm(annotation.getDisplayName()); 
    760     cat.setLabel(annotation.getDisplayName()); 
     813    cat.setTerm(displayName); 
     814    cat.setLabel(displayName); 
    761815    categories.add(cat); 
    762816 
     
    771825   * @return String identifier generated for this feed 
    772826   */ 
    773   private String newFeedID(FeedCacheKey cacheKey) { 
     827  private String newFeedID(ArticleFeedCacheKey cacheKey) { 
    774828    String id = FEED_ID(); 
    775829    if (cacheKey.getCategory() != null && cacheKey.getCategory().length() > 0) 
     
    787841   * @return String feed title. 
    788842   */ 
    789   private String newFeedTitle(FeedCacheKey cacheKey) { 
     843  private String newFeedTitle(ArticleFeedCacheKey cacheKey) { 
    790844    String feedTitle = cacheKey.getTitle(); 
    791845 
     
    870924   * @param articleXmlUtils  a set of XML transformation utilities 
    871925   */ 
     926  @Required 
    872927  public void setArticleXmlUtils(ArticleXMLUtils articleXmlUtils) { 
    873928    this.articleXmlUtils = articleXmlUtils; 
     
    875930 
    876931  /** 
    877    * @param  articleFeedService    Article Feed Service 
    878    */ 
    879   public void setArticleFeedService(ArticleFeedService articleFeedService) { 
    880     this.articleFeedService = articleFeedService; 
     932   * @param  feedService    Article Feed Service 
     933   */ 
     934  @Required 
     935  public void setFeedService(FeedService feedService) { 
     936    this.feedService = feedService; 
    881937  } 
    882938} 
  • head/ambra/webapp/src/main/resources/struts.xml

    r7569 r7613  
    427427    <default-action-ref name="pageNotFound" /> 
    428428 
    429     <action name="getFeed" class="org.topazproject.ambra.article.action.ArticleFeed"> 
    430       <result name="success" type="feed"> 
    431         <param name="feedName">wireFeed</param> 
     429    <action name="getFeed" class="org.topazproject.ambra.feed.action.FeedAction"> 
     430      <result name="success" type="feed"/> 
     431      <result name="input" type="httpheader"> 
     432        <param name="status">400</param><!-- Http status: Bad Request--> 
    432433      </result> 
    433434    </action> 
  • head/ambra/webapp/src/main/webapp/discussion/threaded_replies.ftl

    r7601 r7613  
    22  $HeadURL::                                                                            $ 
    33  $Id$ 
    4    
     4 
    55  Copyright (c) 2007-2009 by Topaz, Inc. 
    66  http://topazproject.org 
    7    
     7 
    88  Licensed under the Apache License, Version 2.0 (the "License"); 
    99  you may not use this file except in compliance with the License. 
    1010  You may obtain a copy of the License at 
    11    
     11 
    1212  http://www.apache.org/licenses/LICENSE-2.0 
    13    
     13 
    1414  Unless required by applicable law or agreed to in writing, software 
    1515  distributed under the License is distributed on an "AS IS" BASIS, 
     
    3131        <div class="hd"> 
    3232          <!-- begin response title --> 
    33           <h3>${reply.commentTitle}</h3> 
     33          <h3><a name="${reply.id}">${reply.commentTitle}</a></h3> 
    3434          <!-- end : response title --> 
    3535          <!-- begin : response poster details --> 
     
    9797      <div class="hd"> 
    9898        <!-- begin response title --> 
    99         <h3>${baseAnnotation.commentTitle}</h3> 
     99        <h3><a name="${baseAnnotation.id}">${baseAnnotation.commentTitle}</a></h3> 
    100100        <!-- end : response title --> 
    101101        <!-- begin : response poster detail --> 
  • head/ambra/webapp/src/main/webapp/WEB-INF/applicationContext.xml

    r7599 r7613  
    9494   </bean> 
    9595 
    96   <bean id="articleFeedService" class="org.topazproject.ambra.article.service.ArticleFeedService"> 
     96  <bean id="feedService" class="org.topazproject.ambra.feed.service.FeedService"> 
    9797    <property name="articleOtmService" ref="articleOtmService"/> 
    9898    <property name="annotationService" ref="annotationService"/> 
  • head/ambra/webapp/src/test/java/org/topazproject/ambra/annotation/service/AnnotationServiceTest.java

    r7331 r7613  
    5454import java.util.ArrayList; 
    5555import java.util.List; 
     56import java.util.HashSet; 
     57import java.util.Set; 
    5658import java.text.SimpleDateFormat; 
    5759import java.text.ParseException; 
     
    147149  } 
    148150 
    149    
     151 
    150152  @Test 
    151   public void getAnnotations() throws ParseException, URISyntaxException { 
     153  public void getAnnotationIds() throws ParseException, URISyntaxException { 
    152154 
    153155    IMocksControl ctl = createControl(); 
     
    155157    PDP pdp = ctl.createMock(PDP.class); 
    156158    Session session = ctl.createMock(Session.class); 
    157     Query query = ctl.createMock(Query.class); 
    158     Results results = ctl.createMock(Results.class); 
     159    Query annQuery = ctl.createMock(Query.class); 
     160    Results annResults = ctl.createMock(Results.class); 
    159161    CacheManager cacheManager = ctl.createMock(CacheManager.class); 
    160162    MockCache cache = new MockCache(); 
     
    164166    Date startDate = dateFormat.parse("02/01/2009"); 
    165167    Date endDate = dateFormat.parse("03/10/2009"); 
    166     int[] states = {0, 1}; 
    167     List<String> annotType = new ArrayList<String>(); 
     168    Set<String> annotType = new HashSet<String>(); 
    168169    annotType.add("FormalCorrection"); 
    169170    annotType.add("MinorCorrection"); 
    170171 
    171     String articleId = "info:doi/10.1371/journal.pone.0002250"; 
    172     String queryString = "select a.id id, cr from Annotation a " + 
    173         "where cr := a.created and a.annotates = :targ and a.mediator = :med and ge(cr, :sd) " + 
    174         "and le(cr, :ed) and (art.state = :st0 or art.state = :st1) " + 
     172    String queryString = "select a.id id, cr from ArticleAnnotation a, Article ar " + 
     173        "where a.annotates = ar " + 
     174        "and cr := a.created " + 
     175        "and ge(cr, :sd) " + 
     176        "and le(cr, :ed) " + 
    175177        "and (a.<rdf:type> = :type0 or a.<rdf:type> = :type1) " + 
    176         "order by cr asc, id asc limit 20;"; 
     178        "order by cr asc, id asc limit 3;"; 
     179 
    177180    String applicationId = "test-app"; 
     181 
    178182    URI annotation1id = URI.create("info:doi/10.1371/annotation1"); 
    179183    URI annotation2id = URI.create("info:doi/10.1371/annotation2"); 
    180184    URI annotation3id = URI.create("info:doi/10.1371/annotation3"); 
    181185 
    182     Comment comment = new Comment(); 
    183     comment.setId(annotation1id); 
    184     FormalCorrection formalCorrection = new FormalCorrection(); 
    185     formalCorrection.setId(annotation2id); 
    186     MinorCorrection minorCorrection = new MinorCorrection(); 
    187     minorCorrection.setId(annotation3id); 
    188  
    189     List<ArticleAnnotation> expected = new ArrayList<ArticleAnnotation>(); 
    190     expected.add(comment); 
    191     expected.add(formalCorrection); 
    192     expected.add(minorCorrection); 
     186    List<String> expected = new ArrayList<String>(); 
     187    expected.add(annotation1id.toString()); 
     188    expected.add(annotation2id.toString()); 
     189    expected.add(annotation3id.toString()); 
    193190 
    194191    cacheManager.registerListener(isA(AbstractObjectListener.class)); 
     
    199196        .anyTimes(); 
    200197 
     198    // Run Annotation Query 
    201199    expect(session.createQuery(queryString)) 
    202         .andReturn(query); 
    203     expect(query.setParameter("targ", articleId)) 
    204         .andReturn(query); 
    205     expect(query.setParameter("med", applicationId)) 
    206         .andReturn(query); 
    207     expect(query.setParameter("sd", startDate)) 
    208         .andReturn(query); 
    209     expect(query.setParameter("ed", endDate)) 
    210         .andReturn(query); 
    211  
    212     for (int i = 0; i < states.length; i++) { 
    213       expect(query.setParameter("st"+i, states[i])) 
    214           .andReturn(query); 
     200        .andReturn(annQuery); 
     201    expect(annQuery.setParameter("sd", startDate)) 
     202        .andReturn(annQuery); 
     203    expect(annQuery.setParameter("ed", endDate)) 
     204        .andReturn(annQuery); 
     205 
     206    int i = 0; 
     207    for (String type : annotType) { 
     208      expect(annQuery.setUri("type"+i++, URI.create(type))) 
     209          .andReturn(annQuery); 
    215210    } 
    216211 
    217     for (int i = 0; i < annotType.size(); i++) { 
    218       expect(query.setUri("type"+i, URI.create(annotType.get(i)))) 
    219           .andReturn(query); 
    220     } 
    221  
    222     expect(query.execute()) 
    223         .andReturn(results); 
    224  
    225     expect(results.next()) 
     212    expect(annQuery.execute()) 
     213        .andReturn(annResults); 
     214 
     215    expect(annResults.next()) 
    226216        .andReturn(true).times(3) 
    227217        .andReturn(false); 
    228     expect(results.getURI(0)) 
     218    expect(annResults.getURI(0)) 
    229219        .andReturn(annotation1id) 
    230220        .andReturn(annotation2id) 
    231221        .andReturn(annotation3id); 
    232      
    233     expect(session.get(ArticleAnnotation.class, annotation1id.toString())) 
    234         .andReturn(comment); 
    235     expect(session.get(ArticleAnnotation.class, annotation2id.toString())) 
    236         .andReturn(formalCorrection); 
    237     expect(session.get(ArticleAnnotation.class, annotation3id.toString())) 
    238         .andReturn(minorCorrection); 
    239      
     222 
    240223    ctl.replay(); 
    241224 
     
    251234 
    252235 
    253     List<ArticleAnnotation> annotations = annotationService.getAnnotations( 
    254         articleId, startDate, endDate, applicationId, annotType, states, true, 20); 
    255  
    256     assertEquals(annotations, expected); 
     236    List<String> annotationIds = annotationService.getAnnotationIds(startDate, endDate, 
     237        new HashSet<String>(annotType), 3); 
     238 
     239    assertEquals(annotationIds, expected); 
    257240 
    258241    ctl.verify();