Skip to content

Commit dd36b67

Browse files
author
David Holt
committed
Merge branch 'master' of github.com:daveeed/SimpleBlog
2 parents a3a25a4 + 2724641 commit dd36b67

31 files changed

+1018
-68
lines changed

SimpleBlog/Sources/com/wowodc/app/Application.java

+104-1
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,35 @@
44
import java.util.Locale;
55

66
import com.webobjects.directtoweb.D2W;
7+
import com.webobjects.eocontrol.EOEditingContext;
8+
import com.webobjects.eocontrol.EOKeyGlobalID;
9+
import com.webobjects.eocontrol.EOObjectStoreCoordinator;
10+
import com.webobjects.eocontrol.EOQualifierEvaluation;
11+
import com.webobjects.foundation.NSArray;
12+
import com.webobjects.foundation.NSDictionary;
13+
import com.webobjects.foundation.NSNotification;
14+
import com.webobjects.foundation.NSNotificationCenter;
15+
import com.webobjects.foundation.NSSelector;
16+
import com.webobjects.foundation.NSTimestamp;
717
import com.wowodc.model.BlogCategory;
818
import com.wowodc.model.BlogEntry;
19+
import com.wowodc.model.DelegatePKHistory;
920
import com.wowodc.model.Person;
21+
import com.wowodc.model.SyncInfo;
22+
import com.wowodc.model.enums.SyncInfoStatus;
23+
import com.wowodc.rest.controllers.BlogEntryController;
1024
import com.wowodc.rest.controllers.OtherRoutesController;
1125
import com.wowodc.rest.controllers.RssController;
1226

1327
import er.extensions.appserver.ERXApplication;
1428
import er.extensions.appserver.navigation.ERXNavigationManager;
29+
import er.extensions.eof.ERXEC;
30+
import er.extensions.eof.ERXEnterpriseObject;
31+
import er.extensions.foundation.ERXArrayUtilities;
32+
import er.extensions.foundation.ERXRandomGUID;
33+
import er.rest.ERXRestContext;
1534
import er.rest.ERXRestNameRegistry;
35+
import er.rest.IERXRestDelegate;
1636
import er.rest.routes.ERXRoute;
1737
import er.rest.routes.ERXRouteRequestHandler;
1838

