You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FileSourceDto.java 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.db.source;
  21. import com.google.common.base.Joiner;
  22. import com.google.common.base.Splitter;
  23. import com.google.protobuf.CodedInputStream;
  24. import com.google.protobuf.InvalidProtocolBufferException;
  25. import java.io.ByteArrayInputStream;
  26. import java.io.ByteArrayOutputStream;
  27. import java.io.IOException;
  28. import java.util.Collections;
  29. import java.util.List;
  30. import javax.annotation.CheckForNull;
  31. import javax.annotation.Nullable;
  32. import net.jpountz.lz4.LZ4BlockInputStream;
  33. import net.jpountz.lz4.LZ4BlockOutputStream;
  34. import org.apache.commons.io.IOUtils;
  35. import org.sonar.db.protobuf.DbFileSources;
  36. import static com.google.common.base.Splitter.on;
  37. import static java.lang.String.format;
  38. public class FileSourceDto {
  39. private static final String SIZE_LIMIT_EXCEEDED_EXCEPTION_MESSAGE = "Protocol message was too large. May be malicious. " +
  40. "Use CodedInputStream.setSizeLimit() to increase the size limit.";
  41. private static final Joiner LINE_RETURN_JOINER = Joiner.on('\n');
  42. public static final Splitter LINES_HASHES_SPLITTER = on('\n');
  43. public static final int LINE_COUNT_NOT_POPULATED = -1;
  44. private Long id;
  45. private String projectUuid;
  46. private String fileUuid;
  47. private long createdAt;
  48. private long updatedAt;
  49. private String lineHashes;
  50. /**
  51. * When {@code line_count} column has been added, it's been populated with value {@link #LINE_COUNT_NOT_POPULATED -1},
  52. * which implies all existing files sources have this value at the time SonarQube is upgraded.
  53. * <p>
  54. * Column {@code line_count} is populated with the correct value from every new files and for existing files as the
  55. * project they belong to is analyzed for the first time after the migration.
  56. * <p>
  57. * Method {@link #getLineCount()} hides this migration-only-related complexity by either returning the value
  58. * of column {@code line_count} when its been populated, or computed the returned value from the value of column
  59. * {@code line_hashes}.
  60. */
  61. private int lineCount = LINE_COUNT_NOT_POPULATED;
  62. private String srcHash;
  63. private byte[] binaryData = new byte[0];
  64. private String dataHash;
  65. private String revision;
  66. @Nullable
  67. private Integer lineHashesVersion;
  68. public int getLineHashesVersion() {
  69. return lineHashesVersion != null ? lineHashesVersion : LineHashVersion.WITHOUT_SIGNIFICANT_CODE.getDbValue();
  70. }
  71. public FileSourceDto setLineHashesVersion(int lineHashesVersion) {
  72. this.lineHashesVersion = lineHashesVersion;
  73. return this;
  74. }
  75. public Long getId() {
  76. return id;
  77. }
  78. public FileSourceDto setId(Long id) {
  79. this.id = id;
  80. return this;
  81. }
  82. public String getProjectUuid() {
  83. return projectUuid;
  84. }
  85. public FileSourceDto setProjectUuid(String projectUuid) {
  86. this.projectUuid = projectUuid;
  87. return this;
  88. }
  89. public String getFileUuid() {
  90. return fileUuid;
  91. }
  92. public FileSourceDto setFileUuid(String fileUuid) {
  93. this.fileUuid = fileUuid;
  94. return this;
  95. }
  96. @CheckForNull
  97. public String getDataHash() {
  98. return dataHash;
  99. }
  100. /**
  101. * MD5 of column BINARY_DATA. Used to know to detect data changes and need for update.
  102. */
  103. public FileSourceDto setDataHash(String s) {
  104. this.dataHash = s;
  105. return this;
  106. }
  107. public DbFileSources.Data decodeSourceData(byte[] binaryData) {
  108. try {
  109. return decodeRegularSourceData(binaryData);
  110. } catch (IOException e) {
  111. throw new IllegalStateException(
  112. format("Fail to decompress and deserialize source data [id=%s,fileUuid=%s,projectUuid=%s]", id, fileUuid, projectUuid),
  113. e);
  114. }
  115. }
  116. private static DbFileSources.Data decodeRegularSourceData(byte[] binaryData) throws IOException {
  117. try (LZ4BlockInputStream lz4Input = new LZ4BlockInputStream(new ByteArrayInputStream(binaryData))) {
  118. return DbFileSources.Data.parseFrom(lz4Input);
  119. } catch (InvalidProtocolBufferException e) {
  120. if (SIZE_LIMIT_EXCEEDED_EXCEPTION_MESSAGE.equals(e.getMessage())) {
  121. return decodeHugeSourceData(binaryData);
  122. }
  123. throw e;
  124. }
  125. }
  126. private static DbFileSources.Data decodeHugeSourceData(byte[] binaryData) throws IOException {
  127. try (LZ4BlockInputStream lz4Input = new LZ4BlockInputStream(new ByteArrayInputStream(binaryData))) {
  128. CodedInputStream input = CodedInputStream.newInstance(lz4Input);
  129. input.setSizeLimit(Integer.MAX_VALUE);
  130. return DbFileSources.Data.parseFrom(input);
  131. }
  132. }
  133. /**
  134. * Serialize and compress protobuf message {@link org.sonar.db.protobuf.DbFileSources.Data}
  135. * in the column BINARY_DATA.
  136. */
  137. public static byte[] encodeSourceData(DbFileSources.Data data) {
  138. ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
  139. LZ4BlockOutputStream compressedOutput = new LZ4BlockOutputStream(byteOutput);
  140. try {
  141. data.writeTo(compressedOutput);
  142. compressedOutput.close();
  143. return byteOutput.toByteArray();
  144. } catch (IOException e) {
  145. throw new IllegalStateException("Fail to serialize and compress source data", e);
  146. } finally {
  147. IOUtils.closeQuietly(compressedOutput);
  148. }
  149. }
  150. /**
  151. * Compressed value of serialized protobuf message {@link org.sonar.db.protobuf.DbFileSources.Data}
  152. */
  153. public byte[] getBinaryData() {
  154. return binaryData;
  155. }
  156. /**
  157. * Set compressed value of the protobuf message {@link org.sonar.db.protobuf.DbFileSources.Data}
  158. */
  159. public FileSourceDto setBinaryData(byte[] data) {
  160. this.binaryData = data;
  161. return this;
  162. }
  163. /**
  164. * Decompressed value of serialized protobuf message {@link org.sonar.db.protobuf.DbFileSources.Data}
  165. */
  166. public DbFileSources.Data getSourceData() {
  167. return decodeSourceData(binaryData);
  168. }
  169. public FileSourceDto setSourceData(DbFileSources.Data data) {
  170. this.binaryData = encodeSourceData(data);
  171. return this;
  172. }
  173. /** Used by MyBatis */
  174. public String getRawLineHashes() {
  175. return lineHashes;
  176. }
  177. public void setRawLineHashes(@Nullable String lineHashes) {
  178. this.lineHashes = lineHashes;
  179. }
  180. public List<String> getLineHashes() {
  181. if (lineHashes == null) {
  182. return Collections.emptyList();
  183. }
  184. return LINES_HASHES_SPLITTER.splitToList(lineHashes);
  185. }
  186. /**
  187. * @return the value of column {@code line_count} if populated, otherwise the size of {@link #getLineHashes()}.
  188. */
  189. public int getLineCount() {
  190. if (lineCount == LINE_COUNT_NOT_POPULATED) {
  191. return getLineHashes().size();
  192. }
  193. return lineCount;
  194. }
  195. public FileSourceDto setLineHashes(@Nullable List<String> lineHashes) {
  196. if (lineHashes == null) {
  197. this.lineHashes = null;
  198. this.lineCount = 0;
  199. } else if (lineHashes.isEmpty()) {
  200. this.lineHashes = null;
  201. this.lineCount = 1;
  202. } else {
  203. this.lineHashes = LINE_RETURN_JOINER.join(lineHashes);
  204. this.lineCount = lineHashes.size();
  205. }
  206. return this;
  207. }
  208. @CheckForNull
  209. public String getSrcHash() {
  210. return srcHash;
  211. }
  212. /**
  213. * Hash of file content. Value is computed by batch.
  214. */
  215. public FileSourceDto setSrcHash(@Nullable String srcHash) {
  216. this.srcHash = srcHash;
  217. return this;
  218. }
  219. public long getCreatedAt() {
  220. return createdAt;
  221. }
  222. public FileSourceDto setCreatedAt(long createdAt) {
  223. this.createdAt = createdAt;
  224. return this;
  225. }
  226. public long getUpdatedAt() {
  227. return updatedAt;
  228. }
  229. public FileSourceDto setUpdatedAt(long updatedAt) {
  230. this.updatedAt = updatedAt;
  231. return this;
  232. }
  233. public String getRevision() {
  234. return revision;
  235. }
  236. public FileSourceDto setRevision(@Nullable String revision) {
  237. this.revision = revision;
  238. return this;
  239. }
  240. }