Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

TicketIndexer.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. /*
  2. * Copyright 2014 gitblit.com.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.gitblit.tickets;
  17. import java.io.File;
  18. import java.io.IOException;
  19. import java.text.MessageFormat;
  20. import java.text.ParseException;
  21. import java.util.ArrayList;
  22. import java.util.Collections;
  23. import java.util.Date;
  24. import java.util.LinkedHashSet;
  25. import java.util.List;
  26. import java.util.Set;
  27. import org.apache.lucene.analysis.standard.StandardAnalyzer;
  28. import org.apache.lucene.document.Document;
  29. import org.apache.lucene.document.Field.Store;
  30. import org.apache.lucene.document.IntField;
  31. import org.apache.lucene.document.LongField;
  32. import org.apache.lucene.document.TextField;
  33. import org.apache.lucene.index.DirectoryReader;
  34. import org.apache.lucene.index.IndexWriter;
  35. import org.apache.lucene.index.IndexWriterConfig;
  36. import org.apache.lucene.index.IndexWriterConfig.OpenMode;
  37. import org.apache.lucene.queryparser.classic.QueryParser;
  38. import org.apache.lucene.search.BooleanClause.Occur;
  39. import org.apache.lucene.search.BooleanQuery;
  40. import org.apache.lucene.search.IndexSearcher;
  41. import org.apache.lucene.search.Query;
  42. import org.apache.lucene.search.ScoreDoc;
  43. import org.apache.lucene.search.Sort;
  44. import org.apache.lucene.search.SortField;
  45. import org.apache.lucene.search.SortField.Type;
  46. import org.apache.lucene.search.TopFieldDocs;
  47. import org.apache.lucene.search.TopScoreDocCollector;
  48. import org.apache.lucene.store.Directory;
  49. import org.apache.lucene.store.FSDirectory;
  50. import org.apache.lucene.util.Version;
  51. import org.slf4j.Logger;
  52. import org.slf4j.LoggerFactory;
  53. import com.gitblit.Keys;
  54. import com.gitblit.manager.IRuntimeManager;
  55. import com.gitblit.models.RepositoryModel;
  56. import com.gitblit.models.TicketModel;
  57. import com.gitblit.models.TicketModel.Attachment;
  58. import com.gitblit.models.TicketModel.Patchset;
  59. import com.gitblit.models.TicketModel.Status;
  60. import com.gitblit.utils.FileUtils;
  61. import com.gitblit.utils.StringUtils;
  62. /**
  63. * Indexes tickets in a Lucene database.
  64. *
  65. * @author James Moger
  66. *
  67. */
  68. public class TicketIndexer {
  69. /**
  70. * Fields in the Lucene index
  71. */
  72. public static enum Lucene {
  73. rid(Type.STRING),
  74. did(Type.STRING),
  75. project(Type.STRING),
  76. repository(Type.STRING),
  77. number(Type.LONG),
  78. title(Type.STRING),
  79. body(Type.STRING),
  80. topic(Type.STRING),
  81. created(Type.LONG),
  82. createdby(Type.STRING),
  83. updated(Type.LONG),
  84. updatedby(Type.STRING),
  85. responsible(Type.STRING),
  86. milestone(Type.STRING),
  87. status(Type.STRING),
  88. type(Type.STRING),
  89. labels(Type.STRING),
  90. participants(Type.STRING),
  91. watchedby(Type.STRING),
  92. mentions(Type.STRING),
  93. attachments(Type.INT),
  94. content(Type.STRING),
  95. patchset(Type.STRING),
  96. comments(Type.INT),
  97. mergesha(Type.STRING),
  98. mergeto(Type.STRING),
  99. patchsets(Type.INT),
  100. votes(Type.INT),
  101. //NOTE: Indexing on the underlying value to allow flexibility on naming
  102. priority(Type.INT),
  103. severity(Type.INT);
  104. final Type fieldType;
  105. Lucene(Type fieldType) {
  106. this.fieldType = fieldType;
  107. }
  108. public String colon() {
  109. return name() + ":";
  110. }
  111. public String matches(String value) {
  112. if (StringUtils.isEmpty(value)) {
  113. return "";
  114. }
  115. boolean not = value.charAt(0) == '!';
  116. if (not) {
  117. return "!" + name() + ":" + escape(value.substring(1));
  118. }
  119. return name() + ":" + escape(value);
  120. }
  121. public String doesNotMatch(String value) {
  122. if (StringUtils.isEmpty(value)) {
  123. return "";
  124. }
  125. return "NOT " + name() + ":" + escape(value);
  126. }
  127. public String isNotNull() {
  128. return matches("[* TO *]");
  129. }
  130. public SortField asSortField(boolean descending) {
  131. return new SortField(name(), fieldType, descending);
  132. }
  133. private String escape(String value) {
  134. if (value.charAt(0) != '"') {
  135. for (char c : value.toCharArray()) {
  136. if (!Character.isLetterOrDigit(c)) {
  137. return "\"" + value + "\"";
  138. }
  139. }
  140. }
  141. return value;
  142. }
  143. public static Lucene fromString(String value) {
  144. for (Lucene field : values()) {
  145. if (field.name().equalsIgnoreCase(value)) {
  146. return field;
  147. }
  148. }
  149. return created;
  150. }
  151. }
  152. private final Logger log = LoggerFactory.getLogger(getClass());
  153. private final Version luceneVersion = Version.LUCENE_5_2_1;
  154. private final File luceneDir;
  155. private IndexWriter writer;
  156. private IndexSearcher searcher;
  157. public TicketIndexer(IRuntimeManager runtimeManager) {
  158. this.luceneDir = runtimeManager.getFileOrFolder(Keys.tickets.indexFolder, "${baseFolder}/tickets/lucene");
  159. }
  160. /**
  161. * Close all writers and searchers used by the ticket indexer.
  162. */
  163. public void close() {
  164. closeSearcher();
  165. closeWriter();
  166. }
  167. /**
  168. * Deletes the entire ticket index for all repositories.
  169. */
  170. public void deleteAll() {
  171. close();
  172. FileUtils.delete(luceneDir);
  173. }
  174. /**
  175. * Deletes all tickets for the the repository from the index.
  176. */
  177. public boolean deleteAll(RepositoryModel repository) {
  178. try {
  179. IndexWriter writer = getWriter();
  180. StandardAnalyzer analyzer = new StandardAnalyzer();
  181. QueryParser qp = new QueryParser(Lucene.rid.name(), analyzer);
  182. BooleanQuery query = new BooleanQuery();
  183. query.add(qp.parse(repository.getRID()), Occur.MUST);
  184. int numDocsBefore = writer.numDocs();
  185. writer.deleteDocuments(query);
  186. writer.commit();
  187. closeSearcher();
  188. int numDocsAfter = writer.numDocs();
  189. if (numDocsBefore == numDocsAfter) {
  190. log.debug(MessageFormat.format("no records found to delete in {0}", repository));
  191. return false;
  192. } else {
  193. log.debug(MessageFormat.format("deleted {0} records in {1}", numDocsBefore - numDocsAfter, repository));
  194. return true;
  195. }
  196. } catch (Exception e) {
  197. log.error("error", e);
  198. }
  199. return false;
  200. }
  201. /**
  202. * Bulk Add/Update tickets in the Lucene index
  203. *
  204. * @param tickets
  205. */
  206. public void index(List<TicketModel> tickets) {
  207. try {
  208. IndexWriter writer = getWriter();
  209. for (TicketModel ticket : tickets) {
  210. Document doc = ticketToDoc(ticket);
  211. writer.addDocument(doc);
  212. }
  213. writer.commit();
  214. closeSearcher();
  215. } catch (Exception e) {
  216. log.error("error", e);
  217. }
  218. }
  219. /**
  220. * Add/Update a ticket in the Lucene index
  221. *
  222. * @param ticket
  223. */
  224. public void index(TicketModel ticket) {
  225. try {
  226. IndexWriter writer = getWriter();
  227. delete(ticket.repository, ticket.number, writer);
  228. Document doc = ticketToDoc(ticket);
  229. writer.addDocument(doc);
  230. writer.commit();
  231. closeSearcher();
  232. } catch (Exception e) {
  233. log.error("error", e);
  234. }
  235. }
  236. /**
  237. * Delete a ticket from the Lucene index.
  238. *
  239. * @param ticket
  240. * @throws Exception
  241. * @return true, if deleted, false if no record was deleted
  242. */
  243. public boolean delete(TicketModel ticket) {
  244. try {
  245. IndexWriter writer = getWriter();
  246. return delete(ticket.repository, ticket.number, writer);
  247. } catch (Exception e) {
  248. log.error("Failed to delete ticket " + ticket.number, e);
  249. }
  250. return false;
  251. }
  252. /**
  253. * Delete a ticket from the Lucene index.
  254. *
  255. * @param repository
  256. * @param ticketId
  257. * @throws Exception
  258. * @return true, if deleted, false if no record was deleted
  259. */
  260. private boolean delete(String repository, long ticketId, IndexWriter writer) throws Exception {
  261. StandardAnalyzer analyzer = new StandardAnalyzer();
  262. QueryParser qp = new QueryParser(Lucene.did.name(), analyzer);
  263. BooleanQuery query = new BooleanQuery();
  264. query.add(qp.parse(StringUtils.getSHA1(repository + ticketId)), Occur.MUST);
  265. int numDocsBefore = writer.numDocs();
  266. writer.deleteDocuments(query);
  267. writer.commit();
  268. closeSearcher();
  269. int numDocsAfter = writer.numDocs();
  270. if (numDocsBefore == numDocsAfter) {
  271. log.debug(MessageFormat.format("no records found to delete in {0}", repository));
  272. return false;
  273. } else {
  274. log.debug(MessageFormat.format("deleted {0} records in {1}", numDocsBefore - numDocsAfter, repository));
  275. return true;
  276. }
  277. }
  278. /**
  279. * Returns true if the repository has tickets in the index.
  280. *
  281. * @param repository
  282. * @return true if there are indexed tickets
  283. */
  284. public boolean hasTickets(RepositoryModel repository) {
  285. return !queryFor(Lucene.rid.matches(repository.getRID()), 1, 0, null, true).isEmpty();
  286. }
  287. /**
  288. * Search for tickets matching the query. The returned tickets are
  289. * shadows of the real ticket, but suitable for a results list.
  290. *
  291. * @param repository
  292. * @param text
  293. * @param page
  294. * @param pageSize
  295. * @return search results
  296. */
  297. public List<QueryResult> searchFor(RepositoryModel repository, String text, int page, int pageSize) {
  298. if (StringUtils.isEmpty(text)) {
  299. return Collections.emptyList();
  300. }
  301. Set<QueryResult> results = new LinkedHashSet<QueryResult>();
  302. StandardAnalyzer analyzer = new StandardAnalyzer();
  303. try {
  304. // search the title, description and content
  305. BooleanQuery query = new BooleanQuery();
  306. QueryParser qp;
  307. qp = new QueryParser(Lucene.title.name(), analyzer);
  308. qp.setAllowLeadingWildcard(true);
  309. query.add(qp.parse(text), Occur.SHOULD);
  310. qp = new QueryParser(Lucene.body.name(), analyzer);
  311. qp.setAllowLeadingWildcard(true);
  312. query.add(qp.parse(text), Occur.SHOULD);
  313. qp = new QueryParser(Lucene.content.name(), analyzer);
  314. qp.setAllowLeadingWildcard(true);
  315. query.add(qp.parse(text), Occur.SHOULD);
  316. IndexSearcher searcher = getSearcher();
  317. Query rewrittenQuery = searcher.rewrite(query);
  318. log.debug(rewrittenQuery.toString());
  319. TopScoreDocCollector collector = TopScoreDocCollector.create(5000);
  320. searcher.search(rewrittenQuery, collector);
  321. int offset = Math.max(0, (page - 1) * pageSize);
  322. ScoreDoc[] hits = collector.topDocs(offset, pageSize).scoreDocs;
  323. for (int i = 0; i < hits.length; i++) {
  324. int docId = hits[i].doc;
  325. Document doc = searcher.doc(docId);
  326. QueryResult result = docToQueryResult(doc);
  327. if (repository != null) {
  328. if (!result.repository.equalsIgnoreCase(repository.name)) {
  329. continue;
  330. }
  331. }
  332. results.add(result);
  333. }
  334. } catch (Exception e) {
  335. log.error(MessageFormat.format("Exception while searching for {0}", text), e);
  336. }
  337. return new ArrayList<QueryResult>(results);
  338. }
  339. /**
  340. * Search for tickets matching the query. The returned tickets are
  341. * shadows of the real ticket, but suitable for a results list.
  342. *
  343. * @param text
  344. * @param page
  345. * @param pageSize
  346. * @param sortBy
  347. * @param desc
  348. * @return
  349. */
  350. public List<QueryResult> queryFor(String queryText, int page, int pageSize, String sortBy, boolean desc) {
  351. if (StringUtils.isEmpty(queryText)) {
  352. return Collections.emptyList();
  353. }
  354. Set<QueryResult> results = new LinkedHashSet<QueryResult>();
  355. StandardAnalyzer analyzer = new StandardAnalyzer();
  356. try {
  357. QueryParser qp = new QueryParser(Lucene.content.name(), analyzer);
  358. Query query = qp.parse(queryText);
  359. IndexSearcher searcher = getSearcher();
  360. Query rewrittenQuery = searcher.rewrite(query);
  361. log.debug(rewrittenQuery.toString());
  362. Sort sort;
  363. if (sortBy == null) {
  364. sort = new Sort(Lucene.created.asSortField(desc));
  365. } else {
  366. sort = new Sort(Lucene.fromString(sortBy).asSortField(desc));
  367. }
  368. int maxSize = 5000;
  369. TopFieldDocs docs = searcher.search(rewrittenQuery, null, maxSize, sort, false, false);
  370. int size = (pageSize <= 0) ? maxSize : pageSize;
  371. int offset = Math.max(0, (page - 1) * size);
  372. ScoreDoc[] hits = subset(docs.scoreDocs, offset, size);
  373. for (int i = 0; i < hits.length; i++) {
  374. int docId = hits[i].doc;
  375. Document doc = searcher.doc(docId);
  376. QueryResult result = docToQueryResult(doc);
  377. result.docId = docId;
  378. result.totalResults = docs.totalHits;
  379. results.add(result);
  380. }
  381. } catch (Exception e) {
  382. log.error(MessageFormat.format("Exception while searching for {0}", queryText), e);
  383. }
  384. return new ArrayList<QueryResult>(results);
  385. }
  386. private ScoreDoc [] subset(ScoreDoc [] docs, int offset, int size) {
  387. if (docs.length >= (offset + size)) {
  388. ScoreDoc [] set = new ScoreDoc[size];
  389. System.arraycopy(docs, offset, set, 0, set.length);
  390. return set;
  391. } else if (docs.length >= offset) {
  392. ScoreDoc [] set = new ScoreDoc[docs.length - offset];
  393. System.arraycopy(docs, offset, set, 0, set.length);
  394. return set;
  395. } else {
  396. return new ScoreDoc[0];
  397. }
  398. }
  399. private IndexWriter getWriter() throws IOException {
  400. if (writer == null) {
  401. Directory directory = FSDirectory.open(luceneDir.toPath());
  402. if (!luceneDir.exists()) {
  403. luceneDir.mkdirs();
  404. }
  405. StandardAnalyzer analyzer = new StandardAnalyzer();
  406. IndexWriterConfig config = new IndexWriterConfig(analyzer);
  407. config.setOpenMode(OpenMode.CREATE_OR_APPEND);
  408. writer = new IndexWriter(directory, config);
  409. }
  410. return writer;
  411. }
  412. private synchronized void closeWriter() {
  413. try {
  414. if (writer != null) {
  415. writer.close();
  416. }
  417. } catch (Exception e) {
  418. log.error("failed to close writer!", e);
  419. } finally {
  420. writer = null;
  421. }
  422. }
  423. private IndexSearcher getSearcher() throws IOException {
  424. if (searcher == null) {
  425. searcher = new IndexSearcher(DirectoryReader.open(getWriter(), true));
  426. }
  427. return searcher;
  428. }
  429. private synchronized void closeSearcher() {
  430. try {
  431. if (searcher != null) {
  432. searcher.getIndexReader().close();
  433. }
  434. } catch (Exception e) {
  435. log.error("failed to close searcher!", e);
  436. } finally {
  437. searcher = null;
  438. }
  439. }
  440. /**
  441. * Creates a Lucene document from a ticket.
  442. *
  443. * @param ticket
  444. * @return a Lucene document
  445. */
  446. private Document ticketToDoc(TicketModel ticket) {
  447. Document doc = new Document();
  448. // repository and document ids for Lucene querying
  449. toDocField(doc, Lucene.rid, StringUtils.getSHA1(ticket.repository));
  450. toDocField(doc, Lucene.did, StringUtils.getSHA1(ticket.repository + ticket.number));
  451. toDocField(doc, Lucene.project, ticket.project);
  452. toDocField(doc, Lucene.repository, ticket.repository);
  453. toDocField(doc, Lucene.number, ticket.number);
  454. toDocField(doc, Lucene.title, ticket.title);
  455. toDocField(doc, Lucene.body, ticket.body);
  456. toDocField(doc, Lucene.created, ticket.created);
  457. toDocField(doc, Lucene.createdby, ticket.createdBy);
  458. toDocField(doc, Lucene.updated, ticket.updated);
  459. toDocField(doc, Lucene.updatedby, ticket.updatedBy);
  460. toDocField(doc, Lucene.responsible, ticket.responsible);
  461. toDocField(doc, Lucene.milestone, ticket.milestone);
  462. toDocField(doc, Lucene.topic, ticket.topic);
  463. toDocField(doc, Lucene.status, ticket.status.name());
  464. toDocField(doc, Lucene.comments, ticket.getComments().size());
  465. toDocField(doc, Lucene.type, ticket.type == null ? null : ticket.type.name());
  466. toDocField(doc, Lucene.mergesha, ticket.mergeSha);
  467. toDocField(doc, Lucene.mergeto, ticket.mergeTo);
  468. toDocField(doc, Lucene.labels, StringUtils.flattenStrings(ticket.getLabels(), ";").toLowerCase());
  469. toDocField(doc, Lucene.participants, StringUtils.flattenStrings(ticket.getParticipants(), ";").toLowerCase());
  470. toDocField(doc, Lucene.watchedby, StringUtils.flattenStrings(ticket.getWatchers(), ";").toLowerCase());
  471. toDocField(doc, Lucene.mentions, StringUtils.flattenStrings(ticket.getMentions(), ";").toLowerCase());
  472. toDocField(doc, Lucene.votes, ticket.getVoters().size());
  473. toDocField(doc, Lucene.priority, ticket.priority.getValue());
  474. toDocField(doc, Lucene.severity, ticket.severity.getValue());
  475. List<String> attachments = new ArrayList<String>();
  476. for (Attachment attachment : ticket.getAttachments()) {
  477. attachments.add(attachment.name.toLowerCase());
  478. }
  479. toDocField(doc, Lucene.attachments, StringUtils.flattenStrings(attachments, ";"));
  480. List<Patchset> patches = ticket.getPatchsets();
  481. if (!patches.isEmpty()) {
  482. toDocField(doc, Lucene.patchsets, patches.size());
  483. Patchset patchset = patches.get(patches.size() - 1);
  484. String flat =
  485. patchset.number + ":" +
  486. patchset.rev + ":" +
  487. patchset.tip + ":" +
  488. patchset.base + ":" +
  489. patchset.commits;
  490. doc.add(new org.apache.lucene.document.Field(Lucene.patchset.name(), flat, TextField.TYPE_STORED));
  491. }
  492. doc.add(new TextField(Lucene.content.name(), ticket.toIndexableString(), Store.NO));
  493. return doc;
  494. }
  495. private void toDocField(Document doc, Lucene lucene, Date value) {
  496. if (value == null) {
  497. return;
  498. }
  499. doc.add(new LongField(lucene.name(), value.getTime(), Store.YES));
  500. }
  501. private void toDocField(Document doc, Lucene lucene, long value) {
  502. doc.add(new LongField(lucene.name(), value, Store.YES));
  503. }
  504. private void toDocField(Document doc, Lucene lucene, int value) {
  505. doc.add(new IntField(lucene.name(), value, Store.YES));
  506. }
  507. private void toDocField(Document doc, Lucene lucene, String value) {
  508. if (StringUtils.isEmpty(value)) {
  509. return;
  510. }
  511. doc.add(new org.apache.lucene.document.Field(lucene.name(), value, TextField.TYPE_STORED));
  512. }
  513. /**
  514. * Creates a query result from the Lucene document. This result is
  515. * not a high-fidelity representation of the real ticket, but it is
  516. * suitable for display in a table of search results.
  517. *
  518. * @param doc
  519. * @return a query result
  520. * @throws ParseException
  521. */
  522. private QueryResult docToQueryResult(Document doc) throws ParseException {
  523. QueryResult result = new QueryResult();
  524. result.project = unpackString(doc, Lucene.project);
  525. result.repository = unpackString(doc, Lucene.repository);
  526. result.number = unpackLong(doc, Lucene.number);
  527. result.createdBy = unpackString(doc, Lucene.createdby);
  528. result.createdAt = unpackDate(doc, Lucene.created);
  529. result.updatedBy = unpackString(doc, Lucene.updatedby);
  530. result.updatedAt = unpackDate(doc, Lucene.updated);
  531. result.title = unpackString(doc, Lucene.title);
  532. result.body = unpackString(doc, Lucene.body);
  533. result.status = Status.fromObject(unpackString(doc, Lucene.status), Status.New);
  534. result.responsible = unpackString(doc, Lucene.responsible);
  535. result.milestone = unpackString(doc, Lucene.milestone);
  536. result.topic = unpackString(doc, Lucene.topic);
  537. result.type = TicketModel.Type.fromObject(unpackString(doc, Lucene.type), TicketModel.Type.defaultType);
  538. result.mergeSha = unpackString(doc, Lucene.mergesha);
  539. result.mergeTo = unpackString(doc, Lucene.mergeto);
  540. result.commentsCount = unpackInt(doc, Lucene.comments);
  541. result.votesCount = unpackInt(doc, Lucene.votes);
  542. result.attachments = unpackStrings(doc, Lucene.attachments);
  543. result.labels = unpackStrings(doc, Lucene.labels);
  544. result.participants = unpackStrings(doc, Lucene.participants);
  545. result.watchedby = unpackStrings(doc, Lucene.watchedby);
  546. result.mentions = unpackStrings(doc, Lucene.mentions);
  547. result.priority = TicketModel.Priority.fromObject(unpackInt(doc, Lucene.priority), TicketModel.Priority.defaultPriority);
  548. result.severity = TicketModel.Severity.fromObject(unpackInt(doc, Lucene.severity), TicketModel.Severity.defaultSeverity);
  549. if (!StringUtils.isEmpty(doc.get(Lucene.patchset.name()))) {
  550. // unpack most recent patchset
  551. String [] values = doc.get(Lucene.patchset.name()).split(":", 5);
  552. Patchset patchset = new Patchset();
  553. patchset.number = Integer.parseInt(values[0]);
  554. patchset.rev = Integer.parseInt(values[1]);
  555. patchset.tip = values[2];
  556. patchset.base = values[3];
  557. patchset.commits = Integer.parseInt(values[4]);
  558. result.patchset = patchset;
  559. }
  560. return result;
  561. }
  562. private String unpackString(Document doc, Lucene lucene) {
  563. return doc.get(lucene.name());
  564. }
  565. private List<String> unpackStrings(Document doc, Lucene lucene) {
  566. if (!StringUtils.isEmpty(doc.get(lucene.name()))) {
  567. return StringUtils.getStringsFromValue(doc.get(lucene.name()), ";");
  568. }
  569. return null;
  570. }
  571. private Date unpackDate(Document doc, Lucene lucene) {
  572. String val = doc.get(lucene.name());
  573. if (!StringUtils.isEmpty(val)) {
  574. long time = Long.parseLong(val);
  575. Date date = new Date(time);
  576. return date;
  577. }
  578. return null;
  579. }
  580. private long unpackLong(Document doc, Lucene lucene) {
  581. String val = doc.get(lucene.name());
  582. if (StringUtils.isEmpty(val)) {
  583. return 0;
  584. }
  585. long l = Long.parseLong(val);
  586. return l;
  587. }
  588. private int unpackInt(Document doc, Lucene lucene) {
  589. String val = doc.get(lucene.name());
  590. if (StringUtils.isEmpty(val)) {
  591. return 0;
  592. }
  593. int i = Integer.parseInt(val);
  594. return i;
  595. }
  596. }