mirror of
https://github.com/Athou/commafeed.git
synced 2026-03-21 21:37:29 +00:00
initial commit
This commit is contained in:
74
src/main/java/com/commafeed/backend/StartupBean.java
Normal file
74
src/main/java/com/commafeed/backend/StartupBean.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package com.commafeed.backend;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.ejb.Singleton;
|
||||
import javax.ejb.Startup;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.commafeed.backend.dao.FeedCategoryService;
|
||||
import com.commafeed.backend.dao.FeedService;
|
||||
import com.commafeed.backend.dao.FeedSubscriptionService;
|
||||
import com.commafeed.backend.dao.UserService;
|
||||
import com.commafeed.backend.security.PasswordEncryptionService;
|
||||
import com.commafeed.model.Feed;
|
||||
import com.commafeed.model.FeedCategory;
|
||||
import com.commafeed.model.FeedSubscription;
|
||||
import com.commafeed.model.User;
|
||||
|
||||
@Startup
|
||||
@Singleton
|
||||
public class StartupBean {
|
||||
|
||||
@Inject
|
||||
FeedService feedService;
|
||||
|
||||
@Inject
|
||||
FeedCategoryService feedCategoryService;
|
||||
|
||||
@Inject
|
||||
FeedSubscriptionService feedSubscriptionService;
|
||||
|
||||
@Inject
|
||||
UserService userService;
|
||||
|
||||
@Inject
|
||||
PasswordEncryptionService encryptionService;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
|
||||
if (userService.getCount() == 0) {
|
||||
User user = new User();
|
||||
byte[] salt = encryptionService.generateSalt();
|
||||
user.setName("admin");
|
||||
user.setSalt(salt);
|
||||
user.setPassword(encryptionService.getEncryptedPassword("admin",
|
||||
salt));
|
||||
userService.save(user);
|
||||
|
||||
Feed feed = new Feed("http://feed.dilbert.com/dilbert/daily_strip");
|
||||
feedService.save(feed);
|
||||
|
||||
FeedCategory newsCategory = new FeedCategory();
|
||||
newsCategory.setName("News");
|
||||
newsCategory.setUser(user);
|
||||
feedCategoryService.save(newsCategory);
|
||||
|
||||
FeedCategory comicsCategory = new FeedCategory();
|
||||
comicsCategory.setName("Comics");
|
||||
comicsCategory.setUser(user);
|
||||
comicsCategory.setParent(newsCategory);
|
||||
feedCategoryService.save(comicsCategory);
|
||||
|
||||
FeedSubscription sub = new FeedSubscription();
|
||||
sub.setCategory(comicsCategory);
|
||||
sub.setFeed(feed);
|
||||
sub.setTitle("Dilbert - Strips");
|
||||
sub.setUser(user);
|
||||
feedSubscriptionService.save(sub);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.ejb.Stateless;
|
||||
|
||||
import com.commafeed.frontend.utils.ModelFactory.MF;
|
||||
import com.commafeed.model.FeedCategory;
|
||||
import com.commafeed.model.User;
|
||||
|
||||
@Stateless
|
||||
public class FeedCategoryService extends GenericDAO<FeedCategory, Long> {
|
||||
|
||||
public List<FeedCategory> findAll(User user) {
|
||||
return findByField(MF.i(MF.p(FeedCategory.class).getUser()), user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.ejb.Stateless;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.commafeed.model.Feed;
|
||||
import com.commafeed.model.FeedEntry;
|
||||
|
||||
@Stateless
|
||||
public class FeedEntryService extends GenericDAO<FeedEntry, String> {
|
||||
|
||||
@Inject
|
||||
FeedService feedService;
|
||||
|
||||
public void updateEntries(String url, Collection<FeedEntry> entries) {
|
||||
Feed feed = feedService.findById(url);
|
||||
for (FeedEntry entry : entries) {
|
||||
FeedEntry existing = findById(entry.getGuid());
|
||||
if (existing == null) {
|
||||
entry.setFeed(feed);
|
||||
save(entry);
|
||||
}
|
||||
}
|
||||
feed.setLastUpdated(Calendar.getInstance().getTime());
|
||||
em.merge(feed);
|
||||
}
|
||||
|
||||
}
|
||||
10
src/main/java/com/commafeed/backend/dao/FeedService.java
Normal file
10
src/main/java/com/commafeed/backend/dao/FeedService.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import javax.ejb.Stateless;
|
||||
|
||||
import com.commafeed.model.Feed;
|
||||
|
||||
@Stateless
|
||||
public class FeedService extends GenericDAO<Feed, String> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.ejb.Stateless;
|
||||
|
||||
import com.commafeed.frontend.utils.ModelFactory.MF;
|
||||
import com.commafeed.model.FeedCategory;
|
||||
import com.commafeed.model.FeedSubscription;
|
||||
import com.commafeed.model.User;
|
||||
|
||||
@Stateless
|
||||
public class FeedSubscriptionService extends GenericDAO<FeedSubscription, Long> {
|
||||
|
||||
public List<FeedSubscription> findAll(User user) {
|
||||
return findByField(MF.i(MF.p(FeedCategory.class).getUser()), user);
|
||||
}
|
||||
}
|
||||
89
src/main/java/com/commafeed/backend/dao/GenericDAO.java
Normal file
89
src/main/java/com/commafeed/backend/dao/GenericDAO.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.uaihebert.factory.EasyCriteriaFactory;
|
||||
import com.uaihebert.model.EasyCriteria;
|
||||
|
||||
public abstract class GenericDAO<T, K> {
|
||||
|
||||
private TypeToken<T> type = new TypeToken<T>(getClass()) {
|
||||
};
|
||||
|
||||
@PersistenceContext
|
||||
protected EntityManager em;
|
||||
|
||||
protected CriteriaBuilder builder;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
builder = em.getCriteriaBuilder();
|
||||
}
|
||||
|
||||
public void save(T object) {
|
||||
em.persist(object);
|
||||
}
|
||||
|
||||
public void update(T... objects) {
|
||||
for (Object object : objects) {
|
||||
em.merge(object);
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(T object) {
|
||||
object = em.merge(object);
|
||||
em.remove(object);
|
||||
}
|
||||
|
||||
public void deleteById(Object id) {
|
||||
Object ref = em.getReference(getType(), id);
|
||||
em.remove(ref);
|
||||
}
|
||||
|
||||
public T findById(K id) {
|
||||
T t = em.find(getType(), id);
|
||||
return t;
|
||||
}
|
||||
|
||||
public List<T> findAll() {
|
||||
return EasyCriteriaFactory.createQueryCriteria(em, getType())
|
||||
.getResultList();
|
||||
}
|
||||
|
||||
public List<T> findAll(int startIndex, int count) {
|
||||
EasyCriteria<T> criteria = EasyCriteriaFactory.createQueryCriteria(em,
|
||||
getType());
|
||||
criteria.setMaxResults(count);
|
||||
criteria.setFirstResult(startIndex);
|
||||
return criteria.getResultList();
|
||||
}
|
||||
|
||||
public long getCount() {
|
||||
CriteriaBuilder builder = em.getCriteriaBuilder();
|
||||
CriteriaQuery<Long> query = builder.createQuery(Long.class);
|
||||
Root<T> root = query.from(getType());
|
||||
query.select(builder.count(root));
|
||||
return em.createQuery(query).getSingleResult();
|
||||
}
|
||||
|
||||
public List<T> findByField(String field, Object value) {
|
||||
EasyCriteria<T> criteria = EasyCriteriaFactory.createQueryCriteria(em,
|
||||
getType());
|
||||
criteria.andEquals(field, value);
|
||||
return criteria.getResultList();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Class<T> getType() {
|
||||
return (Class<T>) type.getRawType();
|
||||
}
|
||||
|
||||
}
|
||||
30
src/main/java/com/commafeed/backend/dao/UserService.java
Normal file
30
src/main/java/com/commafeed/backend/dao/UserService.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package com.commafeed.backend.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.commafeed.backend.security.PasswordEncryptionService;
|
||||
import com.commafeed.frontend.utils.ModelFactory.MF;
|
||||
import com.commafeed.model.User;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
public class UserService extends GenericDAO<User, Long> {
|
||||
|
||||
@Inject
|
||||
PasswordEncryptionService encryptionService;
|
||||
|
||||
public User login(String name, String password) {
|
||||
List<User> users = findByField(MF.i(MF.p(User.class).getName()), name);
|
||||
User user = Iterables.getFirst(users, null);
|
||||
if (user != null) {
|
||||
boolean authenticated = encryptionService.authenticate(password,
|
||||
user.getPassword(), user.getSalt());
|
||||
if (authenticated) {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
53
src/main/java/com/commafeed/backend/feeds/FeedFetcher.java
Normal file
53
src/main/java/com/commafeed/backend/feeds/FeedFetcher.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package com.commafeed.backend.feeds;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.ejb.AccessTimeout;
|
||||
import javax.ejb.AsyncResult;
|
||||
import javax.ejb.Asynchronous;
|
||||
import javax.ejb.Lock;
|
||||
import javax.ejb.LockType;
|
||||
import javax.ejb.Singleton;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.ResponseHandler;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.BasicResponseHandler;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.commafeed.model.Feed;
|
||||
|
||||
@Singleton
|
||||
public class FeedFetcher {
|
||||
|
||||
private static Logger log = LoggerFactory.getLogger(FeedFetcher.class);
|
||||
|
||||
@Inject
|
||||
FeedParser parser;
|
||||
|
||||
@Asynchronous
|
||||
@Lock(LockType.READ)
|
||||
@AccessTimeout(value = 15, unit = TimeUnit.SECONDS)
|
||||
public Future<Feed> fetch(String feedUrl) {
|
||||
log.debug("Fetching feed {}", feedUrl);
|
||||
Feed feed = null;
|
||||
|
||||
HttpClient httpclient = new DefaultHttpClient();
|
||||
try {
|
||||
HttpGet httpget = new HttpGet(feedUrl);
|
||||
ResponseHandler<String> responseHandler = new BasicResponseHandler();
|
||||
String responseBody = httpclient.execute(httpget, responseHandler);
|
||||
feed = parser.parse(feedUrl, responseBody);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
httpclient.getConnectionManager().shutdown();
|
||||
}
|
||||
return new AsyncResult<Feed>(feed);
|
||||
}
|
||||
|
||||
}
|
||||
49
src/main/java/com/commafeed/backend/feeds/FeedParser.java
Normal file
49
src/main/java/com/commafeed/backend/feeds/FeedParser.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package com.commafeed.backend.feeds;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ejb.Stateless;
|
||||
|
||||
import com.commafeed.model.Feed;
|
||||
import com.commafeed.model.FeedEntry;
|
||||
import com.sun.syndication.feed.synd.SyndEntry;
|
||||
import com.sun.syndication.feed.synd.SyndFeed;
|
||||
import com.sun.syndication.io.FeedException;
|
||||
import com.sun.syndication.io.SyndFeedInput;
|
||||
|
||||
@Stateless
|
||||
public class FeedParser {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Feed parse(String feedUrl, String xml) throws FeedException {
|
||||
|
||||
Feed feed = new Feed();
|
||||
feed.setUrl(feedUrl);
|
||||
feed.setLastUpdated(Calendar.getInstance().getTime());
|
||||
|
||||
try {
|
||||
SyndFeed rss = new SyndFeedInput().build(new StringReader(xml));
|
||||
|
||||
List<SyndEntry> items = rss.getEntries();
|
||||
for (SyndEntry item : items) {
|
||||
FeedEntry entry = new FeedEntry();
|
||||
entry.setGuid(item.getUri());
|
||||
entry.setTitle(item.getTitle());
|
||||
entry.setContent(item.getDescription() == null ? null : item
|
||||
.getDescription().getValue());
|
||||
entry.setUrl(item.getLink());
|
||||
entry.setUpdated(item.getUpdatedDate() != null ? item
|
||||
.getUpdatedDate() : item.getPublishedDate());
|
||||
|
||||
feed.getEntries().add(entry);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new FeedException("Could not parse feed : " + e.getMessage(),
|
||||
e);
|
||||
}
|
||||
return feed;
|
||||
}
|
||||
|
||||
}
|
||||
48
src/main/java/com/commafeed/backend/feeds/FeedTimer.java
Normal file
48
src/main/java/com/commafeed/backend/feeds/FeedTimer.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package com.commafeed.backend.feeds;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javax.ejb.Schedule;
|
||||
import javax.ejb.Singleton;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.commafeed.backend.dao.FeedEntryService;
|
||||
import com.commafeed.backend.dao.FeedService;
|
||||
import com.commafeed.model.Feed;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
@Singleton
|
||||
public class FeedTimer {
|
||||
|
||||
@Inject
|
||||
FeedService feedService;
|
||||
|
||||
@Inject
|
||||
FeedEntryService feedEntryService;
|
||||
|
||||
@Inject
|
||||
FeedFetcher fetcher;
|
||||
|
||||
@Schedule(hour = "*", minute = "*", persistent = false)
|
||||
private void timeout() {
|
||||
List<Feed> feeds = feedService.findAll();
|
||||
|
||||
List<Future<Feed>> futures = Lists.newArrayList();
|
||||
for (Feed feed : feeds) {
|
||||
Future<Feed> future = fetcher.fetch(feed.getUrl());
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
for (Future<Feed> future : futures) {
|
||||
try {
|
||||
Feed feed = future.get();
|
||||
feedEntryService
|
||||
.updateEntries(feed.getUrl(), feed.getEntries());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.commafeed.backend.security;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.ejb.Singleton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.commafeed.backend.dao.UserService;
|
||||
|
||||
// http://www.javacodegeeks.com/2012/05/secure-password-storage-donts-dos-and.html
|
||||
@Singleton
|
||||
public class PasswordEncryptionService {
|
||||
|
||||
private static final Logger log = LoggerFactory
|
||||
.getLogger(UserService.class);
|
||||
|
||||
public boolean authenticate(String attemptedPassword,
|
||||
byte[] encryptedPassword, byte[] salt) {
|
||||
// Encrypt the clear-text password using the same salt that was used to
|
||||
// encrypt the original password
|
||||
byte[] encryptedAttemptedPassword = null;
|
||||
try {
|
||||
encryptedAttemptedPassword = getEncryptedPassword(
|
||||
attemptedPassword, salt);
|
||||
} catch (Exception e) {
|
||||
// should never happen
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
// Authentication succeeds if encrypted password that the user entered
|
||||
// is equal to the stored hash
|
||||
return Arrays.equals(encryptedPassword, encryptedAttemptedPassword);
|
||||
}
|
||||
|
||||
public byte[] getEncryptedPassword(String password, byte[] salt) {
|
||||
// PBKDF2 with SHA-1 as the hashing algorithm. Note that the NIST
|
||||
// specifically names SHA-1 as an acceptable hashing algorithm for
|
||||
// PBKDF2
|
||||
String algorithm = "PBKDF2WithHmacSHA1";
|
||||
// SHA-1 generates 160 bit hashes, so that's what makes sense here
|
||||
int derivedKeyLength = 160;
|
||||
// Pick an iteration count that works for you. The NIST recommends at
|
||||
// least 1,000 iterations:
|
||||
// http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
|
||||
// iOS 4.x reportedly uses 10,000:
|
||||
// http://blog.crackpassword.com/2010/09/smartphone-forensics-cracking-blackberry-backup-passwords/
|
||||
int iterations = 20000;
|
||||
|
||||
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations,
|
||||
derivedKeyLength);
|
||||
|
||||
SecretKey key = null;
|
||||
try {
|
||||
SecretKeyFactory f = SecretKeyFactory.getInstance(algorithm);
|
||||
key = f.generateSecret(spec);
|
||||
} catch (Exception e) {
|
||||
// should never happen
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
return key.getEncoded();
|
||||
}
|
||||
|
||||
public byte[] generateSalt() {
|
||||
// VERY important to use SecureRandom instead of just Random
|
||||
SecureRandom random = null;
|
||||
try {
|
||||
random = SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// should never happen
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
// Generate a 8 byte (64 bit) salt as recommended by RSA PKCS5
|
||||
byte[] salt = new byte[8];
|
||||
random.nextBytes(salt);
|
||||
|
||||
return salt;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user