@@ -34,7 +54,8 @@ public Application() {
3454

3555
ERXRouteRequestHandler restRequestHandler = new ERXRouteRequestHandler();
3656
restRequestHandler.addDefaultRoutes(BlogCategory.ENTITY_NAME);
37-
restRequestHandler.addDefaultRoutes(BlogEntry.ENTITY_NAME);
57+
restRequestHandler.insertRoute(new ERXRoute(BlogEntry.ENTITY_NAME, "/posts/{uniqueTitle:String}", ERXRoute.Method.Get, BlogEntryController.class, "show"));
58+
restRequestHandler.insertRoute(new ERXRoute(BlogEntry.ENTITY_NAME, "/posts", ERXRoute.Method.Get, BlogEntryController.class, "index"));
3859
restRequestHandler.addDefaultRoutes(Person.ENTITY_NAME);
3960
restRequestHandler.insertRoute(new ERXRoute("Other", "", ERXRoute.Method.Get, OtherRoutesController.class, "mainPage"));
4061
restRequestHandler.insertRoute(new ERXRoute("Other", "/admin", ERXRoute.Method.Get, OtherRoutesController.class, "adminPage"));
@@ -60,4 +81,86 @@ public String _rewriteURL(String url) {
6081
}
6182
return processedURL;
6283
}
84+
85+
@Override
86+
public void didFinishLaunching() {
87+
super.didFinishLaunching();
88+
NSSelector selector = new NSSelector("coordinateChanges", new Class[] { NSNotification.class } );
89+
NSNotificationCenter.defaultCenter().addObserver( this, selector, EOObjectStoreCoordinator.ObjectsChangedInStoreNotification, EOObjectStoreCoordinator.defaultCoordinator());
90+
}
91+
92+
@SuppressWarnings("unchecked")
93+
public void coordinateChanges(NSNotification notification) {
94+
NSDictionary userInfo = (NSDictionary)notification.userInfo();
95+
EOEditingContext ec = ERXEC.newEditingContext(new EOObjectStoreCoordinator());
96+
ec.lock();
97+
98+
try {
99+
NSArray<Object> insertedObjects = ERXArrayUtilities.filteredArrayWithQualifierEvaluation((NSArray<Object>)userInfo.objectForKey("inserted"), new EOSyncEntityFilter() );
100+
for ( Object id : insertedObjects ) {
101+
ERXEnterpriseObject eo = eo(id, ec);
102+
SyncInfo syncDetail = SyncInfo.createSyncInfo(ec, ERXRandomGUID.newGid(), new NSTimestamp(), SyncInfoStatus.INSERTED, eo.entityName() + ":" + eo.primaryKey());
103+
syncDetail.setDelegatedPrimaryKeyValue((String)entityId(eo));
104+
}
105+
106+
NSArray<Object> deletedObjects = ERXArrayUtilities.filteredArrayWithQualifierEvaluation((NSArray<Object>)userInfo.objectForKey("deleted"), new EOSyncEntityFilter() );
107+
for ( Object id : deletedObjects ) {
108+
ERXEnterpriseObject eo = eo(id, ec);
109+
if (eo != null) {
110+
SyncInfo syncDetail = SyncInfo.fetchSyncInfo(ec, SyncInfo.TOKEN.eq(eo.entityName() + ":" + eo.primaryKey()));
111+
if (syncDetail != null) {
112+
syncDetail.setEtag(ERXRandomGUID.newGid());
113+
syncDetail.setLastModified(new NSTimestamp());
114+
syncDetail.setStatus(SyncInfoStatus.DELETED);
115+
}
116+
}
117+
}
118+
119+
NSArray<Object> updatedObjects = ERXArrayUtilities.filteredArrayWithQualifierEvaluation((NSArray<Object>)userInfo.objectForKey("updated"), new EOSyncEntityFilter() );
120+
for ( Object id : updatedObjects ) {
121+
ERXEnterpriseObject eo = eo(id, ec);
122+
if (eo != null) {
123+
SyncInfo syncDetail = SyncInfo.fetchSyncInfo(ec, SyncInfo.TOKEN.eq(eo.entityName() + ":" + eo.primaryKey()));
124+
if (syncDetail != null) {
125+
if (!(syncDetail.delegatedPrimaryKeyValue().equals((String)entityId(eo)))) {
126+
DelegatePKHistory.createDelegatePKHistory(ec, syncDetail.delegatedPrimaryKeyValue(), syncDetail);
127+
}
128+
syncDetail.setEtag(ERXRandomGUID.newGid());
129+
syncDetail.setLastModified(new NSTimestamp());
130+
syncDetail.setStatus(SyncInfoStatus.UPDATED);
131+
syncDetail.setDelegatedPrimaryKeyValue((String)entityId(eo));
132+
}
133+
}
134+
}
135+
136+
ec.saveChanges();
137+
}
138+
finally {
139+
ec.unlock();
140+
}
141+
}
142+
143+
private ERXEnterpriseObject eo(Object id, EOEditingContext ec) {
144+
EOKeyGlobalID gid = (EOKeyGlobalID)id;
145+
ERXEnterpriseObject eo = (ERXEnterpriseObject)ec.faultForGlobalID( gid, ec);
146+
return eo;
147+
}
148+
149+
private Object entityId(ERXEnterpriseObject eo) {
150+
return IERXRestDelegate.Factory.delegateForEntityNamed(eo.entityName()).primaryKeyForObject(eo, new ERXRestContext(eo.editingContext()));
151+
}
152+
153+
public class EOSyncEntityFilter implements EOQualifierEvaluation
154+
{
155+
public boolean evaluateWithObject(Object object)
156+
{
157+
EOKeyGlobalID eokgid = (EOKeyGlobalID)object;
158+
return syncEntityNames().containsObject(eokgid.entityName());
159+
}
160+
}
161+
162+
public NSArray<String> syncEntityNames() {
163+
return new NSArray<String>( BlogEntry.ENTITY_NAME );
164+
}
165+
63166
}

