| /** |
| * |
| * Copyright 2009-2010 Rickard Öberg AB |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.zest.library.eventsourcing.domain.rest.server; |
| |
| import java.io.IOException; |
| import java.io.Writer; |
| import java.util.ArrayList; |
| import java.util.Date; |
| import java.util.List; |
| import org.apache.zest.api.injection.scope.Service; |
| import org.apache.zest.api.service.qualifier.Tagged; |
| import org.apache.zest.functional.Iterables; |
| import org.apache.zest.io.Outputs; |
| import org.apache.zest.library.eventsourcing.domain.api.UnitOfWorkDomainEventsValue; |
| import org.apache.zest.library.eventsourcing.domain.source.EventSource; |
| import org.restlet.Request; |
| import org.restlet.Response; |
| import org.restlet.Restlet; |
| import org.restlet.data.CharacterSet; |
| import org.restlet.data.MediaType; |
| import org.restlet.data.Reference; |
| import org.restlet.data.Status; |
| import org.restlet.ext.atom.*; |
| import org.restlet.representation.StringRepresentation; |
| import org.restlet.representation.WriterRepresentation; |
| import org.restlet.resource.ResourceException; |
| |
| import static org.apache.zest.functional.Iterables.iterable; |
| |
| /** |
| * Get events in various formats. |
| * <p> |
| * The feed is paged, with one |
| * current set page, one working set page, and the rest being archive pages that never change. The links "next", "previous", |
| * "first" and "last" are used as expected per the Atom spec. |
| * </p> |
| * <pre><code> |
| * / = current set of most recent events (event range: count-pagesize to count) |
| * /n,m = events from index n to index m. These are archive pages. |
| * /n = working set page, where n is the first event index to be presented |
| * </code></pre> |
| */ |
| public class DomainEventSourceResource |
| extends Restlet |
| { |
| EventSource source; |
| |
| public DomainEventSourceResource( @Service @Tagged("domain") EventSource source ) |
| { |
| this.source = source; |
| } |
| |
| @Override |
| public void handle( Request request, Response response ) |
| { |
| long eventCount = source.count(); |
| long pageSize = 10; |
| long startEvent = -1; |
| long endEvent = -1; |
| long limit = pageSize; |
| |
| final List<UnitOfWorkDomainEventsValue> eventsValues = new ArrayList<UnitOfWorkDomainEventsValue>(); |
| |
| final Feed feed = new Feed(); |
| feed.setBaseReference( request.getResourceRef().getParentRef() ); |
| List<Link> links = feed.getLinks(); |
| |
| String remainingPart = request.getResourceRef().getRemainingPart(); |
| if (remainingPart.equals( "/" )) |
| { |
| // Current set - always contains the last "pageSize" events |
| startEvent = Math.max( 0, eventCount - pageSize - 1 ); |
| |
| feed.setTitle( new Text( "Current set" ) ); |
| |
| if (startEvent > 0) |
| { |
| long previousStart = Math.max(0, startEvent-pageSize); |
| long previousEnd = startEvent-1; |
| |
| Link link = new Link( new Reference( previousStart+","+previousEnd ), new Relation( "previous" ), MediaType.APPLICATION_ATOM ); |
| link.setTitle( "Previous page" ); |
| links.add( link ); |
| } |
| |
| } else |
| { |
| // Archive |
| String[] indices = remainingPart.substring(1).split( "," ); |
| |
| if (indices.length == 1) |
| { |
| // Working set |
| startEvent = Long.parseLong( indices[0] ); |
| endEvent = startEvent + pageSize - 1; |
| limit = pageSize; |
| feed.setTitle( new Text("Working set") ); |
| } else if (indices.length == 2) |
| { |
| feed.setTitle( new Text("Archive page") ); |
| startEvent = Long.parseLong( indices[0] ); |
| endEvent = Long.parseLong( indices[1] ); |
| limit = 1+endEvent-startEvent; |
| |
| } else |
| throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND ); |
| |
| if (startEvent > 0) |
| { |
| long previousStart = Math.max(0, startEvent-pageSize); |
| long previousEnd = startEvent-1; |
| |
| Link link = new Link( new Reference( previousStart+","+previousEnd ), new Relation( "previous" ), MediaType.APPLICATION_ATOM ); |
| link.setTitle( "Previous page" ); |
| links.add( link ); |
| } |
| |
| long nextStart = endEvent+1; |
| long nextEnd = nextStart+pageSize-1; |
| |
| if (nextStart < eventCount) |
| if (nextEnd >= eventCount) |
| { |
| Link next = new Link( new Reference( nextStart+"" ), new Relation( "next" ), MediaType.APPLICATION_ATOM ); |
| next.setTitle( "Working set" ); |
| links.add( next ); |
| } else |
| { |
| Link next = new Link( new Reference( nextStart+","+nextEnd ), new Relation( "next" ), MediaType.APPLICATION_ATOM ); |
| next.setTitle( "Next page" ); |
| links.add( next ); |
| } |
| } |
| |
| try |
| { |
| source.events( startEvent, limit ).transferTo( Outputs.collection( eventsValues ) ); |
| } catch (Throwable throwable) |
| { |
| throw new ResourceException( Status.SERVER_ERROR_INTERNAL, throwable ); |
| } |
| |
| Link last = new Link( new Reference( "0,"+(pageSize-1) ), new Relation( "last" ), MediaType.APPLICATION_ATOM ); |
| last.setTitle( "Last archive page" ); |
| links.add( last ); |
| |
| Link first = new Link( new Reference( "." ), new Relation( "first" ), MediaType.APPLICATION_ATOM ); |
| first.setTitle( "Current set" ); |
| links.add( first ); |
| |
| /* |
| if (previousPage != -1) |
| { |
| Link link = new Link( new Reference( ""+previousPage ), new Relation( "prev-archive" ), MediaType.APPLICATION_ATOM ); |
| link.setTitle( "Previous archive page" ); |
| links.add( link ); |
| } |
| if (nextPage != -1) |
| { |
| Link link = new Link( new Reference( "" + nextPage ), new Relation( "next-archive" ), MediaType.APPLICATION_ATOM ); |
| link.setTitle( "Next archive page" ); |
| links.add( link ); |
| } |
| else if (startEvent != workingSetOffset) |
| { |
| Link next = new Link( new Reference( "" ), new Relation( "next" ), MediaType.APPLICATION_ATOM ); |
| next.setTitle( "Next page" ); |
| links.add( next ); |
| } |
| */ |
| |
| Date lastModified = null; |
| for (UnitOfWorkDomainEventsValue eventsValue : eventsValues) |
| { |
| Entry entry = new Entry(); |
| entry.setTitle( new Text( eventsValue.usecase().get() + "(" + eventsValue.user().get() + ")" ) ); |
| entry.setPublished( new Date( eventsValue.timestamp().get() ) ); |
| entry.setModificationDate( lastModified = new Date( eventsValue.timestamp().get() ) ); |
| entry.setId( Long.toString( startEvent + 1 ) ); |
| startEvent++; |
| Content content = new Content(); |
| content.setInlineContent( new StringRepresentation( eventsValue.toString(), MediaType.APPLICATION_JSON ) ); |
| entry.setContent( content ); |
| feed.getEntries().add( entry ); |
| } |
| |
| feed.setModificationDate( lastModified ); |
| |
| MediaType mediaType = request.getClientInfo().getPreferredMediaType( Iterables.toList( iterable( MediaType.TEXT_HTML, MediaType.APPLICATION_ATOM ) )); |
| |
| if (MediaType.APPLICATION_ATOM.equals( mediaType )) |
| { |
| WriterRepresentation representation = new WriterRepresentation( MediaType.APPLICATION_ATOM ) |
| { |
| @Override |
| public void write( final Writer writer ) throws IOException |
| { |
| feed.write( writer ); |
| } |
| }; |
| representation.setCharacterSet( CharacterSet.UTF_8 ); |
| response.setEntity( representation ); |
| } else |
| { |
| WriterRepresentation representation = new WriterRepresentation(MediaType.TEXT_HTML) |
| { |
| @Override |
| public void write( Writer writer ) throws IOException |
| { |
| writer.append( "<html><head><title>Events</title></head><body>" ); |
| |
| for( Link link : feed.getLinks() ) |
| { |
| writer.append( "<a href=\"").append( link.getHref().getPath()).append( "\">" ); |
| writer.append( link.getTitle() ); |
| writer.append( "</a><br/>" ); |
| } |
| |
| writer.append( "<ol>" ); |
| for( Entry entry : feed.getEntries() ) |
| { |
| writer.append( "<li>" ).append( entry.getTitle().toString() ).append( "</li>" ); |
| } |
| writer.append( "</ol></body>" ); |
| } |
| }; |
| representation.setCharacterSet( CharacterSet.UTF_8 ); |
| response.setEntity( representation ); |
| } |
| |
| /* |
| } else |
| { |
| throw new ResourceException( Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE ); |
| } |
| */ |
| } |
| } |