initial commit

This commit is contained in:
Athou
2013-03-20 20:33:42 +01:00
commit 7b3c53fcb9
82 changed files with 3346 additions and 0 deletions

0
src/main/java/.gitkeep Normal file
View File

View 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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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> {
}

View File

@@ -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);
}
}

View 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();
}
}

View 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;
}
}

View 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);
}
}

View 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;
}
}

View 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();
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,116 @@
package com.commafeed.frontend;
import javax.enterprise.inject.spi.BeanManager;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.apache.wicket.Application;
import org.apache.wicket.Page;
import org.apache.wicket.Session;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.authroles.authentication.AbstractAuthenticatedWebSession;
import org.apache.wicket.authroles.authentication.AuthenticatedWebApplication;
import org.apache.wicket.authroles.authorization.strategies.role.IRoleCheckingStrategy;
import org.apache.wicket.authroles.authorization.strategies.role.Roles;
import org.apache.wicket.authroles.authorization.strategies.role.annotations.AnnotationsRoleAuthorizationStrategy;
import org.apache.wicket.cdi.CdiConfiguration;
import org.apache.wicket.cdi.ConversationPropagation;
import org.apache.wicket.core.request.handler.PageProvider;
import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
import org.apache.wicket.core.request.handler.RenderPageRequestHandler.RedirectPolicy;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Response;
import org.apache.wicket.request.cycle.AbstractRequestCycleListener;
import org.apache.wicket.request.cycle.RequestCycle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commafeed.frontend.components.auth.LoginPage;
import com.commafeed.frontend.pages.feed.FeedViewPage;
import com.commafeed.frontend.utils.exception.DisplayExceptionPage;
import de.agilecoders.wicket.Bootstrap;
import de.agilecoders.wicket.settings.BootstrapSettings;
import de.agilecoders.wicket.webjars.util.WicketWebjars;
public class CommaFeedApplication extends AuthenticatedWebApplication {
private Logger log = LoggerFactory.getLogger(CommaFeedApplication.class);
@Override
protected void init() {
super.init();
mountPage("login", LoginPage.class);
mountPage("feeds", FeedViewPage.class);
setupInjection();
getMarkupSettings().setStripWicketTags(true);
getMarkupSettings().setCompressWhitespace(true);
getMarkupSettings().setDefaultMarkupEncoding("UTF-8");
getSecuritySettings().setAuthorizationStrategy(
new AnnotationsRoleAuthorizationStrategy(
new IRoleCheckingStrategy() {
@Override
public boolean hasAnyRole(Roles roles) {
return CommaFeedSession.get().getRoles()
.hasAnyRole(roles);
}
}));
getRequestCycleListeners().add(new AbstractRequestCycleListener() {
@Override
public IRequestHandler onException(RequestCycle cycle, Exception ex) {
AjaxRequestTarget target = cycle.find(AjaxRequestTarget.class);
// redirect to the error page if ajax request, render error on
// current page otherwise
RedirectPolicy policy = target == null ? RedirectPolicy.NEVER_REDIRECT
: RedirectPolicy.AUTO_REDIRECT;
return new RenderPageRequestHandler(new PageProvider(
new DisplayExceptionPage(ex)), policy);
}
});
mountPage("/error", DisplayExceptionPage.class);
BootstrapSettings settings = new BootstrapSettings();
Bootstrap.install(Application.get(), settings);
WicketWebjars.install(this);
}
@Override
public Class<? extends Page> getHomePage() {
return FeedViewPage.class;
}
protected void setupInjection() {
try {
BeanManager beanManager = (BeanManager) new InitialContext()
.lookup("java:comp/BeanManager");
new CdiConfiguration(beanManager).setPropagation(
ConversationPropagation.NONE).configure(this);
} catch (NamingException e) {
log.warn("Could not locate bean manager. CDI is disabled.");
}
}
@Override
public Session newSession(Request request, Response response) {
return new CommaFeedSession(request);
}
@Override
protected Class<? extends WebPage> getSignInPageClass() {
return LoginPage.class;
}
@Override
protected Class<? extends AbstractAuthenticatedWebSession> getWebSessionClass() {
return CommaFeedSession.class;
}
}

View File

