001/*
002 * flattr4j - A Java library for Flattr
003 *
004 * Copyright (C) 2011 Richard "Shred" Körber
005 *   http://flattr4j.shredzone.org
006 *
007 * This program is free software: you can redistribute it and/or modify
008 * it under the terms of the GNU General Public License / GNU Lesser
009 * General Public License as published by the Free Software Foundation,
010 * either version 3 of the License, or (at your option) any later version.
011 *
012 * Licensed under the Apache License, Version 2.0 (the "License");
013 * you may not use this file except in compliance with the License.
014 *
015 * This program is distributed in the hope that it will be useful,
016 * but WITHOUT ANY WARRANTY; without even the implied warranty of
017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
018 *
019 */
020package org.shredzone.flattr4j.impl;
021
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.List;
026
027import org.shredzone.flattr4j.FlattrService;
028import org.shredzone.flattr4j.connector.Connection;
029import org.shredzone.flattr4j.connector.Connector;
030import org.shredzone.flattr4j.connector.FlattrObject;
031import org.shredzone.flattr4j.connector.RateLimit;
032import org.shredzone.flattr4j.connector.RequestType;
033import org.shredzone.flattr4j.exception.FlattrException;
034import org.shredzone.flattr4j.model.Activity;
035import org.shredzone.flattr4j.model.AutoSubmission;
036import org.shredzone.flattr4j.model.Category;
037import org.shredzone.flattr4j.model.Flattr;
038import org.shredzone.flattr4j.model.Language;
039import org.shredzone.flattr4j.model.MiniThing;
040import org.shredzone.flattr4j.model.SearchQuery;
041import org.shredzone.flattr4j.model.SearchResult;
042import org.shredzone.flattr4j.model.Submission;
043import org.shredzone.flattr4j.model.Subscription;
044import org.shredzone.flattr4j.model.Thing;
045import org.shredzone.flattr4j.model.ThingId;
046import org.shredzone.flattr4j.model.User;
047import org.shredzone.flattr4j.model.UserId;
048
049/**
050 * Default implementation of {@link FlattrService}.
051 *
052 * @author Richard "Shred" Körber
053 */
054public class FlattrServiceImpl implements FlattrService {
055    private final Connector connector;
056
057    private RateLimit lastRateLimit = new RateLimit();
058    private boolean fullMode = false;
059
060    public FlattrServiceImpl(Connector connector) {
061        this.connector = connector;
062    }
063
064    /**
065     * Returns the {@link Connector} used for calling the API.
066     *
067     * @return {@link Connector}
068     */
069    protected Connector getConnector() {
070        return connector;
071    }
072
073    @Override
074    public void setFullMode(boolean full) {
075        this.fullMode = full;
076    }
077
078    @Override
079    public boolean isFullMode() {
080        return fullMode;
081    }
082
083    @Override
084    public ThingId create(Submission thing) throws FlattrException {
085        if (thing == null)
086            throw new IllegalArgumentException("thing is required");
087
088        if (thing instanceof AutoSubmission && ((AutoSubmission) thing).getUser() != null)
089            throw new IllegalArgumentException("cannot create a thing on behalf of a user");
090
091        FlattrObject data = getConnector().create(RequestType.POST)
092                .call("things")
093                .data(thing.toFlattrObject())
094                .rateLimit(lastRateLimit)
095                .singleResult();
096
097        return Thing.withId(String.valueOf(data.getInt("id")));
098    }
099
100    @Override
101    public void update(Thing thing) throws FlattrException {
102        if (thing == null)
103            throw new IllegalArgumentException("thing is required");
104
105        FlattrObject update = thing.toUpdate();
106
107        if (update != null) { // Thing was modified.
108            update.getJSONObject().put("_method", "patch");
109            getConnector().create(RequestType.POST)
110                    .call("things/:id")
111                    .parameter("id", thing.getThingId())
112                    .rateLimit(lastRateLimit)
113                    .data(update)
114                    .result();
115        }
116    }
117
118    @Override
119    public void delete(ThingId thingId) throws FlattrException {
120        if (thingId == null || thingId.getThingId().length() == 0)
121            throw new IllegalArgumentException("thing id is required");
122
123        getConnector().create(RequestType.DELETE)
124                .call("things/:id")
125                .parameter("id", thingId.getThingId())
126                .rateLimit(lastRateLimit)
127                .result();
128    }
129
130    @Override
131    public MiniThing flattr(AutoSubmission submission) throws FlattrException {
132        return flattr(submission.toUrl());
133    }
134
135    @Override
136    public MiniThing flattr(String url) throws FlattrException {
137        if (url == null || url.length() == 0)
138            throw new IllegalArgumentException("url is required");
139
140        FlattrObject data = new FlattrObject();
141        data.put("url", url);
142
143        FlattrObject result = getConnector().create(RequestType.POST)
144                .call("flattr")
145                .data(data)
146                .rateLimit(lastRateLimit)
147                .singleResult();
148
149        return new MiniThing(result.getFlattrObject("thing"));
150    }
151
152    @Override
153    public MiniThing flattr(ThingId thingId) throws FlattrException {
154        if (thingId == null || thingId.getThingId().length() == 0)
155            throw new IllegalArgumentException("thingId is required");
156
157        FlattrObject result = getConnector().create(RequestType.POST)
158                .call("things/:id/flattr")
159                .parameter("id", thingId.getThingId())
160                .rateLimit(lastRateLimit)
161                .singleResult();
162
163        return new MiniThing(result.getFlattrObject("thing"));
164    }
165
166    @Override
167    public User getMyself() throws FlattrException {
168        return new User(getConnector().create()
169                .call("user")
170                .rateLimit(lastRateLimit)
171                .singleResult());
172    }
173
174    @Override
175    public List<Thing> getMyThings() throws FlattrException {
176        return getMyThings(null, null);
177    }
178
179    @Override
180    public List<Thing> getMyThings(Integer count, Integer page) throws FlattrException {
181        Connection conn = getConnector().create()
182                .call("user/things")
183                .rateLimit(lastRateLimit);
184
185        setupFullMode(conn);
186
187        if (count != null) {
188            conn.query("count", count.toString());
189        }
190
191        if (page != null) {
192            conn.query("page", page.toString());
193        }
194
195        List<Thing> list = new ArrayList<Thing>();
196        for (FlattrObject data : conn.result()) {
197            list.add(new Thing(data));
198        }
199        return Collections.unmodifiableList(list);
200    }
201
202    @Override
203    public List<Flattr> getMyFlattrs() throws FlattrException {
204        return getMyFlattrs(null, null);
205    }
206
207    @Override
208    public List<Flattr> getMyFlattrs(Integer count, Integer page) throws FlattrException {
209        Connection conn = getConnector().create()
210                .call("user/flattrs")
211                .rateLimit(lastRateLimit);
212
213        setupFullMode(conn);
214
215        if (count != null) {
216            conn.query("count", count.toString());
217        }
218
219        if (page != null) {
220            conn.query("page", page.toString());
221        }
222
223        List<Flattr> list = new ArrayList<Flattr>();
224        for (FlattrObject data : conn.result()) {
225            list.add(new Flattr(data));
226        }
227        return Collections.unmodifiableList(list);
228    }
229
230    @Override
231    public Thing getThing(ThingId thingId) throws FlattrException {
232        if (thingId == null || thingId.getThingId().length() == 0)
233            throw new IllegalArgumentException("thingId is required");
234
235        Connection conn = getConnector().create()
236                .call("things/:id")
237                .parameter("id", thingId.getThingId())
238                .rateLimit(lastRateLimit);
239
240        setupFullMode(conn);
241
242        return new Thing(conn.singleResult());
243    }
244
245    @Override
246    public Thing getThingByUrl(String url) throws FlattrException {
247        if (url == null || url.length() == 0)
248            throw new IllegalArgumentException("url is required");
249
250        FlattrObject data = getConnector().create()
251                .call("things/lookup/")
252                .query("url", url)
253                .rateLimit(lastRateLimit)
254                .singleResult();
255
256        if (data.has("message") && "not_found".equals(data.get("message"))) {
257            return null;
258        }
259
260        return new Thing(data);
261    }
262
263    @Override
264    public Thing getThingBySubmission(AutoSubmission submission) throws FlattrException {
265        return getThingByUrl(submission.toUrl());
266    }
267
268    @Override
269    public List<Thing> getThings(UserId user) throws FlattrException {
270        return getThings(user, null, null);
271    }
272
273    @Override
274    public List<Thing> getThings(UserId user, Integer count, Integer page) throws FlattrException {
275        if (user == null || user.getUserId().length() == 0)
276            throw new IllegalArgumentException("user is required");
277
278        Connection conn = getConnector().create()
279                .call("users/:username/things")
280                .parameter("username", user.getUserId())
281                .rateLimit(lastRateLimit);
282
283        setupFullMode(conn);
284
285        if (count != null) {
286            conn.query("count", count.toString());
287        }
288
289        if (page != null) {
290            conn.query("page", page.toString());
291        }
292
293        List<Thing> list = new ArrayList<Thing>();
294        for (FlattrObject data : conn.result()) {
295            list.add(new Thing(data));
296        }
297        return Collections.unmodifiableList(list);
298    }
299
300    @Override
301    public List<Thing> getThings(Collection<? extends ThingId> thingIds) throws FlattrException {
302        if (thingIds.isEmpty()) {
303            // No IDs, so the result will be empty anyways
304            return Collections.emptyList();
305        }
306
307        String[] params = new String[thingIds.size()];
308        int ix = 0;
309        for (ThingId thingId : thingIds) {
310            params[ix++] = thingId.getThingId();
311        }
312
313        Connection conn = getConnector().create()
314                        .call("things/:ids")
315                        .parameterArray("ids", params)
316                        .rateLimit(lastRateLimit);
317
318        setupFullMode(conn);
319
320        List<Thing> list = new ArrayList<Thing>();
321        for (FlattrObject data : conn.result()) {
322            list.add(new Thing(data));
323        }
324        return Collections.unmodifiableList(list);
325    }
326
327    @Override
328    public SearchResult searchThings(SearchQuery query, Integer count, Integer page) throws FlattrException {
329        Connection conn = getConnector().create()
330                        .call("things/search")
331                        .rateLimit(lastRateLimit);
332
333        if (query != null) {
334            query.setupConnection(conn);
335        }
336
337        setupFullMode(conn);
338
339        if (count != null) {
340            conn.query("count", count.toString());
341        }
342
343        if (page != null) {
344            conn.query("page", page.toString());
345        }
346
347        return new SearchResult(conn.singleResult());
348    }
349
350    @Override
351    public User getUser(UserId user) throws FlattrException {
352        if (user == null || user.getUserId().length() == 0)
353            throw new IllegalArgumentException("user is required");
354
355        return new User(getConnector().create()
356                .call("users/:username")
357                .parameter("username", user.getUserId())
358                .rateLimit(lastRateLimit)
359                .singleResult());
360    }
361
362    @Override
363    public List<Flattr> getFlattrs(UserId user) throws FlattrException {
364        return getFlattrs(user, null, null);
365    }
366
367    @Override
368    public List<Flattr> getFlattrs(UserId userId, Integer count, Integer page) throws FlattrException {
369        if (userId == null || userId.getUserId().length() == 0)
370            throw new IllegalArgumentException("userId is required");
371
372        Connection conn = getConnector().create()
373                .call("users/:username/flattrs")
374                .parameter("username", userId.getUserId())
375                .rateLimit(lastRateLimit);
376
377        setupFullMode(conn);
378
379        if (count != null) {
380            conn.query("count", count.toString());
381        }
382
383        if (page != null) {
384            conn.query("page", page.toString());
385        }
386
387        List<Flattr> list = new ArrayList<Flattr>();
388        for (FlattrObject data : conn.result()) {
389            list.add(new Flattr(data));
390        }
391        return Collections.unmodifiableList(list);
392    }
393
394    @Override
395    public List<Flattr> getFlattrs(ThingId thingId) throws FlattrException {
396        return getFlattrs(thingId, null, null);
397    }
398
399    @Override
400    public List<Flattr> getFlattrs(ThingId thingId, Integer count, Integer page) throws FlattrException {
401        if (thingId == null || thingId.getThingId().length() == 0)
402            throw new IllegalArgumentException("thingId is required");
403
404        Connection conn = getConnector().create()
405                .call("things/:id/flattrs")
406                .parameter("id", thingId.getThingId())
407                .rateLimit(lastRateLimit);
408
409        setupFullMode(conn);
410
411        if (count != null) {
412            conn.query("count", count.toString());
413        }
414
415        if (page != null) {
416            conn.query("page", page.toString());
417        }
418
419        List<Flattr> list = new ArrayList<Flattr>();
420        for (FlattrObject data : conn.result()) {
421            list.add(new Flattr(data));
422        }
423        return Collections.unmodifiableList(list);
424    }
425
426    @Override
427    public List<Activity> getActivities(UserId user, Activity.Type type) throws FlattrException {
428        if (user == null || user.getUserId().length() == 0)
429            throw new IllegalArgumentException("userId is required");
430
431        Connection conn = getConnector().create()
432                        .call("users/:username/activities.as")
433                        .parameter("username", user.getUserId())
434                        .rateLimit(lastRateLimit);
435
436        if (type != null) {
437            conn.query("type", type.name().toLowerCase());
438        }
439
440        FlattrObject data = conn.singleResult();
441        List<Activity> list = new ArrayList<Activity>();
442        for (FlattrObject item : data.getObjects("items")) {
443            list.add(new Activity(item));
444        }
445        return Collections.unmodifiableList(list);
446    }
447
448    @Override
449    public List<Activity> getMyActivities(Activity.Type type) throws FlattrException {
450        Connection conn = getConnector().create()
451                        .call("user/activities.as")
452                        .rateLimit(lastRateLimit);
453
454        if (type != null) {
455            conn.query("type", type.name().toLowerCase());
456        }
457
458        FlattrObject data = conn.singleResult();
459        List<Activity> list = new ArrayList<Activity>();
460        for (FlattrObject item : data.getObjects("items")) {
461            list.add(new Activity(item));
462        }
463        return Collections.unmodifiableList(list);
464    }
465
466    @Override
467    public List<Subscription> getMySubscriptions() throws FlattrException {
468        Connection conn = getConnector().create()
469                        .call("user/subscriptions")
470                        .rateLimit(lastRateLimit);
471
472        List<Subscription> list = new ArrayList<Subscription>();
473        for (FlattrObject item : conn.result()) {
474            list.add(new Subscription(item));
475        }
476        return Collections.unmodifiableList(list);
477    }
478
479    @Override
480    public Subscription getSubscription(ThingId thingId) throws FlattrException {
481        if (thingId == null || thingId.getThingId().length() == 0) {
482            throw new IllegalArgumentException("thingId is required");
483        }
484
485        Connection conn = getConnector().create()
486                        .call("user/subscriptions")
487                        .rateLimit(lastRateLimit);
488
489        for (FlattrObject item : conn.result()) {
490            if (thingId.getThingId().equals(String.valueOf(item.getFlattrObject("thing").getInt("id")))) {
491                return new Subscription(item);
492            }
493        }
494
495        return null;
496    }
497
498    @Override
499    public void subscribe(ThingId thingId) throws FlattrException {
500        if (thingId == null || thingId.getThingId().length() == 0)
501            throw new IllegalArgumentException("thingId is required");
502
503        getConnector().create(RequestType.POST)
504                        .call("things/:id/subscriptions")
505                        .parameter("id", thingId.getThingId())
506                        .rateLimit(lastRateLimit)
507                        .result();
508    }
509
510    @Override
511    public void unsubscribe(ThingId thingId) throws FlattrException {
512        if (thingId == null || thingId.getThingId().length() == 0)
513            throw new IllegalArgumentException("thingId is required");
514
515        getConnector().create(RequestType.DELETE)
516                        .call("things/:id/subscriptions")
517                        .parameter("id", thingId.getThingId())
518                        .rateLimit(lastRateLimit)
519                        .result();
520    }
521
522    @Override
523    public boolean toggleSubscription(ThingId thingId) throws FlattrException {
524        if (thingId == null || thingId.getThingId().length() == 0)
525            throw new IllegalArgumentException("thingId is required");
526
527        FlattrObject data = getConnector().create(RequestType.PUT)
528                        .call("things/:id/subscriptions")
529                        .parameter("id", thingId.getThingId())
530                        .rateLimit(lastRateLimit)
531                        .singleResult();
532
533        return "paused".equals(data.get("message"));
534    }
535
536    @Override
537    public void pauseSubscription(ThingId thingId, boolean paused) throws FlattrException {
538        boolean current = toggleSubscription(thingId);
539
540        if (current != paused) {
541            toggleSubscription(thingId);
542        }
543    }
544
545    @Override
546    public List<Category> getCategories() throws FlattrException {
547        Connection conn = getConnector().create()
548                .call("categories")
549                .rateLimit(lastRateLimit);
550
551        List<Category> list = new ArrayList<Category>();
552        for (FlattrObject data : conn.result()) {
553            list.add(new Category(data));
554        }
555        return Collections.unmodifiableList(list);
556    }
557
558    @Override
559    public List<Language> getLanguages() throws FlattrException {
560        Connection conn = getConnector().create()
561                .call("languages")
562                .rateLimit(lastRateLimit);
563
564        List<Language> list = new ArrayList<Language>();
565        for (FlattrObject data : conn.result()) {
566            list.add(new Language(data));
567        }
568        return Collections.unmodifiableList(list);
569    }
570
571    @Override
572    public RateLimit getCurrentRateLimit() throws FlattrException {
573        Connection conn = getConnector().create()
574                        .call("rate_limit");
575
576        return new RateLimit(conn.singleResult());
577    }
578
579    @Override
580    public RateLimit getLastRateLimit() {
581        return lastRateLimit;
582    }
583
584    /**
585     * Sets the {@link Connection} according to the current full mode.
586     *
587     * @param conn
588     *            {@link Connection} to set
589     */
590    protected void setupFullMode(Connection conn) {
591        if (fullMode) {
592            conn.query("full", "1");
593        }
594    }
595
596}