SimpleBlog/Sources/com/wowodc/rest/controllers/BaseRestController.java

+22-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

66
import er.extensions.appserver.ERXHttpStatusCodes;
77
import er.rest.format.ERXRestFormat;
8-
import er.rest.routes.ERXRouteController;
8+
import er.rest.routes.ERXDefaultRouteController;
99

10-
public class BaseRestController extends ERXRouteController {
10+
public class BaseRestController extends ERXDefaultRouteController {
1111

1212
public BaseRestController(WORequest request) {
1313
super(request);
@@ -35,4 +35,24 @@ protected boolean isHTMlFormat() {
3535
return (ERXRestFormat.html().name().equals(this.format().name())) ? true: false;
3636
}
3737

38+
@Override
39+
public WOActionResults newAction() throws Throwable {
40+
return errorResponse(ERXHttpStatusCodes.METHOD_NOT_ALLOWED);
41+
}
42+
43+
@Override
44+
public WOActionResults destroyAction() throws Throwable {
45+
return errorResponse(ERXHttpStatusCodes.METHOD_NOT_ALLOWED);
46+
}
47+
48+
@Override
49+
public WOActionResults showAction() throws Throwable {
50+
return errorResponse(ERXHttpStatusCodes.METHOD_NOT_ALLOWED);
51+
}
52+
53+
@Override
54+
public WOActionResults indexAction() throws Throwable {
55+
return errorResponse(ERXHttpStatusCodes.METHOD_NOT_ALLOWED);
56+
}
57+
3858
}

SimpleBlog/Sources/com/wowodc/rest/controllers/BlogEntryController.java

+153-10
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,35 @@
11
package com.wowodc.rest.controllers;
22

3+
import java.text.ParseException;
4+
import java.text.SimpleDateFormat;
5+
import java.util.Locale;
6+
37
import com.webobjects.appserver.WOActionResults;
8+
import com.webobjects.appserver.WORedirect;
49
import com.webobjects.appserver.WORequest;
510
import com.webobjects.foundation.NSArray;
11+
import com.webobjects.foundation.NSTimestamp;
612
import com.wowodc.model.BlogCategory;
713
import com.wowodc.model.BlogEntry;
14+
import com.wowodc.model.DelegatePKHistory;
815
import com.wowodc.model.Person;
9-
import com.wowodc.ui.components.BlogEntryShowPage;
16+
import com.wowodc.model.SyncInfo;
17+
import com.wowodc.model.enums.SyncInfoStatus;
18+
import com.wowodc.ui.components.BlogEntryDetailPage;
19+
import com.wowodc.ui.components.BlogEntryListPage;
1020

21+
import er.extensions.appserver.ERXHttpStatusCodes;
22+
import er.extensions.appserver.ERXResponse;
1123
import er.extensions.eof.ERXKeyFilter;
1224
import er.rest.ERXRestContext;
25+
import er.rest.format.ERXRestFormat;
26+
import er.rest.routes.ERXRouteResults;
27+
import er.rest.routes.ERXRouteUrlUtils;
1328

1429
public class BlogEntryController extends BaseRestController {
1530

31+
public static final SimpleDateFormat formatter = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", Locale.ENGLISH); // Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT
32+
1633
public BlogEntryController(WORequest request) {
1734
super(request);
1835
}
@@ -54,24 +71,150 @@ protected ERXKeyFilter filter() {
5471

5572
return filter;
5673
}
74+
75+
public String ifNoneMatchHeader() {
76+
return this.request().headerForKey("If-None-Match");
77+
}
78+
79+
public NSTimestamp ifModifiedSinceHeader() {
80+
String ifModifiedSinceHeader = this.request().headerForKey("If-Modified-Since");
81+
if (ifModifiedSinceHeader != null) {
82+
java.util.Date ifModifiedSince;
83+
try {
84+
ifModifiedSince = formatter.parse(ifModifiedSinceHeader);
85+
return new NSTimestamp(ifModifiedSince);
86+
}
87+
catch (ParseException e) {
88+
return null;
89+
}
90+
}
91+
return null;
92+
}
5793

94+
public SyncInfo syncInfoForETag() {
95+
return SyncInfo.fetchSyncInfo(editingContext(), SyncInfo.ETAG.eq(ifNoneMatchHeader()));
96+
}
97+
98+
public SyncInfo syncInfoForLastModified() throws Throwable {
99+
return SyncInfo.fetchSyncInfo(editingContext(), SyncInfo.LAST_MODIFIED.eq(ifModifiedSinceHeader()));
100+
}
101+
102+
public WOActionResults responseForNotModified(SyncInfo syncDetails) {
103+
if (syncDetails.status() == SyncInfoStatus.DELETED) {
104+
return response(ERXHttpStatusCodes.GONE);
105+
} else {
106+
return response(ERXHttpStatusCodes.NOT_MODIFIED);
107+
}
108+
}
109+
110+
public boolean isDeleted(String delegatedPK) {
111+
SyncInfo syncDetails = SyncInfo.fetchSyncInfo(editingContext(), SyncInfo.DELEGATED_PRIMARY_KEY_VALUE.eq(delegatedPK));
112+
if (syncDetails != null) {
113+
if (syncDetails.status() == SyncInfoStatus.DELETED) {
114+
return true;
115+
}
116+
}
117+
return false;
118+
}
119+
120+
public SyncInfo delegatedPKInHistory(String delegatedPK) {
121+
NSArray<DelegatePKHistory> histories = DelegatePKHistory.fetchDelegatePKHistories(editingContext(), DelegatePKHistory.DELEGATED_PRIMARY_KEY_VALUE.eq(delegatedPK), null);
122+
if (histories.count() > 0) {
123+
return histories.lastObject().syncInfo();
124+
}
125+
return null;
126+
}
127+
128+
@Override
58129
public WOActionResults showAction() throws Throwable {
59-
String title = routeObjectForKey("title");
60-
if (title == null) {
61-
return errorResponse(404);
130+
SyncInfo syncDetails = null;
131+
BlogEntry post = null;
132+
String uniqueTitle = routeObjectForKey("uniqueTitle");
133+
134+
syncDetails = syncInfoForETag();
135+
if (syncDetails != null) {
136+
return responseForNotModified(syncDetails);
137+
} else {
138+
syncDetails = syncInfoForLastModified();
139+
if (syncDetails != null) {
140+
return responseForNotModified(syncDetails);
141+
} else {
142+
syncDetails = SyncInfo.fetchSyncInfo(editingContext(), SyncInfo.DELEGATED_PRIMARY_KEY_VALUE.eq(uniqueTitle));
143+
post = BlogEntry.fetchBlogEntry(editingContext(), BlogEntry.UNIQUE_TITLE.eq(uniqueTitle));
144+
}
145+
}
146+
147+
if (post != null) {
148+
if (isDeleted(post.uniqueTitle())) {
149+
return response(ERXHttpStatusCodes.GONE);
150+
}
151+
} else {
152+
syncDetails = delegatedPKInHistory(uniqueTitle);
153+
if (syncDetails != null) {
154+
post = BlogEntry.fetchBlogEntry(editingContext(), BlogEntry.UNIQUE_TITLE.eq(syncDetails.delegatedPrimaryKeyValue()));
155+
if (post != null) {
156+
WORedirect redirect = new WORedirect(this.context());
157+
redirect.setUrl(ERXRouteUrlUtils.actionUrlForRecord(this.context(), post, "show", ERXRestFormat.html().name(), null, false, false));
158+
} else {
159+
return errorResponse(ERXHttpStatusCodes.NOT_FOUND);
160+
}
161+
} else {
162+
return errorResponse(ERXHttpStatusCodes.NOT_FOUND);
163+
}
62164
}
63-
BlogEntry post = BlogEntry.fetchRequiredBlogEntry(editingContext(), BlogEntry.TITLE_KEY, title);
165+
166+
ERXResponse response = null;
167+
64168
if (isHTMlFormat()) {
65-
BlogEntryShowPage nextPage = (BlogEntryShowPage)pageWithName(BlogEntryShowPage.class);
169+
BlogEntryDetailPage nextPage = (BlogEntryDetailPage)pageWithName(BlogEntryDetailPage.class);
66170
nextPage.setBlogEntry(post);
67-
return nextPage;
171+
nextPage.setSyncDetails(syncDetails);
172+
response = (ERXResponse)nextPage.generateResponse();
173+
} else {
174+
response = (ERXResponse) response(post, filter());
68175
}
69-
return response(post, filter());
176+
177+
response.setHeader("max-age=300", "Cache-Control");
178+
179+
if (syncDetails != null) {
180+
response.setHeader(formatter.format(syncDetails.lastModified()), "Last-Modified");
181+
response.setHeader(syncDetails.etag(), "Etag");
182+
}
183+
184+
return response;
70185
}
71186

72187
public WOActionResults indexAction() throws Throwable {
73-
NSArray<BlogEntry> entries = BlogEntry.fetchAllBlogEntries(editingContext());
74-
return response(entries, filter());
188+
ERXRouteResults response = null;
189+
190+
NSArray<SyncInfo> syncDetails = SyncInfo.fetchSyncInfos(editingContext(), SyncInfo.TOKEN.like(BlogEntry.ENTITY_NAME + ":*"), SyncInfo.LAST_MODIFIED.ascs());
191+
SyncInfo moreRecentSync = syncDetails.lastObject();
192+
193+
if (moreRecentSync != null) {
194+
if (moreRecentSync.etag().equals(ifNoneMatchHeader())) {
195+
return responseForNotModified(moreRecentSync);
196+
}
197+
198+
if (moreRecentSync.lastModified().equals(ifModifiedSinceHeader())) {
199+
return responseForNotModified(moreRecentSync);
200+
}
201+
}
202+
203+
if (isHTMlFormat()) {
204+
BlogEntryListPage nextPage = (BlogEntryListPage)pageWithName(BlogEntryListPage.class);
205+
nextPage.setSyncDetails(moreRecentSync);
206+
return nextPage;
207+
} else {
208+
NSArray<BlogEntry> entries = BlogEntry.fetchAllBlogEntries(editingContext());
209+
response = (ERXRouteResults) response(entries, filter());
210+
}
211+
212+
if (moreRecentSync != null) {
213+
response.setHeaderForKey(formatter.format(moreRecentSync.lastModified()), "Last-Modified");
214+
response.setHeaderForKey(moreRecentSync.etag(), "Etag");
215+
}
216+
217+
return response;
75218
}
76219

77220
public WOActionResults showByTitleAction() throws Throwable {

SimpleBlog/Sources/com/wowodc/rest/controllers/OtherRoutesController.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import com.webobjects.appserver.WOActionResults;
44
import com.webobjects.appserver.WORequest;
5-
import com.wowodc.ui.components.BlogEntryIndexPage;
5+
import com.wowodc.ui.components.BlogEntryListPage;
66
import com.wowodc.ui.components.Main;
77

88
import er.rest.format.ERXRestFormat;
@@ -14,7 +14,7 @@ public OtherRoutesController(WORequest request) {
1414
}
1515

1616
public WOActionResults mainPageAction() {
17-
return pageWithName(BlogEntryIndexPage.class);
17+
return pageWithName(BlogEntryListPage.class);
1818
}
1919

2020
public WOActionResults adminPageAction() {

0 commit comments

Comments
 (0)