@@ -0,0 +1,51 @@
package com.commafeed.frontend;
import javax.inject.Inject;
import org.apache.wicket.Session;
import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
import org.apache.wicket.authroles.authorization.strategies.role.Roles;
import org.apache.wicket.request.Request;
import com.commafeed.backend.dao.UserService;
import com.commafeed.frontend.components.auth.Role;
import com.commafeed.model.User;
public class CommaFeedSession extends AuthenticatedWebSession {
@Inject
UserService userService;
private User user;
public CommaFeedSession(Request request) {
super(request);
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public static CommaFeedSession get() {
return (CommaFeedSession) Session.get();
}
@Override
public Roles getRoles() {
// TODO change this
return isSignedIn() ? new Roles(new String[] { Role.USER, Role.ADMIN })
: new Roles();
}
@Override
public boolean authenticate(String userName, String password) {
User user = userService.login(userName, password);
setUser(user);
return user != null;
}
}

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<body>
<wicket:panel>
<div class="css-treeview">
<ul>
<li wicket:id="tree"></li>
</ul>
</div>
<wicket:fragment wicket:id="node">
<input type="checkbox" wicket:id="checkbox" />
<label wicket:id="label">
<span wicket:id="content"></span>
</label>
<ul>
<li wicket:id="node"></li>
</ul>
</wicket:fragment>
<wicket:fragment wicket:id="leaf">
<a wicket:id="link"></a>
</wicket:fragment>
</wicket:panel>
</body>
</html>

View File

@@ -0,0 +1,82 @@
package com.commafeed.frontend.components;
import java.util.Collection;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.FormComponentLabel;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.model.IDetachable;
import org.apache.wicket.model.IModel;
import com.commafeed.frontend.references.csstree.CssTreeViewReference;
public abstract class CssTreeView<T, V> extends Panel {
private ITreeProvider<T, V> provider;
public CssTreeView(String id, final ITreeProvider<T, V> provider) {
super(id);
setRenderBodyOnly(true);
this.provider = provider;
RepeatingView view = new RepeatingView("tree");
addNode(null, view);
add(view);
}
private void addNode(T node, RepeatingView view) {
for (T child : provider.getChildren(node)) {
Fragment fragment = new Fragment(view.newChildId(), "node",
CssTreeView.this);
CheckBox checkBox = new CheckBox("checkbox");
checkBox.add(new AttributeModifier("checked", "checked"));
fragment.add(checkBox);
fragment.add(new FormComponentLabel("label", checkBox)
.add(new Label("content", provider.getChildLabel(child))
.setRenderBodyOnly(true)));
RepeatingView viewChild = new RepeatingView("node");
addNode(child, viewChild);
fragment.add(viewChild);
view.add(fragment);
}
for (V leaf : provider.getLeaves(node)) {
Fragment fragment = new Fragment(view.newChildId(), "leaf",
CssTreeView.this);
fragment.add(newLink("link", provider.model(leaf)));
view.add(fragment);
}
}
@Override
protected void onDetach() {
super.onDetach();
provider.detach();
}
protected abstract Component newLink(String markupId, IModel<V> model);
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
CssTreeViewReference.render(response);
}
public static interface ITreeProvider<T, V> extends IDetachable {
Collection<T> getChildren(T node);
IModel<String> getChildLabel(T node);
Collection<V> getLeaves(T node);
IModel<V> model(V object);
}
}

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
<meta charset="utf-8">
</head>
<body>
<div class="container">
<div class="page-header">
<h1>Login</h1>
</div>
<div class="row">
<span wicket:id="login"></span>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,21 @@
package com.commafeed.frontend.components.auth;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.WebPage;
import de.agilecoders.wicket.Bootstrap;
@SuppressWarnings("serial")
public class LoginPage extends WebPage {
public LoginPage() {
add(new LoginPanel("login"));
}
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
Bootstrap.renderHead(response);
}
}

View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<body>
<wicket:panel>
<span wicket:id="feedback"></span>
<form wicket:id="signInForm">
<div class="control-group">
<label class="control-label" for="username">User Name</label>
<div class="controls">
<input type="text" id="username" wicket:id="username"></input>
</div>
</div>
<div class="control-group">
<label class="control-label" for="password">Password</label>
<div class="controls">
<input type="password" id="password" wicket:id="password"></input>
</div>
</div>
<p class="help-block" wicket:id="rememberMeRow">
<input wicket:id="rememberMe" type="checkbox" /> Remember me
</p>
<div>
<input type="submit" class="btn btn-primary" value="Log in" />
</div>
</form>
</wicket:panel>
</body>
</html>

View File

@@ -0,0 +1,12 @@
package com.commafeed.frontend.components.auth;
import org.apache.wicket.authroles.authentication.panel.SignInPanel;
@SuppressWarnings("serial")
public class LoginPanel extends SignInPanel {
public LoginPanel(String id) {
super(id);
}
}

View File

@@ -0,0 +1,6 @@
package com.commafeed.frontend.components.auth;
public class Role {
public static final String USER = "user";
public static final String ADMIN = "admin";
}

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<head>
<meta charset="utf-8">
</head>
<body>
<wicket:child />
</body>
</html>

View File

