• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

88250 / solo / #729

06 Sep 2024 06:22PM UTC coverage: 54.892% (+0.04%) from 54.856%
#729

push

web-flow
Create jekyll-gh-pages.yml

4533 of 8258 relevant lines covered (54.89%)

0.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.11
/src/main/java/org/b3log/solo/service/ArticleMgmtService.java
1
/*
2
 * Solo - A small and beautiful blogging system written in Java.
3
 * Copyright (c) 2010-present, b3log.org
4
 *
5
 * Solo is licensed under Mulan PSL v2.
6
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
7
 * You may obtain a copy of Mulan PSL v2 at:
8
 *         http://license.coscl.org.cn/MulanPSL2
9
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
10
 * See the Mulan PSL v2 for more details.
11
 */
12
package org.b3log.solo.service;
13

14
import org.apache.commons.lang3.StringUtils;
15
import org.apache.commons.lang3.time.DateFormatUtils;
16
import org.apache.commons.lang3.time.DateUtils;
17
import org.apache.logging.log4j.Level;
18
import org.apache.logging.log4j.LogManager;
19
import org.apache.logging.log4j.Logger;
20
import org.b3log.latke.Keys;
21
import org.b3log.latke.event.Event;
22
import org.b3log.latke.event.EventManager;
23
import org.b3log.latke.ioc.Inject;
24
import org.b3log.latke.repository.RepositoryException;
25
import org.b3log.latke.repository.Transaction;
26
import org.b3log.latke.service.LangPropsService;
27
import org.b3log.latke.service.ServiceException;
28
import org.b3log.latke.service.annotation.Service;
29
import org.b3log.latke.util.Ids;
30
import org.b3log.solo.event.B3ArticleSender;
31
import org.b3log.solo.event.EventTypes;
32
import org.b3log.solo.model.*;
33
import org.b3log.solo.repository.*;
34
import org.b3log.solo.util.GitHubs;
35
import org.b3log.solo.util.Statics;
36
import org.json.JSONArray;
37
import org.json.JSONException;
38
import org.json.JSONObject;
39

40
import java.text.ParseException;
41
import java.util.ArrayList;
42
import java.util.Arrays;
43
import java.util.List;
44

45
import static org.b3log.solo.model.Article.*;
46

47
/**
48
 * Article management service.
49
 *
50
 * @author <a href="http://88250.b3log.org">Liang Ding</a>
51
 * @version 1.3.4.2, Jul 8, 2020
52
 * @since 0.3.5
53
 */
