]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3344 Display metadata of SonarSource licenses
authorSimon Brandhof <simon.brandhof@gmail.com>
Tue, 20 Mar 2012 21:46:03 +0000 (22:46 +0100)
committerSimon Brandhof <simon.brandhof@gmail.com>
Tue, 20 Mar 2012 21:47:41 +0000 (22:47 +0100)
16 files changed:
sonar-plugin-api/src/main/java/org/sonar/api/PropertyType.java
sonar-plugin-api/src/main/java/org/sonar/api/config/License.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/config/PropertyDefinition.java
sonar-plugin-api/src/test/java/org/sonar/api/config/LicenseTest.java [new file with mode: 0644]
sonar-plugin-api/src/test/java/org/sonar/api/config/PropertyDefinitionTest.java
sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_BOOLEAN.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_FLOAT.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_INTEGER.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_LICENSE.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_PASSWORD.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_STRING.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_TEXT.html.erb

index 356e4c1a65c91d05b3c55499919cd2d6166bbb3a..aacf82cd1ceb8a82e52c4a3bedea4c4e9a522833 100644 (file)
  */
 package org.sonar.api;
 
+/**
+ * @since 2.15
+ */
 public enum PropertyType {
-  STRING, TEXT, PASSWORD, BOOLEAN, INTEGER, FLOAT, SINGLE_SELECT_LIST
+  /**
+   * Basic single line input field
+   */
+  STRING,
+
+  /**
+   * Multiple line text-area
+   */
+  TEXT,
+
+  /**
+   * Variation of {#STRING} with masked characters
+   */
+  PASSWORD,
+
+  /**
+   * True/False
+   */
+  BOOLEAN,
+
+  /**
+   * Integer value, positive or negative
+   */
+  INTEGER,
+
+  /**
+   * Floating point number
+   */
+  FLOAT,
+
+  /**
+   * Single select list with a list of options
+   */
+  SINGLE_SELECT_LIST,
+
+  /**
+   * Sonar Metric
+   */
+  METRIC,
+
+  /**
+   * SonarSource license
+   */
+  LICENSE
 }
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/License.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/License.java
new file mode 100644 (file)
index 0000000..bfc0cab
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.api.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Maps;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.DateUtils;
+
+import javax.annotation.Nullable;
+import java.io.StringReader;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * SonarSource license. This class aims to extract metadata but not to validate or - of course -
+ * to generate license
+ *
+ * @since 2.15
+ */
+public final class License {
+  private String product;
+  private String organization;
+  private String expirationDate;
+  private String type;
+  private String server;
+
+  private License(Map<String, String> properties) {
+    product = StringUtils.defaultString(properties.get("Product"), properties.get("Plugin"));
+    organization = StringUtils.defaultString(properties.get("Organisation"), properties.get("Name"));
+    expirationDate = StringUtils.defaultString(properties.get("Expiration"), properties.get("Expires"));
+    type = properties.get("Type");
+    server = properties.get("Server");
+  }
+
+  @Nullable
+  public String getProduct() {
+    return product;
+  }
+
+  @Nullable
+  public String getOrganization() {
+    return organization;
+  }
+
+  @Nullable
+  public String getExpirationDateAsString() {
+    return expirationDate;
+  }
+
+  @Nullable
+  public Date getExpirationDate() {
+    return DateUtils.parseDateQuietly(expirationDate);
+  }
+
+  public boolean isExpired() {
+    return isExpired(new Date());
+  }
+
+  @VisibleForTesting
+  boolean isExpired(Date now) {
+    Date date = getExpirationDate();
+    return date != null && !date.after(org.apache.commons.lang.time.DateUtils.truncate(now, Calendar.DATE));
+  }
+
+  @Nullable
+  public String getType() {
+    return type;
+  }
+
+  @Nullable
+  public String getServer() {
+    return server;
+  }
+
+  public static License readBase64(String base64) {
+    return readPlainText(new String(Base64.decodeBase64(base64.getBytes())));
+  }
+
+  @VisibleForTesting
+  static License readPlainText(String data) {
+    Map<String, String> props = Maps.newHashMap();
+    StringReader reader = new StringReader(data);
+    try {
+      List<String> lines = IOUtils.readLines(reader);
+      for (String line : lines) {
+        if (StringUtils.isNotBlank(line) && line.indexOf(':') > 0) {
+          String key = StringUtils.substringBefore(line, ":");
+          String value = StringUtils.substringAfter(line, ":");
+          props.put(StringUtils.trimToEmpty(key), StringUtils.trimToEmpty(value));
+        }
+      }
+
+    } catch (Exception e) {
+      // silently ignore
+
+    } finally {
+      IOUtils.closeQuietly(reader);
+    }
+    return new License(props);
+  }
+}
index ab4446eb01e7177e979abd01a29b0b320bcbf71f..658d328422c42dba0ca9b892181cd4fd5beda660 100644 (file)
@@ -82,12 +82,17 @@ public final class PropertyDefinition {
   }
 
   private PropertyType fixType(String key, PropertyType type) {
-    // Auto-detect passwords for old versions of plugins that
-    // do not declare the type
-    if (type==PropertyType.STRING && StringUtils.endsWith(key, ".password.secured")) {
-      return PropertyType.PASSWORD;
+    // Auto-detect passwords and licenses for old versions of plugins that
+    // do not declare property types
+    PropertyType fix = type;
+    if (type == PropertyType.STRING) {
+      if (StringUtils.endsWith(key, ".password.secured")) {
+        fix = PropertyType.PASSWORD;
+      } else if (StringUtils.endsWith(key, ".license.secured")) {
+        fix = PropertyType.LICENSE;
+      }
     }
-    return type;
+    return fix;
   }
 
   @VisibleForTesting
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/config/LicenseTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/config/LicenseTest.java
new file mode 100644 (file)
index 0000000..37e880e
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.api.config;
+
+import org.apache.commons.codec.binary.Base64;
+import org.hamcrest.core.Is;
+import org.junit.Test;
+import org.sonar.api.utils.DateUtils;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+public class LicenseTest {
+
+  private static final String V2_FORMAT = "Foo: bar\n" +
+    "Organisation: ABC \n" +
+    "Server: 12345   \n" +
+    "Product: SQALE\n" +
+    "  Expiration: 2012-05-18  \n" +
+    "Type:  EVALUATION   \n" +
+    "Other: field\n";
+
+  private static final String V1_FORMAT = "Foo: bar\n" +
+    "Name: ABC \n" +
+    "Plugin: SQALE\n" +
+    "  Expires: 2012-05-18  \n" +
+    "Other: field\n";
+
+  @Test
+  public void readPlainTest() {
+    License license = License.readPlainText(V2_FORMAT);
+
+    assertThat(license.getOrganization(), Is.is("ABC"));
+    assertThat(license.getServer(), Is.is("12345"));
+    assertThat(license.getProduct(), Is.is("SQALE"));
+    assertThat(license.getExpirationDateAsString(), Is.is("2012-05-18"));
+    assertThat(license.getType(), Is.is("EVALUATION"));
+  }
+
+  @Test
+  public void readPlainText_empty_fields() {
+    License license = License.readPlainText("");
+
+    assertThat(license.getOrganization(), nullValue());
+    assertThat(license.getServer(), nullValue());
+    assertThat(license.getProduct(), nullValue());
+    assertThat(license.getExpirationDateAsString(), nullValue());
+    assertThat(license.getExpirationDate(), nullValue());
+    assertThat(license.getType(), nullValue());
+  }
+
+  @Test
+  public void readPlainText_not_valid_input() {
+    License license = License.readPlainText("old pond ... a frog leaps in water’s sound");
+
+    assertThat(license.getOrganization(), nullValue());
+    assertThat(license.getServer(), nullValue());
+    assertThat(license.getProduct(), nullValue());
+    assertThat(license.getExpirationDateAsString(), nullValue());
+    assertThat(license.getExpirationDate(), nullValue());
+    assertThat(license.getType(), nullValue());
+  }
+
+  @Test
+  public void readPlainTest_version_1() {
+    License license = License.readPlainText(V1_FORMAT);
+
+    assertThat(license.getOrganization(), Is.is("ABC"));
+    assertThat(license.getServer(), nullValue());
+    assertThat(license.getProduct(), Is.is("SQALE"));
+    assertThat(license.getExpirationDateAsString(), Is.is("2012-05-18"));
+    assertThat(license.getType(), nullValue());
+  }
+
+  @Test
+  public void readBase64() {
+    License license = License.readBase64(new String(Base64.encodeBase64(V2_FORMAT.getBytes())));
+
+    assertThat(license.getOrganization(), Is.is("ABC"));
+    assertThat(license.getServer(), Is.is("12345"));
+    assertThat(license.getProduct(), Is.is("SQALE"));
+    assertThat(license.getExpirationDateAsString(), Is.is("2012-05-18"));
+    assertThat(license.getType(), Is.is("EVALUATION"));
+  }
+
+  @Test
+  public void readBase64_not_base64() {
+    License license = License.readBase64("çé '123$@");
+
+    assertThat(license.getOrganization(), nullValue());
+    assertThat(license.getServer(), nullValue());
+    assertThat(license.getProduct(), nullValue());
+    assertThat(license.getExpirationDateAsString(), nullValue());
+    assertThat(license.getExpirationDate(), nullValue());
+    assertThat(license.getType(), nullValue());
+  }
+
+  @Test
+  public void isExpired() {
+    License license = License.readPlainText(V2_FORMAT);
+
+    assertThat(license.isExpired(DateUtils.parseDate("2013-06-23")), is(true));
+    assertThat(license.isExpired(DateUtils.parseDate("2012-05-18")), is(true));
+    assertThat(license.isExpired(DateUtils.parseDateTime("2012-05-18T15:50:45+0100")), is(true));
+    assertThat(license.isExpired(DateUtils.parseDate("2011-01-01")), is(false));
+  }
+}
index 427698793d446d64f2667d375bfcae35a57c6f27..00557bdc231ce6e68b1f85c088a341ef11e64d1a 100644 (file)
@@ -158,7 +158,7 @@ public class PropertyDefinitionTest {
   }
 
   @Test
-  public void autodetectPasswordType() {
+  public void autoDetectPasswordType() {
     Properties props = AnnotationUtils.getClassAnnotation(OldScmPlugin.class, Properties.class);
     Property prop = props.value()[0];
 
@@ -167,4 +167,21 @@ public class PropertyDefinitionTest {
     assertThat(def.getKey(), Is.is("scm.password.secured"));
     assertThat(def.getType(), Is.is(PropertyType.PASSWORD));
   }
+
+  @Properties({
+    @Property(key = "views.license.secured", name = "Views license")
+  })
+  static class ViewsPlugin {
+  }
+
+  @Test
+  public void autoDetectLicenseType() {
+    Properties props = AnnotationUtils.getClassAnnotation(ViewsPlugin.class, Properties.class);
+    Property prop = props.value()[0];
+
+    PropertyDefinition def = PropertyDefinition.create(prop);
+
+    assertThat(def.getKey(), Is.is("views.license.secured"));
+    assertThat(def.getType(), Is.is(PropertyType.LICENSE));
+  }
 }
index 82f899f3e22283f293d93e46ebbcc4db39110e64..76517e30df8dded4b1ab89e50a0e548361b41604 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.ui;
 
 import org.apache.commons.configuration.Configuration;
 import org.slf4j.LoggerFactory;
+import org.sonar.api.config.License;
 import org.sonar.api.config.PropertyDefinitions;
 import org.sonar.api.config.Settings;
 import org.sonar.api.platform.ComponentContainer;
@@ -410,6 +411,10 @@ public final class JRubyFacade {
   public String generateRandomSecretKey() {
     return getContainer().getComponentByType(Settings.class).getEncryption().generateRandomSecretKey();
   }
+  
+  public License parseLicense(String base64) {
+    return License.readBase64(base64);
+  }
 
 
   public ReviewsNotificationManager getReviewsNotificationManager() {
index 2508e29c27235b6ee5d898bd420825c185594ab8..97fe97b7f60f0c314d9fa5116219ba4fb92bd4fd 100644 (file)
                value = Property.value(property.getKey(), (@project ? @project.id : nil), '') unless value
 
                # for backward-compatibility with properties that do not define the type TEXT
-               property_type = value.include?("\n") ? 'TEXT' : property.getType()
+               property_type = property.getType()
+               if property_type.to_s=='STRING' && value.include?("\n")
+                 property_type = 'TEXT'
+               end
+
         %>
             <tr class="<%= cycle('even', 'odd', :name => 'properties') -%>">
-              <td style="padding: 10px" id="foo_<%= property.getKey() -%>">
+              <td style="padding: 10px" id="block_<%= property.getKey() -%>">
                 <h3>
                   <%= message("property.#{property.key()}.name", :default => property.name()) -%>
                   <br/><span class="note"><%= property.getKey() -%></span>
index 55e4fc408f3a09c36ad654d0e23cf391495f85fb..9271bbd0c929bffa2d8bc45c472d5787f8afeff2 100644 (file)
@@ -1,4 +1,4 @@
-<select name="<%= h property.key -%>" id="input_<%= h property.key-%>">
+<select name="<%= h property.getKey() -%>" id="input_<%= h property.getKey() -%>">
   <option value="" <%= 'selected' if value.blank? -%>><%= message('default') -%></option>
   <option value="true" <%= 'selected' if value=='true' -%>><%= message('true') -%></option>
   <option value="false" <%= 'selected' if value=='false' -%>><%= message('false') -%></option>
index 6706e88ecb2cde4768fe746b4e004ea8a2d933d7..163677b4478533997318faa0015ba736695a5ced 100644 (file)
@@ -1 +1 @@
-<input type="text" name="<%= h property.key -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.key-%>"/>
\ No newline at end of file
+<input type="text" name="<%= h property.getKey() -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.getKey() -%>"/>
\ No newline at end of file
index 6706e88ecb2cde4768fe746b4e004ea8a2d933d7..5603dfe4a750a4c4fe06f24269a003145eb49b0b 100644 (file)
@@ -1 +1 @@
-<input type="text" name="<%= h property.key -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.key-%>"/>
\ No newline at end of file
+<input type="text" name="<%= h property.getKey() -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.getKey()-%>"/>
\ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_LICENSE.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_LICENSE.html.erb
new file mode 100644 (file)
index 0000000..69996fa
--- /dev/null
@@ -0,0 +1,45 @@
+<% if !value || value.blank? %>
+  <textarea rows="5" cols="80" class="width100" name="<%= h property.getKey() -%>" id="input_<%= h property.getKey() -%>"></textarea>
+<%
+   else
+     license = controller.java_facade.parseLicense(value)
+     date = license.getExpirationDateAsString()
+%>
+  <div class="width100">
+    <textarea rows="6" name="<%= h property.getKey() -%>" id="input_<%= h property.getKey() -%>" style="float: left;width: 390px"><%= h value -%></textarea>
+
+    <div style="margin-left: 400px">
+      <table>
+        <tr>
+          <td class="form-key-cell">Product:</td>
+          <td class="form-val-cell"><%= license.getProduct() || '-' -%></td>
+        </tr>
+        <tr>
+          <td class="form-key-cell">Organization:</td>
+          <td><%= license.getOrganization() || '-' -%></td>
+        </tr>
+        <tr>
+          <td class="form-key-cell">Expiration:</td>
+          <td>
+            <% if license.getExpirationDateAsString()
+              formatted_date = l(Date.parse(license.getExpirationDateAsString()))
+            %>
+              <%= license.isExpired() ? "<span class='error'>#{formatted_date}</span>" : formatted_date -%>
+            <% else %>
+              -
+            <% end %>
+
+          </td>
+        </tr>
+        <tr>
+          <td class="form-key-cell">Type:</td>
+          <td><%= license.getType() || '-' -%></td>
+        </tr>
+        <tr>
+          <td class="form-key-cell">Server:</td>
+          <td><%= license.getServer() || '-' -%></td>
+        </tr>
+      </table>
+    </div>
+  </div>
+<% end %>
\ No newline at end of file
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb
new file mode 100644 (file)
index 0000000..45ed790
--- /dev/null
@@ -0,0 +1,27 @@
+<select name="<%= h property.getKey() -%>" id="input_<%= h property.getKey() -%>">
+  <option value=""><%= message('default') -%></option>
+  <%
+   metrics_per_domain={}
+   Metric.all.select { |m| m.display? }.sort_by { |m| m.short_name }.each do |metric|
+     domain=metric.domain || ''
+     metrics_per_domain[domain]||=[]
+     metrics_per_domain[domain]<<metric
+   end
+
+   metrics_per_domain.keys.sort.each do |domain|
+%>
+
+     <optgroup label="<%= h domain -%>">
+<%
+     metrics_per_domain[domain].each do |m|
+       selected_attr = (m.key==value || m.id==value) ? " selected='selected'" : ''
+%>
+       <option value="<%= m.key -%>" <%= selected_attr -%>><%= m.short_name -%></option>
+<%
+     end
+%>
+   </optgroup>
+<%
+  end
+%>
+</select>
\ No newline at end of file
index 3b6d49854b7bdbbf8dfdf23174d0e850e7ce763d..e8b70971e02e3012f10b872d4998b50ebaac66c1 100644 (file)
@@ -1 +1 @@
-<input type="password" name="<%= h property.key -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.key-%>"/>
\ No newline at end of file
+<input type="password" name="<%= h property.getKey() -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.getKey() -%>"/>
\ No newline at end of file
index a317e48ef385e0b1272641480177670d348da917..ac0e0d3d86792508ce68d0db33c3c9407e6ce916 100644 (file)
@@ -1,4 +1,4 @@
-<select name="<%= h property.key -%>" id="input_<%= h property.key-%>">
+<select name="<%= h property.getKey() -%>" id="input_<%= h property.key-%>">
   <option value=""><%= message('default') -%></option>
   <% property.options.each do |option| %>
     <option value="<%= h option -%>" <%= 'selected' if value && value==option -%>><%= h option -%></option>
index 0dc07f7c872a3397c5d37b3a353febfe7a44a905..b4c3b451f4d84f638cbc12374b107a43ecd3fdc9 100644 (file)
@@ -1,2 +1,2 @@
-<input type="text" name="<%= h property.key -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.key-%>"/>
-<%= link_to_function(image_tag('zoom.png'), "enlargeTextInput('#{property.key}')", :class => 'nolink') -%>
\ No newline at end of file
+<input type="text" name="<%= h property.getKey() -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.getKey() -%>"/>
+<%= link_to_function(image_tag('zoom.png'), "enlargeTextInput('#{property.getKey()}')", :class => 'nolink') -%>
\ No newline at end of file
index c557137557ebd2bfde05bd0f2a683a8666487d89..dfefb03af9f633769a9a92630589278b5d6da476 100644 (file)
@@ -1 +1 @@
-<textarea rows="5" cols="80" class="width100" name="<%= h property.key -%>" id="input_<%= h property.key-%>"><%= h value if value -%></textarea>
\ No newline at end of file
+<textarea rows="5" cols="80" class="width100" name="<%= h property.getKey() -%>" id="input_<%= h property.getKey() -%>"><%= h value if value -%></textarea>
\ No newline at end of file