@@ -0,0 +1,19 @@
package com.commafeed.frontend.pages;
import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.WebPage;
import com.commafeed.frontend.components.auth.Role;
import de.agilecoders.wicket.Bootstrap;
@AuthorizeInstantiation(Role.USER)
public abstract class BasePage extends WebPage {
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
Bootstrap.renderHead(response);
}
}

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">
<body>
<wicket:extend>
<div class="container-fluid">
<div class="row-fluid">
<div class="span2">
<div wicket:id="tree"></div>
</div>
<div class="span10">
<div wicket:id="entries"></div>
</div>
</div>
</div>
</wicket:extend>
</body>
</html>

View File

@@ -0,0 +1,115 @@
package com.commafeed.frontend.pages.feed;
import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import com.commafeed.backend.dao.FeedCategoryService;
import com.commafeed.backend.dao.FeedSubscriptionService;
import com.commafeed.frontend.CommaFeedSession;
import com.commafeed.frontend.components.CssTreeView;
import com.commafeed.frontend.components.CssTreeView.ITreeProvider;
import com.commafeed.frontend.pages.BasePage;
import com.commafeed.frontend.utils.ModelFactory.MF;
import com.commafeed.frontend.utils.stateless.StatelessAjaxLink;
import com.commafeed.model.FeedCategory;
import com.commafeed.model.FeedSubscription;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
public class FeedViewPage extends BasePage {
@Inject
FeedSubscriptionService feedSubscriptionService;
@Inject
FeedCategoryService feedCategoryService;
public FeedViewPage() {
add(newTree("tree"));
add(new Label("entries", Model.of("")));
}
private Component newTree(String markupId) {
ITreeProvider<FeedCategory, FeedSubscription> provider = new ITreeProvider<FeedCategory, FeedSubscription>() {
private List<FeedCategory> cats;
private List<FeedSubscription> subsWithoutCategory;
private void init() {
if (cats == null) {
cats = feedCategoryService.findAll(CommaFeedSession.get()
.getUser());
}
if (subsWithoutCategory == null) {
subsWithoutCategory = feedSubscriptionService.findByField(
MF.i(MF.p(FeedSubscription.class).getCategory()),
null);
}
}
@Override
public Collection<FeedCategory> getChildren(final FeedCategory node) {
init();
return Lists.newArrayList(Collections2.filter(cats,
new Predicate<FeedCategory>() {
@Override
public boolean apply(FeedCategory cat) {
return (node == null && cat.getParent() == null)
|| (cat.getParent() != null
&& node != null && cat
.getParent().getId() == node
.getId());
}
}));
}
@Override
public Collection<FeedSubscription> getLeaves(FeedCategory node) {
init();
if (node == null) {
return subsWithoutCategory;
}
return node.getSubscriptions();
}
@Override
public IModel<String> getChildLabel(FeedCategory node) {
return Model.of(node.getName());
}
@Override
public IModel<FeedSubscription> model(FeedSubscription object) {
return Model.of(object);
}
@Override
public void detach() {
cats = null;
}
};
return new CssTreeView<FeedCategory, FeedSubscription>(markupId,
provider) {
@Override
protected Component newLink(String markupId,
final IModel<FeedSubscription> model) {
return new StatelessAjaxLink<Void>(markupId) {
@Override
public void onClick(AjaxRequestTarget target) {
System.out.println(model.getObject().getId());
}
}.setBody(Model.of(model.getObject().getTitle()));
}
};
}
}

View File

@@ -0,0 +1,7 @@
package com.commafeed.frontend.pages.home;
import com.commafeed.frontend.pages.BasePage;
public class HomePage extends BasePage {
}

View File

@@ -0,0 +1,25 @@
package com.commafeed.frontend.references.csstree;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.request.resource.CssResourceReference;
/**
* http://experiments.wemakesites.net/css3-treeview.html
*
*/
public class CssTreeViewReference extends CssResourceReference {
private static CssTreeViewReference instance = new CssTreeViewReference();
public CssTreeViewReference() {
super(CssTreeViewReference.class, "css3-treeview.css");
}
public static void render(IHeaderResponse response) {
response.render(CssHeaderItem.forReference(instance));
}
public static CssTreeViewReference get() {
return instance;
}
}

View File