54
@Service
55
public class ArticleMgmtService {
1✔
56

57
    /**
58
     * Logger.
59
     */
60
    private static final Logger LOGGER = LogManager.getLogger(ArticleMgmtService.class);
1✔
61

62
    /**
63
     * Article query service.
64
     */
65
    @Inject
66
    private ArticleQueryService articleQueryService;
67

68
    /**
69
     * Article repository.
70
     */
71
    @Inject
72
    private ArticleRepository articleRepository;
73

74
    /**
75
     * Page repository.
76
     */
77
    @Inject
78
    private PageRepository pageRepository;
79

80
    /**
81
     * User repository.
82
     */
83
    @Inject
84
    private UserRepository userRepository;
85

86
    /**
87
     * Tag repository.
88
     */
89
    @Inject
90
    private TagRepository tagRepository;
91

92
    /**
93
     * Archive date repository.
94
     */
95
    @Inject
96
    private ArchiveDateRepository archiveDateRepository;
97

98
    /**
99
     * Archive date-Article repository.
100
     */
101
    @Inject
102
    private ArchiveDateArticleRepository archiveDateArticleRepository;
103

104
    /**
105
     * Tag-Article repository.
106
     */
107
    @Inject
108
    private TagArticleRepository tagArticleRepository;
109

110
    /**
111
     * Category-tag repository.
112
     */
113
    @Inject
114
    private CategoryTagRepository categoryTagRepository;
115

116
    /**
117
     * Permalink query service.
118
     */
119
    @Inject
120
    private PermalinkQueryService permalinkQueryService;
121

122
    /**
123
     * Event manager.
124
     */
125
    @Inject
126
    private EventManager eventManager;
127

128
    /**
129
     * Language service.
130
     */
131
    @Inject
132
    private LangPropsService langPropsService;
133

134
    /**
135
     * Statistic management service.
136
     */
137
    @Inject
138
    private StatisticMgmtService statisticMgmtService;
139

140
    /**
141
     * Statistic query service.
142
     */
143
    @Inject
144
    private StatisticQueryService statisticQueryService;
145

146
    /**
147
     * Init service.
148
     */
149
    @Inject
150
    private InitService initService;
151

152
    /**
153
     * Tag management service.
154
     */
155
    @Inject
156
    private TagMgmtService tagMgmtService;
157

158
    /**
159
     * Option query service.
160
     */
161
    @Inject
162
    private OptionQueryService optionQueryService;
163

164
    /**
165
     * Option management service.
166
     */
167
    @Inject
168
    private OptionMgmtService optionMgmtService;
169

170
    /**
171
     * Refreshes GitHub repos. 同步拉取 GitHub 仓库 https://github.com/b3log/solo/issues/12514
172
     */
173
    public void refreshGitHub() {
174
        if (!initService.isInited()) {
×
175
            return;
×
176
        }
177

178
        final JSONObject preference = optionQueryService.getPreference();
×
179
        if (null == preference) {
×
180
            return;
×
181
        }
182

183
        if (!preference.optBoolean(Option.ID_C_PULL_GITHUB)) {
×
184
            return;
×
185
        }
186

187
        JSONObject admin;
188
        try {
189
            admin = userRepository.getAdmin();
×
190
        } catch (final Exception e) {
×
191
            return;
×
192
        }
×
193

194
        if (null == admin) {
×
195
            return;
×
196
        }
197

198
        final String githubId = admin.optString(UserExt.USER_GITHUB_ID);
×
199
        final JSONArray gitHubRepos = GitHubs.getGitHubRepos(githubId);
×
200
        if (null == gitHubRepos || gitHubRepos.isEmpty()) {
×
201
            return;
×
202
        }
203

204
        JSONObject githubReposOpt = optionQueryService.getOptionById(Option.ID_C_GITHUB_REPOS);
×
205
        if (null == githubReposOpt) {
×
206
            githubReposOpt = new JSONObject();
×
207
            githubReposOpt.put(Keys.OBJECT_ID, Option.ID_C_GITHUB_REPOS);
×
208
            githubReposOpt.put(Option.OPTION_CATEGORY, Option.CATEGORY_C_GITHUB);
×
209
        }
210
        githubReposOpt.put(Option.OPTION_VALUE, gitHubRepos.toString());
×
211

212
        try {
213
            optionMgmtService.addOrUpdateOption(githubReposOpt);
×
214
        } catch (final Exception e) {
×
215
            LOGGER.log(Level.ERROR, "Updates github repos option failed", e);
×
216
            return;
×
217
        }
×
218

219
        final StringBuilder contentBuilder = new StringBuilder();
×
220
        contentBuilder.append("<!-- 该页面会被定时任务自动覆盖,所以请勿手工更新 -->\n");
×
221
        contentBuilder.append("<!-- 如果你有更漂亮的排版方式,请发 issue 告诉我们 -->\n\n");
×
222
        for (int i = 0; i < gitHubRepos.length(); i++) {
×
223
            final JSONObject repo = gitHubRepos.optJSONObject(i);
×
224
            final String url = repo.optString("githubrepoHTMLURL");
×
225
            final String desc = repo.optString("githubrepoDescription");
×
226
            final String name = repo.optString("githubrepoName");
×
227
            final String stars = repo.optString("githubrepoStargazersCount");
×
228
            final String watchers = repo.optString("githubrepoWatchersCount");
×
229
            final String forks = repo.optString("githubrepoForksCount");
×
230
            final String lang = repo.optString("githubrepoLanguage");
×
231
            final String hp = repo.optString("githubrepoHomepage");
×
232

233
            String stat = "<span style=\"font-size: 12px;\">[🤩`{watchers}`]({url}/watchers \"关注数\")&nbsp;&nbsp;[⭐️`{stars}`]({url}/stargazers \"收藏数\")&nbsp;&nbsp;[🖖`{forks}`]({url}/network/members \"分叉数\")";
×
234
            stat = stat.replace("{watchers}", watchers).replace("{stars}", stars).replace("{url}", url).replace("{forks}", forks);
×
235
            if (StringUtils.isNotBlank(hp)) {
×
236
                stat += "&nbsp;&nbsp;[\uD83C\uDFE0`{hp}`]({hp} \"项目主页\")";
×
237
                stat = stat.replace("{hp}", hp);
×
238
            }
239
            stat += "</span>";
×
240
            contentBuilder.append("### " + (i + 1) + ". [" + name + "](" + url + ") <kbd title=\"主要编程语言\">" + lang + "</kbd> " + stat + "\n\n" + desc + "\n\n");
×
241
            if (i < gitHubRepos.length() - 1) {
×
242
                contentBuilder.append("\n\n---\n\n");
×
243
            }
244
        }
245
        final String content = contentBuilder.toString();
×
246

247
        try {
248
            final String permalink = "/my-github-repos";
×
249
            JSONObject article = articleRepository.getByPermalink(permalink);
×
250
            if (null == article) {
×
251
                article = new JSONObject();
×
252
                article.put(Article.ARTICLE_AUTHOR_ID, admin.optString(Keys.OBJECT_ID));
×
253
                article.put(Article.ARTICLE_TITLE, "我在 GitHub 上的开源项目");
×
254
                article.put(Article.ARTICLE_ABSTRACT, Article.getAbstractText(content));
×
255
                article.put(Article.ARTICLE_TAGS_REF, "开源,GitHub");
×
256
                article.put(Article.ARTICLE_PERMALINK, permalink);
×
257
                article.put(Article.ARTICLE_CONTENT, content);
×
258
                article.put(Article.ARTICLE_VIEW_PWD, "");
×
259
                article.put(Article.ARTICLE_STATUS, Article.ARTICLE_STATUS_C_PUBLISHED);
×
260
                article.put(Common.POST_TO_COMMUNITY, false);
×
261

262
                final JSONObject addArticleReq = new JSONObject();
×
263
                addArticleReq.put(Article.ARTICLE, article);
×
264
                addArticle(addArticleReq);
×
265
            } else {
×
266
                article.put(Article.ARTICLE_CONTENT, content);
×
267

268
                final String articleId = article.optString(Keys.OBJECT_ID);
×
269
                final Transaction transaction = articleRepository.beginTransaction();
×
270
                articleRepository.update(articleId, article);
×
271
                transaction.commit();
×
272
            }
273

274
            final Transaction transaction = pageRepository.beginTransaction();
×
275
            JSONObject page = pageRepository.getByPermalink(permalink);
×
276
            if (null == page) {
×
277
                page = new JSONObject();
×
278
                final int maxOrder = pageRepository.getMaxOrder();
×
279
                page.put(Page.PAGE_ORDER, maxOrder + 1);
×
280
                page.put(Page.PAGE_TITLE, "我的开源");
×
281
                page.put(Page.PAGE_OPEN_TARGET, "_self");
×
282
                page.put(Page.PAGE_PERMALINK, permalink);
×
283
                page.put(Page.PAGE_ICON, "/images/github-icon.png");
×
284
                pageRepository.add(page);
×
285
            } else {
×
286
                page.put(Page.PAGE_OPEN_TARGET, "_self");
×
287
                page.put(Page.PAGE_ICON, "/images/github-icon.png");
×
288
                pageRepository.update(page.optString(Keys.OBJECT_ID), page);
×
289
            }
290
            transaction.commit();
×
291
        } catch (final Exception e) {
×
292
            LOGGER.log(Level.ERROR, "Updates github repos page failed", e);
×
293
        }
×
294
    }
×
295

296
    /**
297
     * Pushes an article specified by the given article id to community.
298
     *
299
     * @param articleId the given article id
300
     */
301
    public void pushArticleToCommunity(final String articleId) {
302
        try {
303
            final JSONObject article = articleRepository.get(articleId);
×
304
            if (null == article) {
×
305
                return;
×
306
            }
307

308
            article.put(Common.POST_TO_COMMUNITY, true);
×
309
            final JSONObject data = new JSONObject().put(ARTICLE, article);
×
310
            B3ArticleSender.pushArticleToRhy(data);
×
311
        } catch (final Exception e) {
×
312
            LOGGER.log(Level.ERROR, "Pushes an article [id=" + articleId + "] to LianDi failed", e);
×
313
        }
×
314
    }
×
315

316
    /**
317
     * Cancels publish an article by the specified article id.
318
     *
319
     * @param articleId the specified article id
320
     * @throws ServiceException service exception
321
     */
322
    public void cancelPublishArticle(final String articleId) throws ServiceException {
323
        final Transaction transaction = articleRepository.beginTransaction();
1✔
324

325
        try {
326
            final JSONObject article = articleRepository.get(articleId);
1✔
327
            article.put(ARTICLE_STATUS, ARTICLE_STATUS_C_DRAFT);
1✔
328
            articleRepository.update(articleId, article, ARTICLE_STATUS);
1✔
329

330
            transaction.commit();
1✔
331
        } catch (final Exception e) {
×
332
            if (transaction.isActive()) {
×
333
                transaction.rollback();
×
334
            }
335

336
            LOGGER.log(Level.ERROR, "Cancels publish article failed", e);
×
337
            throw new ServiceException(e);
×
338
        }
1✔
339
    }
1✔
340

341
    /**
342
     * Puts an article specified by the given article id to top or cancel top.
343
     *
344
     * @param articleId the given article id
345
     * @param top       the specified flag, {@code true} to top, {@code false} to
346
     *                  cancel top
347
     * @throws ServiceException service exception
348
     */
349
    public void topArticle(final String articleId, final boolean top) throws ServiceException {
350
        final Transaction transaction = articleRepository.beginTransaction();
1✔
351

352
        try {
353
            final JSONObject topArticle = articleRepository.get(articleId);
1✔
354
            topArticle.put(ARTICLE_PUT_TOP, top);
1✔
355
            articleRepository.update(articleId, topArticle, ARTICLE_PUT_TOP);
1✔
356

357
            transaction.commit();
1✔
358
        } catch (final Exception e) {
×
359
            if (transaction.isActive()) {
×
360
                transaction.rollback();
×
361
            }
362

363
            LOGGER.log(Level.ERROR, "Can't put the article[oId{}] to top", articleId);
×
364
            throw new ServiceException(e);
×
365
        }
1✔
366
    }
1✔
367

368
    /**
369
     * Updates an article by the specified request json object.
370
     *
371
     * @param requestJSONObject the specified request json object, for example,
372
     *                          {
373
     *                          "article": {
374
     *                          "oId": "",
375
     *                          "articleTitle": "",
376
     *                          "articleAbstract": "",
377
     *                          "articleContent": "",
378
     *                          "articleTags": "tag1,tag2,tag3", // optional, default set "待分类"
379
     *                          "articlePermalink": "", // optional
380
     *                          "articleStatus": int, // 0: published, 1: draft
381
     *                          "articleSignId": "", // optional
382
     *                          "articleViewPwd": ""
383
     *                          }
384
     *                          }
385
     * @throws ServiceException service exception
386
     */
387
    public void updateArticle(final JSONObject requestJSONObject) throws ServiceException {
388
        final Transaction transaction = articleRepository.beginTransaction();
1✔
389

390
        try {
391
            final JSONObject article = requestJSONObject.getJSONObject(ARTICLE);
1✔
392
            String tagsString = article.optString(Article.ARTICLE_TAGS_REF);
1✔
393
            tagsString = Tag.formatTags(tagsString, 4);
1✔
394
            if (StringUtils.isBlank(tagsString)) {
1✔
395
                tagsString = "待分类";
×
396
            }
397
            article.put(Article.ARTICLE_TAGS_REF, tagsString);
1✔
398

399
            final String articleId = article.getString(Keys.OBJECT_ID);
1✔
400
            // Set permalink
401
            final JSONObject oldArticle = articleRepository.get(articleId);
1✔
402
            final String permalink = getPermalinkForUpdateArticle(oldArticle, article, oldArticle.optLong(ARTICLE_CREATED));
1✔
403
            article.put(ARTICLE_PERMALINK, permalink);
1✔
404

405
            processTagsForArticleUpdate(oldArticle, article);
1✔
406

407
            archiveDate(article);
1✔
408

409
            // Fill auto properties
410
            fillAutoProperties(oldArticle, article);
1✔
411
            // Set date
412
            article.put(ARTICLE_UPDATED, oldArticle.getLong(ARTICLE_UPDATED));
1✔
413
            final long now = System.currentTimeMillis();
1✔
414

415
            // The article to update has no sign
416
            if (!article.has(Article.ARTICLE_SIGN_ID)) {
1✔
417
                article.put(Article.ARTICLE_SIGN_ID, "0");
×
418
            }
419

420
            article.put(ARTICLE_UPDATED, now);
1✔
421

422
            final String articleImg1URL = getArticleImg1URL(article);
1✔
423
            article.put(ARTICLE_IMG1_URL, articleImg1URL);
1✔
424

425
            final String articleAbstractText = Article.getAbstractText(article);
1✔
426
            article.put(ARTICLE_ABSTRACT_TEXT, articleAbstractText);
1✔
427

428
            final boolean postToCommunity = article.optBoolean(Common.POST_TO_COMMUNITY);
1✔
429
            article.remove(Common.POST_TO_COMMUNITY);
1✔
430
            articleRepository.update(articleId, article);
1✔
431
            article.put(Common.POST_TO_COMMUNITY, postToCommunity);
1✔
432

433
            final boolean publishNewArticle = Article.ARTICLE_STATUS_C_DRAFT == oldArticle.optInt(ARTICLE_STATUS) && Article.ARTICLE_STATUS_C_PUBLISHED == article.optInt(ARTICLE_STATUS);
1✔
434
            final JSONObject eventData = new JSONObject();
1✔
435
            eventData.put(ARTICLE, article);
1✔
436
            if (publishNewArticle) {
1✔
437
                eventManager.fireEventAsynchronously(new Event<>(EventTypes.ADD_ARTICLE, eventData));
×
438
            } else {
439
                eventManager.fireEventAsynchronously(new Event<>(EventTypes.UPDATE_ARTICLE, eventData));
1✔
440
            }
441

442
            transaction.commit();
1✔
443

444
            Statics.clear();
1✔
445
        } catch (final ServiceException e) {
×
446
            if (transaction.isActive()) {
×
447
                transaction.rollback();
×
448
            }
449

450
            LOGGER.log(Level.ERROR, "Updates an article failed", e);
×
451
            throw e;
×
452
        } catch (final Exception e) {
×
453
            if (transaction.isActive()) {
×
454
                transaction.rollback();
×
455
            }
456

457
            LOGGER.log(Level.ERROR, "Updates an article failed", e);
×
458
            throw new ServiceException(e.getMessage());
×
459
        }
1✔
460
    }
1✔
461

462
    /**
463
     * Adds an article from the specified request json object.
464
     *
465
     * @param requestJSONObject the specified request json object, for example,
466
     *                          {
467
     *                          "article": {
468
     *                          "articleAuthorId": "",
469
     *                          "articleTitle": "",
470
     *                          "articleAbstract": "",
471
     *                          "articleContent": "",
472
     *                          "articleTags": "tag1,tag2,tag3",
473
     *                          "articleStatus": int, // 0: published, 1: draft
474
     *                          "articlePermalink": "", // optional
475
     *                          "postToCommunity": boolean, // optional
476
     *                          "articleSignId": "" // optional, default is "0",
477
     *                          "articleViewPwd": "",
478
     *                          "oId": "" // optional, generate it if not exists this key
479
     *                          }
480
     *                          }
481
     * @return generated article id
482
     * @throws ServiceException service exception
483
     */
484
    public String addArticle(final JSONObject requestJSONObject) throws ServiceException {
485
        final Transaction transaction = articleRepository.beginTransaction();
1✔
486

487
        try {
488
            final JSONObject article = requestJSONObject.getJSONObject(Article.ARTICLE);
1✔
489
            String ret = article.optString(Keys.OBJECT_ID);
1✔
490
            if (StringUtils.isBlank(ret)) {
1✔
491
                ret = Ids.genTimeMillisId();
1✔
492
                article.put(Keys.OBJECT_ID, ret);
1✔
493
            }
494

495
            String tagsString = article.optString(Article.ARTICLE_TAGS_REF);
1✔
496
            tagsString = Tag.formatTags(tagsString, 4);
1✔
497
            if (StringUtils.isBlank(tagsString)) {
1✔
498
                tagsString = "待分类";
×
499
            }
500
            article.put(Article.ARTICLE_TAGS_REF, tagsString);
1✔
501
            final String[] tagTitles = tagsString.split(",");
1✔
502
            final JSONArray tags = tag(tagTitles, article);
1✔
503

504
            if (!article.has(Article.ARTICLE_CREATED)) {
1✔
505
                article.put(Article.ARTICLE_CREATED, System.currentTimeMillis());
1✔
506
            }
507
            article.put(Article.ARTICLE_UPDATED, article.optLong(Article.ARTICLE_CREATED));
1✔
508
            article.put(Article.ARTICLE_PUT_TOP, false);
1✔
509

510
            addTagArticleRelation(tags, article);
1✔
511

512
            archiveDate(article);
1✔
513

514
            final String permalink = getPermalinkForAddArticle(article);
1✔
515
            article.put(Article.ARTICLE_PERMALINK, permalink);
1✔
516

517
            final String signId = article.optString(Article.ARTICLE_SIGN_ID, "1");
1✔
518
            article.put(Article.ARTICLE_SIGN_ID, signId);
1✔
519

520
            article.put(Article.ARTICLE_RANDOM_DOUBLE, Math.random());
1✔
521

522
            final String articleImg1URL = getArticleImg1URL(article);
1✔
523
            article.put(ARTICLE_IMG1_URL, articleImg1URL);
1✔
524

525
            final String articleAbstractText = Article.getAbstractText(article);
1✔
526
            article.put(ARTICLE_ABSTRACT_TEXT, articleAbstractText);
1✔
527

528
            final boolean postToCommunity = article.optBoolean(Common.POST_TO_COMMUNITY);
1✔
529
            article.remove(Common.POST_TO_COMMUNITY);
1✔
530
            articleRepository.add(article);
1✔
531
            transaction.commit();
1✔
532

533
            Statics.clear();
1✔
534

535
            article.put(Common.POST_TO_COMMUNITY, postToCommunity);
1✔
536
            if (Article.ARTICLE_STATUS_C_PUBLISHED == article.optInt(ARTICLE_STATUS)) {
1✔
537
                final JSONObject eventData = new JSONObject();
1✔
538
                eventData.put(Article.ARTICLE, article);
1✔
539
                eventManager.fireEventAsynchronously(new Event<>(EventTypes.ADD_ARTICLE, eventData));
1✔
540
            }
541

542
            return ret;
1✔
543
        } catch (final Exception e) {
×
544
            if (transaction.isActive()) {
×
545
                transaction.rollback();
×
546
            }
547

548
            throw new ServiceException(e.getMessage());
×
549
        }
550
    }
551

552
    /**
553
     * Removes the article specified by the given id.
554
     *
555
     * @param articleId the given id
556
     * @throws ServiceException service exception
557
     */
558
    public void removeArticle(final String articleId) throws ServiceException {
559
        final Transaction transaction = articleRepository.beginTransaction();
1✔
560
        try {
561
            unArchiveDate(articleId);
1✔
562
            removeTagArticleRelations(articleId);
1✔
563
            articleRepository.remove(articleId);
1✔
564
            transaction.commit();
1✔
565

566
            Statics.clear();
1✔
567
        } catch (final Exception e) {
×
568
            if (transaction.isActive()) {
×
569
                transaction.rollback();
×
570
            }
571

572
            LOGGER.log(Level.ERROR, "Removes an article[id=" + articleId + "] failed", e);
×
573
            throw new ServiceException(e);
×
574
        }
1✔
575
    }
1✔
576

577
    /**
578
     * Updates the random values of articles fetched with the specified update
579
     * count.
580
     *
581
     * @param updateCnt the specified update count
582
     * @throws ServiceException service exception
583
     */
584
    public void updateArticlesRandomValue(final int updateCnt) throws ServiceException {
585
        final Transaction transaction = articleRepository.beginTransaction();
1✔
586
        try {
587
            final List<JSONObject> randomArticles = articleRepository.getRandomly(updateCnt);
1✔
588
            for (final JSONObject article : randomArticles) {
1✔
589
                article.put(Article.ARTICLE_RANDOM_DOUBLE, Math.random());
1✔
590
                articleRepository.update(article.getString(Keys.OBJECT_ID), article, ARTICLE_RANDOM_DOUBLE);
1✔
591
            }
1✔
592
            transaction.commit();
1✔
593
        } catch (final Exception e) {
×
594
            if (transaction.isActive()) {
×
595
                transaction.rollback();
×
596
            }
597
            LOGGER.log(Level.WARN, "Updates article random value failed");
×
598
            throw new ServiceException(e);
×
599
        }
1✔
600
    }
1✔
601

602
    /**
603
     * Un-archive an article specified by the given specified article id.
604
     *
605
     * @param articleId the given article id
606
     * @throws ServiceException service exception
607
     */
608
    private void unArchiveDate(final String articleId) throws ServiceException {
609
        try {
610
            final JSONObject archiveDateArticleRelation = archiveDateArticleRepository.getByArticleId(articleId);
1✔
611
            if (null == archiveDateArticleRelation) {
1✔
612
                // 草稿不生成存档,所以需要判空
613
                return;
×
614
            }
615

616
            final String archiveDateId = archiveDateArticleRelation.getString(ArchiveDate.ARCHIVE_DATE + "_" + Keys.OBJECT_ID);
1✔
617
            final int publishedArticleCount = archiveDateArticleRepository.getPublishedArticleCount(archiveDateId);
1✔
618
            if (1 > publishedArticleCount) {
1✔
619
                archiveDateRepository.remove(archiveDateId);
×
620
            }
621
            archiveDateArticleRepository.remove(archiveDateArticleRelation.getString(Keys.OBJECT_ID));
1✔
622
        } catch (final Exception e) {
×
623
            LOGGER.log(Level.ERROR, "Unarchive date for article[id=" + articleId + "] failed", e);
×
624
            throw new ServiceException(e);
×
625
        }
1✔
626
    }
1✔
627

628
    /**
629
     * Processes tags for article update.
630
     * <p>
631
     * <ul>
632
     * <li>Un-tags old article, decrements tag reference count</li>
633
     * <li>Removes old article-tag relations</li>
634
     * <li>Saves new article-tag relations with tag reference count</li>
635
     * </ul>
636
     *
637
     * @param oldArticle the specified old article
638
     * @param newArticle the specified new article
639
     * @throws Exception exception
640
     */
641
    private void processTagsForArticleUpdate(final JSONObject oldArticle, final JSONObject newArticle) throws Exception {
642
        final String oldArticleId = oldArticle.getString(Keys.OBJECT_ID);
1✔
643
        final List<JSONObject> oldTags = tagRepository.getByArticleId(oldArticleId);
1✔
644
        final String tagsString = newArticle.getString(Article.ARTICLE_TAGS_REF);
1✔
645
        String[] tagStrings = tagsString.split(",");
1✔
646
        final List<JSONObject> newTags = new ArrayList<>();
1✔
647

648
        for (final String tagString : tagStrings) {
1✔
649
            final String tagTitle = tagString.trim();
1✔
650
            JSONObject newTag = tagRepository.getByTitle(tagTitle);
1✔
651
            if (null == newTag) {
1✔
652
                newTag = new JSONObject();
×
653
                newTag.put(Tag.TAG_TITLE, tagTitle);
×
654
            }
655
            newTags.add(newTag);
1✔
656
        }
657

658
        final List<JSONObject> tagsDropped = new ArrayList<>();
1✔
659
        final List<JSONObject> tagsNeedToAdd = new ArrayList<>();
1✔
660
        final List<JSONObject> tagsUnchanged = new ArrayList<>();
1✔
661

662
        for (final JSONObject newTag : newTags) {
1✔
663
            final String newTagTitle = newTag.getString(Tag.TAG_TITLE);
1✔
664

665
            if (!tagExists(newTagTitle, oldTags)) {
1✔
666
                LOGGER.log(Level.DEBUG, "Tag need to add[title={}]", newTagTitle);
×
667
                tagsNeedToAdd.add(newTag);
×
668
            } else {
669
                tagsUnchanged.add(newTag);
1✔
670
            }
671
        }
1✔
672
        for (final JSONObject oldTag : oldTags) {
1✔
673
            final String oldTagTitle = oldTag.getString(Tag.TAG_TITLE);
1✔
674

675
            if (!tagExists(oldTagTitle, newTags)) {
1✔
676
                LOGGER.log(Level.DEBUG, "Tag dropped[title={}]", oldTag);
×
677
                tagsDropped.add(oldTag);
×
678
            } else {
679
                tagsUnchanged.remove(oldTag);
1✔
680
            }
681
        }
1✔
682

683
        LOGGER.log(Level.DEBUG, "Tags unchanged [{}]", tagsUnchanged);
1✔
684

685
        final String[] tagIdsDropped = new String[tagsDropped.size()];
1✔
686
        for (int i = 0; i < tagIdsDropped.length; i++) {
1✔
687
            final JSONObject tag = tagsDropped.get(i);
×
688
            final String id = tag.getString(Keys.OBJECT_ID);
×
689
            tagIdsDropped[i] = id;
×
690
        }
691

692
        removeTagArticleRelations(oldArticleId, 0 == tagIdsDropped.length ? new String[]{"l0y0l"} : tagIdsDropped);
1✔
693

694
        tagStrings = new String[tagsNeedToAdd.size()];
1✔
695
        for (int i = 0; i < tagStrings.length; i++) {
1✔
696
            final JSONObject tag = tagsNeedToAdd.get(i);
×
697
            final String tagTitle = tag.getString(Tag.TAG_TITLE);
×
698
            tagStrings[i] = tagTitle;
×
699
        }
700
        final JSONArray tags = tag(tagStrings, newArticle);
1✔
701

702
        addTagArticleRelation(tags, newArticle);
1✔
703
    }
1✔
704

705
    /**
706
     * Removes tag-article relations by the specified article id and tag ids of the relations to be removed.
707
     * <p>
708
     * Removes all relations if not specified the tag ids.
709
     * </p>
710
     *
711
     * @param articleId the specified article id
712
     * @param tagIds    the specified tag ids of the relations to be removed
713
     * @throws JSONException       json exception
714
     * @throws RepositoryException repository exception
715
     */
716
    private void removeTagArticleRelations(final String articleId, final String... tagIds) throws JSONException, RepositoryException {
717
        final List<String> tagIdList = Arrays.asList(tagIds);
1✔
718
        final List<JSONObject> tagArticleRelations = tagArticleRepository.getByArticleId(articleId);
1✔
719

720
        for (int i = 0; i < tagArticleRelations.size(); i++) {
1✔
721
            final JSONObject tagArticleRelation = tagArticleRelations.get(i);
1✔
722
            String relationId;
723
            if (tagIdList.isEmpty()) { // Removes all if un-specified
1✔
724
                relationId = tagArticleRelation.getString(Keys.OBJECT_ID);
1✔
725
                tagArticleRepository.remove(relationId);
1✔
726
            } else {
727
                if (tagIdList.contains(tagArticleRelation.getString(Tag.TAG + "_" + Keys.OBJECT_ID))) {
1✔
728
                    relationId = tagArticleRelation.getString(Keys.OBJECT_ID);
×
729
                    tagArticleRepository.remove(relationId);
×
730
                }
731
            }
732

733
            final String tagId = tagArticleRelation.optString(Tag.TAG + "_" + Keys.OBJECT_ID);
1✔
734
            final int articleCount = tagArticleRepository.getArticleCount(tagId);
1✔
735
            if (1 > articleCount) {
1✔
736
                categoryTagRepository.removeByTagId(tagId);
×
737
                tagRepository.remove(tagId);
×
738
            }
739
        }
740
    }
1✔
741

742
    /**
743
     * Adds relation of the specified tags and article.
744
     *
745
     * @param tags    the specified tags
746
     * @param article the specified article
747
     * @throws RepositoryException repository exception
748
     */
749
    private void addTagArticleRelation(final JSONArray tags, final JSONObject article) throws RepositoryException {
750
        for (int i = 0; i < tags.length(); i++) {
1✔
751
            final JSONObject tag = tags.optJSONObject(i);
1✔
752
            final JSONObject tagArticleRelation = new JSONObject();
1✔
753
            tagArticleRelation.put(Tag.TAG + "_" + Keys.OBJECT_ID, tag.optString(Keys.OBJECT_ID));
1✔
754
            tagArticleRelation.put(Article.ARTICLE + "_" + Keys.OBJECT_ID, article.optString(Keys.OBJECT_ID));
1✔
755
            tagArticleRepository.add(tagArticleRelation);
1✔
756
        }
757
    }
1✔
758

759
    /**
760
     * Tags the specified article with the specified tag titles.
761
     *
762
     * @param tagTitles the specified tag titles
763
     * @param article   the specified article
764
     * @return an array of tags
765
     * @throws RepositoryException repository exception
766
     */
767
    private JSONArray tag(final String[] tagTitles, final JSONObject article) throws RepositoryException {
768
        final JSONArray ret = new JSONArray();
1✔
769

770
        for (int i = 0; i < tagTitles.length; i++) {
1✔
771
            final String tagTitle = tagTitles[i].trim();
1✔
772
            JSONObject tag = tagRepository.getByTitle(tagTitle);
1✔
773
            String tagId;
774

775
            if (null == tag) {
1✔
776
                LOGGER.log(Level.TRACE, "Found a new tag[title={}] in article[title={}]",
1✔
777
                        tagTitle, article.optString(Article.ARTICLE_TITLE));
1✔
778
                tag = new JSONObject();
1✔
779
                tag.put(Tag.TAG_TITLE, tagTitle);
1✔
780
                tagId = tagRepository.add(tag);
1✔
781
                tag.put(Keys.OBJECT_ID, tagId);
1✔
782
            } else {
783
                tagId = tag.optString(Keys.OBJECT_ID);
1✔
784
                LOGGER.log(Level.TRACE, "Found a existing tag[title={}, id={}] in article[title={}]",
1✔
785
                        tag.optString(Tag.TAG_TITLE), tag.optString(Keys.OBJECT_ID), article.optString(Article.ARTICLE_TITLE));
1✔
786
                final JSONObject tagTmp = new JSONObject();
1✔
787
                tagTmp.put(Keys.OBJECT_ID, tagId);
1✔
788
                tagTmp.put(Tag.TAG_TITLE, tagTitle);
1✔
789
                tagRepository.update(tagId, tagTmp);
1✔
790
            }
791

792
            ret.put(tag);
1✔
793
        }
794
        return ret;
1✔
795
    }
796

797
    /**
798
     * Archive the create date with the specified article.
799
     *
800
     * @param article the specified article, for example,
801
     *                {
802
     *                "oId": "",
803
     *                ....
804
     *                }
805
     * @throws RepositoryException repository exception
806
     */
807
    private void archiveDate(final JSONObject article) throws RepositoryException {
808
        if (Article.ARTICLE_STATUS_C_PUBLISHED != article.optInt(ARTICLE_STATUS)) {
1✔
809
            // 草稿不生成存档
810
            return;
1✔
811
        }
812

813
        final long created = article.optLong(Keys.OBJECT_ID);
1✔
814
        final String createDateString = DateFormatUtils.format(created, "yyyy/MM");
1✔
815
        JSONObject archiveDate = archiveDateRepository.getByArchiveDate(createDateString);
1✔
816
        if (null == archiveDate) {
1✔
817
            archiveDate = new JSONObject();
×
818
            try {
819
                archiveDate.put(ArchiveDate.ARCHIVE_TIME, DateUtils.parseDate(createDateString, new String[]{"yyyy/MM"}).getTime());
×
820
                archiveDateRepository.add(archiveDate);
×
821
            } catch (final ParseException e) {
×
822
                LOGGER.log(Level.ERROR, e.getMessage(), e);
×
823
                throw new RepositoryException(e);
×
824
            }
×
825
        }
826

827
        final String articleId = article.optString(Keys.OBJECT_ID);
1✔
828
        if (null == archiveDateArticleRepository.getByArticleId(articleId)) {
1✔
829
            final JSONObject archiveDateArticleRelation = new JSONObject();
1✔
830
            archiveDateArticleRelation.put(ArchiveDate.ARCHIVE_DATE + "_" + Keys.OBJECT_ID, archiveDate.optString(Keys.OBJECT_ID));
1✔
831
            archiveDateArticleRelation.put(Article.ARTICLE + "_" + Keys.OBJECT_ID, articleId);
1✔
832
            archiveDateArticleRepository.add(archiveDateArticleRelation);
1✔
833
        }
834
    }
1✔
835

836
    /**
837
     * Fills 'auto' properties for the specified article and old article.
838
     * <p>
839
     * Some properties of an article are not been changed while article
840
     * updating, these properties are called 'auto' properties.
841
     * </p>
842
     * <p>
843
     * The property(named {@value org.b3log.solo.model.Article#ARTICLE_RANDOM_DOUBLE}) of the specified
844
     * article will be regenerated.
845
     * </p>
846
     *
847
     * @param oldArticle the specified old article
848
     * @param article    the specified article
849
     * @throws JSONException json exception
850
     */
851
    private void fillAutoProperties(final JSONObject oldArticle, final JSONObject article) throws JSONException {
852
        final long created = oldArticle.getLong(ARTICLE_CREATED);
1✔
853
        article.put(ARTICLE_CREATED, created);
1✔
854
        article.put(ARTICLE_PUT_TOP, oldArticle.getBoolean(ARTICLE_PUT_TOP));
1✔
855
        article.put(ARTICLE_AUTHOR_ID, oldArticle.getString(ARTICLE_AUTHOR_ID));
1✔
856
        article.put(ARTICLE_RANDOM_DOUBLE, Math.random());
1✔
857
    }
1✔
858

859
    /**
860
     * Gets article permalink for adding article with the specified article.
861
     *
862
     * @param article the specified article
863
     * @return permalink
864
     * @throws ServiceException if invalid permalink occurs
865
     */
866
    private String getPermalinkForAddArticle(final JSONObject article) throws ServiceException {
867
        final long date = article.optLong(Article.ARTICLE_CREATED);
1✔
868
        String ret = article.optString(Article.ARTICLE_PERMALINK);
1✔
869
        if (StringUtils.isBlank(ret)) {
1✔
870
            ret = "/articles/" + DateFormatUtils.format(date, "yyyy/MM/dd") + "/" + article.optString(Keys.OBJECT_ID) + ".html";
1✔
871
        }
872

873
        if (!ret.startsWith("/")) {
1✔
874
            ret = "/" + ret;
1✔
875
        }
876

877
        if (PermalinkQueryService.invalidArticlePermalinkFormat(ret)) {
1✔
878
            throw new ServiceException(langPropsService.get("invalidPermalinkFormatLabel"));
×
879
        }
880

881
        if (permalinkQueryService.exist(ret)) {
1✔
882
            throw new ServiceException(langPropsService.get("duplicatedPermalinkLabel"));
×
883
        }
884

885
        return ret.replaceAll(" ", "-");
1✔
886
    }
887

888
    /**
889
     * Gets article permalink for updating article with the specified old article, article, created at.
890
     *
891
     * @param oldArticle the specified old article
892
     * @param article    the specified article
893
     * @param created    the specified created
894
     * @return permalink
895
     * @throws ServiceException if invalid permalink occurs
896
     * @throws JSONException    json exception
897
     */
898
    private String getPermalinkForUpdateArticle(final JSONObject oldArticle, final JSONObject article, final long created)
899
            throws ServiceException, JSONException {
900
        final String articleId = article.getString(Keys.OBJECT_ID);
1✔
901
        String ret = article.optString(ARTICLE_PERMALINK).trim();
1✔
902
        final String oldPermalink = oldArticle.getString(ARTICLE_PERMALINK);
1✔
903

904
        if (!oldPermalink.equals(ret)) {
1✔
905
            if (StringUtils.isBlank(ret)) {
×
906
                ret = "/articles/" + DateFormatUtils.format(created, "yyyy/MM/dd") + "/" + articleId + ".html";
×
907
            }
908

909
            if (!ret.startsWith("/")) {
×
910
                ret = "/" + ret;
×
911
            }
912

913
            if (PermalinkQueryService.invalidArticlePermalinkFormat(ret)) {
×
914
                throw new ServiceException(langPropsService.get("invalidPermalinkFormatLabel"));
×
915
            }
916

917
            if (!oldPermalink.equals(ret) && permalinkQueryService.exist(ret)) {
×
918
                throw new ServiceException(langPropsService.get("duplicatedPermalinkLabel"));
×
919
            }
920
        }
921
        return ret.replaceAll(" ", "-");
1✔
922
    }
923

924
    /**
925
     * Determines whether the specified tag title exists in the specified tags.
926
     *
927
     * @param tagTitle the specified tag title
928
     * @param tags     the specified tags
929
     * @return {@code true} if it exists, {@code false} otherwise
930
     * @throws JSONException json exception
931
     */
932
    private static boolean tagExists(final String tagTitle, final List<JSONObject> tags) throws JSONException {
933
        for (final JSONObject tag : tags) {
1✔
934
            if (tag.getString(Tag.TAG_TITLE).equals(tagTitle)) {
1✔
935
                return true;
1✔
936
            }
937
        }
1✔
938
        return false;
×
939
    }
940
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc