99import dev .learning .xapi .model .Statement ;
1010import dev .learning .xapi .model .StatementResult ;
1111import java .net .URI ;
12+ import java .nio .charset .StandardCharsets ;
1213import java .time .Instant ;
1314import java .util .ArrayList ;
15+ import java .util .Base64 ;
1416import java .util .Collection ;
1517import java .util .List ;
18+ import java .util .Objects ;
1619import java .util .Optional ;
1720import java .util .UUID ;
18- import java .util .stream .StreamSupport ;
21+ import org .springframework .data .domain .PageRequest ;
22+ import org .springframework .data .domain .Pageable ;
23+ import org .springframework .data .domain .Slice ;
24+ import org .springframework .data .domain .Sort ;
1925import org .slf4j .Logger ;
2026import org .slf4j .LoggerFactory ;
2127import org .springframework .stereotype .Service ;
2935@ Service
3036public class StatementService {
3137
38+ private static final int PAGE_SIZE = 10 ;
39+
3240 private final Logger log = LoggerFactory .getLogger (StatementService .class );
3341
3442 private final StatementRepository repository ;
@@ -73,12 +81,63 @@ public StatementResult getStatements() {
7381
7482 log .info ("get statements" );
7583
76- // add custom logic here...
84+ return buildStatementResult ( 0 , null );
7785
78- final var statements = StreamSupport .stream (repository .findAll ().spliterator (), false ).limit (10 )
79- .map (e -> convertToStatement (e )).toList ();
86+ }
8087
81- return StatementResult .builder ().statements (statements ).more (URI .create ("" )).build ();
88+ /**
89+ * Get multiple Statements since a specific time.
90+ *
91+ * @param since return statements stored since this instant (inclusive)
92+ *
93+ * @return populated StatementResults
94+ */
95+ public StatementResult getStatementsSince (Instant since ) {
96+
97+ log .info ("get statements since: {}" , since );
98+
99+ return buildStatementResult (0 , since );
100+
101+ }
102+
103+ /**
104+ * Get multiple Statements using a more token.
105+ *
106+ * @param moreToken the more token indicating where to continue retrieval
107+ *
108+ * @return populated StatementResults
109+ */
110+ public StatementResult getStatementsMore (String moreToken ) {
111+
112+ log .info ("get statements more: {}" , moreToken );
113+
114+ final var more = decodeMoreToken (moreToken );
115+
116+ return buildStatementResult (more .page (), more .since ());
117+
118+ }
119+
120+ private StatementResult buildStatementResult (int page , Instant since ) {
121+
122+ final Pageable pageable = PageRequest .of (page , PAGE_SIZE ,
123+ Sort .by (Sort .Direction .ASC , "stored" ).and (Sort .by ("id" )));
124+
125+ final Slice <StatementEntity > slice ;
126+ if (since == null ) {
127+ slice = repository .findAllByOrderByStoredAscIdAsc (pageable );
128+ } else {
129+ slice = repository .findByStoredGreaterThanEqualOrderByStoredAscIdAsc (since , pageable );
130+ }
131+
132+ final var statements = slice .getContent ().stream ()
133+ .map (this ::convertToStatement )
134+ .filter (Objects ::nonNull )
135+ .toList ();
136+
137+ final var more = slice .hasNext () ? URI .create ("/xapi/statements?more="
138+ + encodeMoreToken (page + 1 , since )) : URI .create ("" );
139+
140+ return StatementResult .builder ().statements (statements ).more (more ).build ();
82141
83142 }
84143
@@ -94,8 +153,10 @@ public void processStatement(UUID statementId, Statement statement) {
94153
95154 // add custom logic here...
96155
156+ final Instant stored = Instant .now ();
157+
97158 repository .save (new StatementEntity (statementId ,
98- mapper .valueToTree (statement .withId (statementId ).withStored (Instant . now ())) ));
159+ mapper .valueToTree (statement .withId (statementId ).withStored (stored )), stored ));
99160
100161 }
101162
@@ -113,21 +174,56 @@ public Collection<UUID> processStatements(List<Statement> statements) {
113174 for (final Statement statement : statements ) {
114175 log .info ("processing statement: {}" , statement );
115176
177+ final Instant stored = Instant .now ();
178+
116179 if (statement .getId () == null ) {
117- processedStatements .add (statement .withId (UUID .randomUUID ()).withStored (Instant . now () ));
180+ processedStatements .add (statement .withId (UUID .randomUUID ()).withStored (stored ));
118181 } else {
119- processedStatements .add (statement .withStored (Instant . now () ));
182+ processedStatements .add (statement .withStored (stored ));
120183 }
121184 }
122185
123186 // add custom logic here...
124187
125188 repository .saveAll (processedStatements .stream ()
126- .map (s -> new StatementEntity (s .getId (), mapper .valueToTree (s ))).toList ());
189+ .map (s -> new StatementEntity (s .getId (), mapper .valueToTree (s ), s . getStored () )).toList ());
127190
128191 return processedStatements .stream ().map (s -> s .getId ()).toList ();
129192 }
130193
194+ private String encodeMoreToken (int page , Instant since ) {
195+
196+ final var sinceValue = since == null ? "" : since .toString ();
197+ final var payload = page + "|" + sinceValue ;
198+
199+ return Base64 .getUrlEncoder ().encodeToString (payload .getBytes (StandardCharsets .UTF_8 ));
200+
201+ }
202+
203+ private MoreToken decodeMoreToken (String token ) {
204+
205+ try {
206+ final var decoded = new String (Base64 .getUrlDecoder ().decode (token ), StandardCharsets .UTF_8 );
207+ final var parts = decoded .split ("\\ |" , -1 );
208+
209+ final var page = Integer .parseInt (parts [0 ]);
210+ final Instant since ;
211+
212+ if (parts .length > 1 && !parts [1 ].isBlank ()) {
213+ since = Instant .parse (parts [1 ]);
214+ } else {
215+ since = null ;
216+ }
217+
218+ return new MoreToken (page , since );
219+ } catch (IllegalArgumentException ex ) {
220+ throw ex ;
221+ } catch (Exception ex ) {
222+ throw new IllegalArgumentException ("Invalid more token" , ex );
223+ }
224+
225+ }
226+
131227 private Statement convertToStatement (StatementEntity statementEntity ) {
132228
133229 try {
@@ -142,4 +238,6 @@ private Statement convertToStatement(StatementEntity statementEntity) {
142238
143239 }
144240
241+ private record MoreToken (int page , Instant since ) {}
242+
145243}
0 commit comments