@@ -0,0 +1,115 @@
.css-treeview ul,
.css-treeview li
{
padding: 0;
margin: 0;
list-style: none;
}
.css-treeview input
{
position: absolute;
opacity: 0;
}
.css-treeview
{
font: normal 11px "Segoe UI", Arial, Sans-serif;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
}
.css-treeview a
{
color: #00f;
text-decoration: none;
}
.css-treeview a:hover
{
text-decoration: underline;
}
.css-treeview input + label + ul
{
margin: 0 0 0 22px;
}
.css-treeview input ~ ul
{
display: none;
}
.css-treeview label,
.css-treeview label::before
{
cursor: pointer;
}
.css-treeview input:disabled + label
{
cursor: default;
opacity: .6;
}
.css-treeview input:checked:not(:disabled) ~ ul
{
display: block;
}
.css-treeview label,
.css-treeview label::before
{
background: url("icons.png") no-repeat;
}
.css-treeview label,
.css-treeview a,
.css-treeview label::before
{
display: inline-block;
height: 16px;
line-height: 16px;,
vertical-align: middle;
}
.css-treeview label
{
background-position: 18px 0;
}
.css-treeview label::before
{
content: "";
width: 16px;
margin: 0 22px 0 0;
vertical-align: middle;
background-position: 0 -32px;
}
.css-treeview input:checked + label::before
{
background-position: 0 -16px;
}
/* webkit adjacent element selector bugfix */
@media screen and (-webkit-min-device-pixel-ratio:0)
{
.css-treeview
{
-webkit-animation: webkit-adjacent-element-selector-bugfix infinite 1s;
}
@-webkit-keyframes webkit-adjacent-element-selector-bugfix
{
from
{
padding: 0;
}
to
{
padding: 0;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

View File

@@ -0,0 +1,40 @@
package com.commafeed.frontend.utils;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import com.google.common.collect.Lists;
public class MapToListModel<K, V> extends
LoadableDetachableModel<List<Map.Entry<K, V>>> {
private static final long serialVersionUID = 1L;
private IModel<Map<K, V>> model;
public MapToListModel(Map<K, V> map) {
this.model = Model.ofMap(map);
}
public MapToListModel(IModel<Map<K, V>> model) {
this.model = model;
}
@Override
protected List<Entry<K, V>> load() {
Map<K, V> map = model.getObject();
return map == null ? null : Lists.newArrayList(map.entrySet());
}
@Override
public void detach() {
super.detach();
model.detach();
}
}

View File

@@ -0,0 +1,58 @@
package com.commafeed.frontend.utils;
import org.apache.wicket.model.PropertyModel;
import ch.lambdaj.Lambda;
import ch.lambdaj.function.argument.Argument;
import ch.lambdaj.function.argument.ArgumentsFactory;
/**
* Utility class to generate PropertyModels in a type-safe way
*
*/
public class ModelFactory {
public static <T> String invokedProperty(T proxiedValue) {
Argument<T> a = ArgumentsFactory.actualArgument(proxiedValue);
return a.getInkvokedPropertyName();
}
public static <T> PropertyModel<T> model(Object value, T proxiedValue) {
String invokedPN = invokedProperty(proxiedValue);
PropertyModel<T> m = new PropertyModel<T>(value, invokedPN);
return m;
}
@SuppressWarnings("unchecked")
public static <T> T proxy(T t) {
Object object = Lambda.on(t.getClass());
return (T) object;
}
public static <T> T proxy(Class<T> clazz) {
return Lambda.on(clazz);
}
/**
* shortcuts to ModelFactory
*
*/
public static class MF {
public static <T> String i(T proxiedValue) {
return ModelFactory.invokedProperty(proxiedValue);
}
public static <T> PropertyModel<T> m(Object value, T proxiedValue) {
return ModelFactory.model(value, proxiedValue);
}
public static <T> T p(T t) {
return ModelFactory.proxy(t);
}
public static <T> T p(Class<T> clazz) {
return ModelFactory.proxy(clazz);
}
}
}

View File

@@ -0,0 +1,135 @@
package com.commafeed.frontend.utils;
import java.security.Principal;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.wicket.ajax.WicketEventJQueryResourceReference;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.http.WebResponse;
import org.apache.wicket.request.resource.CssResourceReference;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
import org.apache.wicket.util.io.IOUtils;
import org.apache.wicket.util.template.PackageTextTemplate;
import de.agilecoders.wicket.webjars.request.resource.WebjarsCssResourceReference;
import de.agilecoders.wicket.webjars.request.resource.WebjarsJavaScriptResourceReference;
public class WicketUtils {
public static void loadJQuery(IHeaderResponse response) {
response.render(JavaScriptHeaderItem
.forReference(WicketEventJQueryResourceReference.get()));
}
public static JavaScriptHeaderItem buildJavaScriptHeaderItem(Class<?> klass) {
return JavaScriptHeaderItem
.forReference(new JavaScriptResourceReference(klass, klass
.getSimpleName() + ".js"));
}
public static JavaScriptHeaderItem buildJavaScriptWebJarHeaderItem(
String name) {
return JavaScriptHeaderItem
.forReference(new WebjarsJavaScriptResourceReference(name));
}
public static void loadJS(IHeaderResponse response, Class<?> klass) {
response.render(buildJavaScriptHeaderItem(klass));
}
public static void loadJS(IHeaderResponse response, Class<?> klass,
Map<String, ? extends Object> variables) {
OnDomReadyHeaderItem result = null;
PackageTextTemplate template = null;
try {
template = new PackageTextTemplate(klass, klass.getSimpleName()
+ ".js");
String script = template.asString(variables);
result = OnDomReadyHeaderItem.forScript(script);
} finally {
IOUtils.closeQuietly(template);
}
response.render(result);
}
public static void loadWebJarJS(IHeaderResponse response, String name) {
response.render(buildJavaScriptWebJarHeaderItem(name));
}
public static CssHeaderItem buildCssHeaderItem(Class<?> klass) {
return CssHeaderItem.forReference(new CssResourceReference(klass, klass
.getSimpleName() + ".css"));
}
public static CssHeaderItem buildCssWebJarHeaderItem(String name) {
return CssHeaderItem
.forReference(new WebjarsCssResourceReference(name));
}
public static void loadCSS(IHeaderResponse response, Class<?> klass) {
response.render(buildCssHeaderItem(klass));
}
public static void loadCSS(IHeaderResponse response, Class<?> klass,
Map<String, ? extends Object> variables) {
CssHeaderItem result = null;
PackageTextTemplate template = null;
try {
template = new PackageTextTemplate(klass, klass.getSimpleName()
+ ".js");
String css = template.asString(variables);
result = CssHeaderItem.forCSS(css, null);
} finally {
IOUtils.closeQuietly(template);
}
response.render(result);
}
public static void loadWebJarCSS(IHeaderResponse response, String name) {
response.render(buildCssWebJarHeaderItem(name));
}
public static HttpServletRequest getHttpServletRequest() {
ServletWebRequest servletWebRequest = (ServletWebRequest) RequestCycle
.get().getRequest();
return servletWebRequest.getContainerRequest();
}
public static HttpServletResponse getHttpServletResponse() {
WebResponse webResponse = (WebResponse) RequestCycle.get()
.getResponse();
return (HttpServletResponse) webResponse.getContainerResponse();
}
public static Principal getPrincipal() {
return getHttpServletRequest().getUserPrincipal();
}
public static String getPrincipalName() {
Principal principal = getPrincipal();
return principal == null ? null : principal.toString();
}
public static boolean isUserInRole(String role) {
return getHttpServletRequest().isUserInRole(role);
}
public static boolean isUserInRoles(String... roles) {
boolean inRoles = true;
for (String role : roles) {
if (!isUserInRole(role)) {
inRoles = false;
break;
}
}
return inRoles;
}
}

View File

@@ -0,0 +1,23 @@
package com.commafeed.frontend.utils.exception;
public class DisplayException extends RuntimeException {
private static final long serialVersionUID = 1L;
private DisplayException() {
}
public DisplayException(String message) {
super(message, new DisplayException());
}
public DisplayException(Throwable t) {
super(t.getMessage(), t);
}
public DisplayException(String message, Throwable t) {
super(message, t);
}
}

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en"
xmlns:wicket="http://git-wip-us.apache.org/repos/asf/wicket/repo?p=wicket.git;a=blob_plain;f=wicket-core/src/main/resources/META-INF/wicket-1.5.xsd;hb=master">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<div class="container">
<div class="page-header">
<h1>An unexpected error occured</h1>
</div>
<div class="alert alert-error" style="cursor: pointer"
data-toggle="collapse" data-target="#stacktrace">
<ul class="feedbackPanel unstyled" style="margin-bottom: 0px;">
<li class="feedbackPanelERROR">
<span class="feedbackPanelERROR" wicket:id="message"></span>
</li>
</ul>
</div>
Go back to the <a href="javascript:window.history.back()">previous page</a> or to the <a wicket:id="homepage">home page.</a>
<div id="stacktrace" class="collapse">
<pre wicket:id="stacktrace"></pre>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,40 @@
package com.commafeed.frontend.utils.exception;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import com.commafeed.frontend.pages.BasePage;
public class DisplayExceptionPage extends BasePage {
private static final long serialVersionUID = 1L;
public DisplayExceptionPage(Throwable t) {
Throwable de = findDisplayException(t);
if (de != null) {
t = de;
}
add(new Label("message", t.getMessage()));
add(new BookmarkablePageLink<Void>("homepage", getApplication()
.getHomePage()));
StringWriter stringWriter = new StringWriter();
t.printStackTrace(new PrintWriter(stringWriter));
t.printStackTrace();
add(new Label("stacktrace", stringWriter.toString()));
}
private Throwable findDisplayException(Throwable t) {
while (t != null && !(t instanceof DisplayException)) {
t = t.getCause();
}
return t;
}
}

View File

@@ -0,0 +1,93 @@
package com.commafeed.frontend.utils.stateless;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import de.agilecoders.wicket.markup.html.bootstrap.button.BootstrapAjaxButton;
import de.agilecoders.wicket.markup.html.bootstrap.button.Buttons;
public abstract class BootstrapStatelessAjaxButton extends BootstrapAjaxButton {
private static final long serialVersionUID = 1L;
private PageParameters parameters;
public BootstrapStatelessAjaxButton(final String componentId,
final Buttons.Type buttonType) {
super(componentId, buttonType);
}
public BootstrapStatelessAjaxButton(final String componentId,
final IModel<String> model, final Buttons.Type buttonType) {
super(componentId, model, buttonType);
}
public BootstrapStatelessAjaxButton(final String componentId,
final IModel<String> model, final Buttons.Type buttonType,
PageParameters parameters) {
super(componentId, model, buttonType);
this.parameters = parameters;
}
public BootstrapStatelessAjaxButton(String id, Form<?> form,
Buttons.Type buttonType) {
super(id, form, buttonType);
}
public BootstrapStatelessAjaxButton(String id, IModel<String> model,
Form<?> form, Buttons.Type buttonType) {
super(id, model, form, buttonType);
}
public BootstrapStatelessAjaxButton(String id, IModel<String> model,
Form<?> form, Buttons.Type buttonType, PageParameters parameters) {
super(id, model, form, buttonType);
this.parameters = parameters;
}
@Override
protected AjaxFormSubmitBehavior newAjaxFormSubmitBehavior(String event) {
return new StatelessAjaxFormSubmitBehavior(getForm(), event) {
private static final long serialVersionUID = 1L;
@Override
protected void onSubmit(AjaxRequestTarget target) {
BootstrapStatelessAjaxButton.this.onSubmit(target,
BootstrapStatelessAjaxButton.this.getForm());
}
@Override
protected void onAfterSubmit(AjaxRequestTarget target) {
BootstrapStatelessAjaxButton.this.onAfterSubmit(target,
BootstrapStatelessAjaxButton.this.getForm());
}
@Override
protected void onError(AjaxRequestTarget target) {
BootstrapStatelessAjaxButton.this.onError(target,
BootstrapStatelessAjaxButton.this.getForm());
}
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
BootstrapStatelessAjaxButton.this.updateAjaxAttributes(attributes);
}
@Override
public boolean getDefaultProcessing() {
return BootstrapStatelessAjaxButton.this.getDefaultFormProcessing();
}
@Override
protected PageParameters getPageParameters() {
return parameters;
}
};
}
}

View File

@@ -0,0 +1,55 @@
package com.commafeed.frontend.utils.stateless;
import org.apache.wicket.ajax.AjaxEventBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import de.agilecoders.wicket.markup.html.bootstrap.button.BootstrapAjaxLink;
import de.agilecoders.wicket.markup.html.bootstrap.button.Buttons;
public abstract class BootstrapStatelessAjaxLink<T> extends BootstrapAjaxLink<T> {
private static final long serialVersionUID = 1L;
private PageParameters parameters;
public BootstrapStatelessAjaxLink(final String id, final Buttons.Type buttonType) {
super(id, buttonType);
}
public BootstrapStatelessAjaxLink(String id, IModel<T> model,
Buttons.Type buttonType) {
super(id, model, buttonType);
}
public BootstrapStatelessAjaxLink(String id, IModel<T> model,
Buttons.Type buttonType, PageParameters parameters) {
super(id, model, buttonType);
this.parameters = parameters;
}
@Override
protected AjaxEventBehavior newAjaxEventBehavior(String event) {
return new StatelessAjaxEventBehavior(event) {
private static final long serialVersionUID = 1L;
@Override
protected void onEvent(AjaxRequestTarget target) {
BootstrapStatelessAjaxLink.this.onClick(target);
}
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
BootstrapStatelessAjaxLink.this.updateAjaxAttributes(attributes);
}
@Override
protected PageParameters getPageParameters() {
return parameters;
}
};
}
}

View File

@@ -0,0 +1,36 @@
package com.commafeed.frontend.utils.stateless;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxEventBehavior;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.mapper.parameter.PageParameters;
public abstract class StatelessAjaxEventBehavior extends AjaxEventBehavior {
private static final long serialVersionUID = 1L;
public StatelessAjaxEventBehavior(final String event) {
super(event);
}
@Override
public CharSequence getCallbackUrl() {
final Url url = Url.parse(super.getCallbackUrl().toString());
final PageParameters params = getPageParameters();
return StatelessEncoder.mergeParameters(url, params).toString();
}
@Override
public boolean getStatelessHint(final Component component) {
return true;
}
/**
* Override this to pass the context of the current page to the behavior,
* allowing it to recreate the context for the ajax request.
*
*/
protected PageParameters getPageParameters() {
return null;
}
}

View File

@@ -0,0 +1,37 @@
package com.commafeed.frontend.utils.stateless;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.mapper.parameter.PageParameters;
public abstract class StatelessAjaxFormComponentUpdatingBehavior extends
AjaxFormComponentUpdatingBehavior {
private static final long serialVersionUID = -286307141298283926L;
public StatelessAjaxFormComponentUpdatingBehavior(final String event) {
super(event);
}
@Override
public CharSequence getCallbackUrl() {
final Url url = Url.parse(super.getCallbackUrl().toString());
final PageParameters params = getPageParameters();
return StatelessEncoder.mergeParameters(url, params).toString();
}
@Override
public boolean getStatelessHint(final Component component) {
return true;
}
/**
* Override this to pass the context of the current page to the behavior,
* allowing it to recreate the context for the ajax request.
*
*/
protected PageParameters getPageParameters() {
return null;
}
}

View File

@@ -0,0 +1,43 @@
package com.commafeed.frontend.utils.stateless;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.mapper.parameter.PageParameters;
public abstract class StatelessAjaxFormSubmitBehavior extends
AjaxFormSubmitBehavior {
private static final long serialVersionUID = 1L;
public StatelessAjaxFormSubmitBehavior(final String event) {
super(event);
}
public StatelessAjaxFormSubmitBehavior(Form<?> form, String event) {
super(form, event);
}
@Override
public CharSequence getCallbackUrl() {
final Url url = Url.parse(super.getCallbackUrl().toString());
final PageParameters params = getPageParameters();
return StatelessEncoder.mergeParameters(url, params).toString();
}
@Override
public boolean getStatelessHint(final Component component) {
return true;
}
/**
* Override this to pass the context of the current page to the behavior,
* allowing it to recreate the context for the ajax request.
*
*/
protected PageParameters getPageParameters() {
return null;
}
}

View File

@@ -0,0 +1,70 @@
package com.commafeed.frontend.utils.stateless;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.ajax.markup.html.IAjaxLink;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.mapper.parameter.PageParameters;
public abstract class StatelessAjaxLink<T> extends StatelessLink<T>
implements IAjaxLink {
private static final long serialVersionUID = -133600842398684777L;
public StatelessAjaxLink(final String id) {
this(id, null, null);
}
public StatelessAjaxLink(final String id,
final PageParameters params) {
this(id, null, params);
}
public StatelessAjaxLink(final String id, final IModel<T> model) {
this(id, model, null);
}
public StatelessAjaxLink(final String id, final IModel<T> model,
final PageParameters params) {
super(id, model, params);
add(new StatelessAjaxEventBehavior("click") {
private static final long serialVersionUID = -8445395501430605953L;
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
StatelessAjaxLink.this.updateAjaxAttributes(attributes);
}
@Override
protected PageParameters getPageParameters() {
return StatelessAjaxLink.this.getPageParameters();
}
@Override
protected void onComponentTag(final ComponentTag tag) {
if (isLinkEnabled()) {
super.onComponentTag(tag);
}
}
@Override
protected void onEvent(final AjaxRequestTarget target) {
onClick(target);
target.add(StatelessAjaxLink.this);
}
});
}
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
}
@Override
public final void onClick() {
onClick(null);
}
public abstract void onClick(final AjaxRequestTarget target);
}

