private void loadYouTubeThumbnails(Viewpoint viewpoint, ExternalAccountView accountView) {
    ExternalAccount account = accountView.getExternalAccount();

    if (account.getAccountType() != ExternalAccountType.YOUTUBE)
      throw new IllegalArgumentException("should be a YouTube account here");

    if (account.getSentiment() != Sentiment.LOVE)
      throw new IllegalArgumentException("YouTube account is unloved =(");

    if (account.getHandle() == null) return;

    try {
      youTubeUpdater.getCachedStatus(account);
    } catch (NotFoundException e) {
      logger.debug("No cached YouTube status for {}", account);
      return;
    }

    List<? extends YouTubeVideo> videos = youTubeVideosCache.getSync(account.getHandle());
    if (videos.isEmpty()) {
      logger.debug("Empty list of videos for {}", account);
      return;
    }

    accountView.setThumbnailsData(
        TypeUtils.castList(Thumbnail.class, videos),
        videos.size(),
        videos.get(0).getThumbnailWidth(),
        videos.get(0).getThumbnailHeight());
  }
  public List<FacebookPhotoDataView> queryExisting(
      String key, Set<FacebookPhotoDataStatus> photos) {
    List<FacebookPhotoDataView> results = new ArrayList<FacebookPhotoDataView>();
    StringBuilder sb = new StringBuilder();
    sb.append(
        "SELECT photo FROM CachedFacebookPhotoData photo WHERE photo.userId = :userId"
            + " AND photo.facebookPhotoId IN (");
    boolean facebookPhotoIdsAvailable = false;
    for (FacebookPhotoDataStatus photoStatus : photos) {
      if (photoStatus.getFacebookPhotoId() != null) {
        sb.append(photoStatus.getFacebookPhotoId());
        sb.append(",");
        facebookPhotoIdsAvailable = true;
      }
    }

    // no need to do the query if we don't have a single id
    if (!facebookPhotoIdsAvailable) {
      return results;
    }

    sb.setLength(sb.length() - 1); // chop comma
    sb.append(")");
    Query q = em.createQuery(sb.toString());
    q.setParameter("userId", key);
    List<CachedFacebookPhotoData> entities =
        TypeUtils.castList(CachedFacebookPhotoData.class, q.getResultList());
    for (CachedFacebookPhotoData entity : entities) {
      results.add(resultFromEntity(entity));
    }
    return results;
  }
 @Override
 public List<CachedFacebookPhotoData> queryExisting(String key) {
   Query q =
       em.createQuery(
           "SELECT photo FROM CachedFacebookPhotoData photo WHERE photo.userId = :userId");
   q.setParameter("userId", key);
   List<CachedFacebookPhotoData> results =
       TypeUtils.castList(CachedFacebookPhotoData.class, q.getResultList());
   return results;
 }
  @Override
  protected List<FacebookPhotoDataView> fetchFromNetImpl(String key) {
    FacebookWebServices ws = new FacebookWebServices(REQUEST_TIMEOUT, config);
    FacebookAccount facebookAccount = lookupFacebookAccount(key);
    // this facebookAccount is detached
    List<FacebookPhotoData> photos = ws.getTaggedPhotos(facebookAccount);

    // we don't want to call facebookTracker.handleExpiredSessionKey(facebookAccount.getId());
    // here because we'll handle that when we are updating tagged photos and we also
    // need to drop the photos cache for the user in that case, which we do there
    if (photos == null) return null;
    else return TypeUtils.castList(FacebookPhotoDataView.class, photos);
  }
  private void loadFlickrThumbnails(Viewpoint viewpoint, ExternalAccountView accountView) {
    ExternalAccount account = accountView.getExternalAccount();

    if (account.getAccountType() != ExternalAccountType.FLICKR)
      throw new IllegalArgumentException("should be a flickr account here");

    if (account.getSentiment() != Sentiment.LOVE)
      throw new IllegalArgumentException("Flickr account is unloved");

    if (account.getHandle() == null) return;

    FlickrPhotosView photos = flickrUserPhotosCache.getSync(account.getHandle());
    if (photos == null) {
      logger.debug("No public photos for {}", account);
      return;
    }

    accountView.setThumbnailsData(
        TypeUtils.castList(Thumbnail.class, photos.getPhotos()),
        photos.getTotal(),
        FlickrPhotoSize.SMALL_SQUARE.getPixels(),
        FlickrPhotoSize.SMALL_SQUARE.getPixels());
  }
  public void validateAll() {
    logger.info("Administrator kicked off validation of all ExternalAccount rows in the database");
    Query q =
        em.createQuery("SELECT ea.id, ea.accountType, ea.handle, ea.extra FROM ExternalAccount ea");
    List<Object[]> results = TypeUtils.castList(Object[].class, q.getResultList());

    logger.info("There are {} ExternalAccount rows to validate", results.size());

    List<Long> invalidHandles = new ArrayList<Long>();
    List<Long> invalidExtras = new ArrayList<Long>();

    for (Object[] row : results) {
      // logger.debug("row is: {}", Arrays.toString(row));
      Long id = (Long) row[0];
      ExternalAccountType accountType = (ExternalAccountType) row[1];
      String handle = (String) row[2];
      String extra = (String) row[3];
      if (accountType == null) {
        logger.info("Row has null accountType: {}", Arrays.toString(row));
      } else {
        try {
          String c = accountType.canonicalizeHandle(handle);
          if (handle != null && !c.equals(handle)) {
            logger.info(
                "Row is not canonicalized: {}: '{}' vs. '{}'",
                new Object[] {Arrays.toString(row), handle, c});
          }
        } catch (ValidationException e) {
          logger.info("Row had invalid 'handle': {}: {}", Arrays.toString(row), e.getMessage());
          invalidHandles.add(id);
        }
        try {
          String c = accountType.canonicalizeExtra(extra);
          if (extra != null && !c.equals(extra)) {
            logger.info(
                "Row is not canonicalized: {}: '{}' vs. '{}'",
                new Object[] {Arrays.toString(row), extra, c});
          }
        } catch (ValidationException e) {
          logger.info("Row had invalid 'extra': {}: {}", Arrays.toString(row), e.getMessage());
          invalidExtras.add(id);
        }
      }
    }

    if (!invalidHandles.isEmpty()) {
      StringBuilder sb = new StringBuilder();
      sb.append("UPDATE ExternalAccount SET handle = NULL WHERE id IN (");
      for (Long id : invalidHandles) {
        sb.append(id);
        sb.append(",");
      }
      sb.setLength(sb.length() - 1); // chop comma
      sb.append(");");
      logger.info("Possible query to null invalid handles: {}", sb);
    }

    if (!invalidExtras.isEmpty()) {
      StringBuilder sb = new StringBuilder();
      sb.append("UPDATE ExternalAccount SET extra = NULL WHERE id IN (");
      for (Long id : invalidHandles) {
        sb.append(id);
        sb.append(",");
      }
      sb.setLength(sb.length() - 1); // chop comma
      sb.append(");");
      logger.info("Possible query to null invalid extras: {}", sb);
    }

    logger.info(
        "ExternalAccount validation complete, {} invalid handles {} invalid extras",
        invalidHandles.size(),
        invalidExtras.size());
  }