View File

@@ -0,0 +1,42 @@
package com.commafeed.frontend.utils.stateless;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Set;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.mapper.parameter.INamedParameters;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.encoding.UrlEncoder;
final class StatelessEncoder {
static Url mergeParameters(final Url url, final PageParameters params) {
if (params == null) {
return url;
}
Charset charset = url.getCharset();
Url mergedUrl = Url.parse(url.toString(), charset);
UrlEncoder urlEncoder = UrlEncoder.QUERY_INSTANCE;
Set<String> setParameters = new HashSet<String>();
for (INamedParameters.NamedPair pair : params.getAllNamed()) {
String key = urlEncoder.encode(pair.getKey(), charset);
String value = urlEncoder.encode(pair.getValue(), charset);
if (setParameters.contains(key)) {
mergedUrl.addQueryParameter(key, value);
} else {
mergedUrl.setQueryParameter(key, value);
setParameters.add(key);
}
}
return mergedUrl;
}
private StatelessEncoder() {
}
}

View File

@@ -0,0 +1,43 @@
package com.commafeed.frontend.utils.stateless;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.mapper.parameter.PageParameters;
public abstract class StatelessLink<T> extends Link<T> {
private static final long serialVersionUID = 1L;
private final PageParameters parameters;
public StatelessLink(final String id) {
this(id, null, null);
}
public StatelessLink(final String id, final IModel<T> model) {
this(id, model, null);
}
public StatelessLink(final String id, final IModel<T> model,
final PageParameters params) {
super(id, model);
setMarkupId(id);
this.parameters = params;
}
protected final PageParameters getPageParameters() {
return parameters;
}
@Override
protected boolean getStatelessHint() {
return true;
}
@Override
protected CharSequence getURL() {
final Url url = Url.parse(super.getURL().toString());
Url mergedUrl = StatelessEncoder.mergeParameters(url, parameters);
return mergedUrl.toString();
}
}

View File

@@ -0,0 +1,64 @@
package com.commafeed.model;
import java.io.Serializable;
import java.util.Date;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import com.google.common.collect.Sets;
@Entity
@Table(name = "FEEDS")
public class Feed implements Serializable {
@Id
@Column(length = 2048)
private String url;
@Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated;
@OneToMany(mappedBy = "feed", fetch = FetchType.EAGER)
private Set<FeedEntry> entries = Sets.newHashSet();
public Feed() {
}
public Feed(String url) {
this.url = url;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Date getLastUpdated() {
return lastUpdated;
}
public void setLastUpdated(Date lastUpdated) {
this.lastUpdated = lastUpdated;
}
public Set<FeedEntry> getEntries() {
return entries;
}
public void setEntries(Set<FeedEntry> entries) {
this.entries = entries;
}
}

View File

@@ -0,0 +1,78 @@
package com.commafeed.model;
import java.io.Serializable;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import com.google.common.collect.Sets;
@Entity
@Table(name = "FEEDCATEGORIES")
public class FeedCategory implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(length = 128)
private String name;
@ManyToOne
private User user;
@ManyToOne
private FeedCategory parent;
@OneToMany(mappedBy = "category", fetch = FetchType.EAGER)
private Set<FeedSubscription> subscriptions = Sets.newHashSet();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public FeedCategory getParent() {
return parent;
}
public void setParent(FeedCategory parent) {
this.parent = parent;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Set<FeedSubscription> getSubscriptions() {
return subscriptions;
}
public void setSubscriptions(Set<FeedSubscription> subscriptions) {
this.subscriptions = subscriptions;
}
}

View File

@@ -0,0 +1,86 @@
package com.commafeed.model;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
@Table(name = "FEEDENTRIES")
public class FeedEntry implements Serializable {
@Id
@Column(length = 2048)
private String guid;
@ManyToOne
private Feed feed;
@Column(length = 256)
private String title;
@Lob
private String content;
@Column(length = 2048)
private String url;
@Temporal(TemporalType.TIMESTAMP)
private Date updated;
public String getGuid() {
return guid;
}
public void setGuid(String guid) {
this.guid = guid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Date getUpdated() {
return updated;
}
public void setUpdated(Date updated) {
this.updated = updated;
}
public Feed getFeed() {
return feed;
}
public void setFeed(Feed feed) {
this.feed = feed;
}
}

View File

@@ -0,0 +1,71 @@
package com.commafeed.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name = "FEEDENTRYSTATUSES")
public class FeedEntryStatus implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne
private User user;
@ManyToOne
private FeedEntry entry;
@Column(name = "read_status")
private boolean read;
private boolean starred;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public FeedEntry getEntry() {
return entry;
}
public void setEntry(FeedEntry entry) {
this.entry = entry;
}
public boolean isRead() {
return read;
}
public void setRead(boolean read) {
this.read = read;
}
public boolean isStarred() {
return starred;
}
public void setStarred(boolean starred) {
this.starred = starred;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}

View File

@@ -0,0 +1,73 @@
package com.commafeed.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name = "FEEDSUBSCRIPTIONS")
public class FeedSubscription implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne
private User user;
@ManyToOne
private Feed feed;
@Column(length = 128)
private String title;
@ManyToOne
private FeedCategory category;
public Feed getFeed() {
return feed;
}
public void setFeed(Feed feed) {
this.feed = feed;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public FeedCategory getCategory() {
return category;
}
public void setCategory(FeedCategory category) {
this.category = category;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}

View File

@@ -0,0 +1,61 @@
package com.commafeed.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "USERS")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(length = 32)
private String name;
@Column(length = 256)
private byte[] password;
@Column(length = 8)
private byte[] salt;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public byte[] getPassword() {
return password;
}
public void setPassword(byte[] password) {
this.password = password;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public byte[] getSalt() {
return salt;
}
public void setSalt(byte[] salt) {
this.salt = salt;
}
}