提交 5218b761 编写于 作者: K Kohsuke Kawaguchi

Introducing GlobalConfigurationCategory.

... instead of creating a copy of each GlobalConfiguration variant (like GlobalSecurityConfigurationPart) for the following reasons:

- This allows future UI to render tabs and other means to divide configurations into even smaller granularity, which is something that has been discussed before.

- Eliminates code duplication between GlobalConfiguration look-alikes.
上级 215e2257
......@@ -25,6 +25,8 @@
*/
package hudson;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import hudson.cli.CLICommand;
import hudson.console.ConsoleAnnotationDescriptor;
import hudson.console.ConsoleAnnotatorFactory;
......@@ -33,6 +35,7 @@ import hudson.model.ParameterDefinition.ParameterDescriptor;
import hudson.search.SearchableModelObject;
import hudson.security.AccessControlled;
import hudson.security.AuthorizationStrategy;
import hudson.security.GlobalSecurityConfiguration;
import hudson.security.Permission;
import hudson.security.SecurityRealm;
import hudson.security.captcha.CaptchaSupport;
......@@ -57,7 +60,8 @@ import hudson.views.MyViewsTabBar;
import hudson.views.ViewsTabBar;
import hudson.widgets.RenderOnDemandClosure;
import jenkins.model.GlobalConfiguration;
import jenkins.model.GlobalSecurityConfigurationPart;
import jenkins.model.GlobalConfigurationCategory;
import jenkins.model.GlobalConfigurationCategory.Unclassified;
import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithContextMenu;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
......@@ -769,8 +773,11 @@ public class Functions {
* Perhaps it is better to introduce another annotation element? But then,
* extensions shouldn't normally concern themselves about ordering too much, and the only reason
* we needed this for {@link GlobalConfiguration}s are for backward compatibility.
*
* @param predicate
* Filter the descriptors based on {@link GlobalConfigurationCategory}
*/
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfig() {
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfig(Predicate<GlobalConfigurationCategory> predicate) {
ExtensionList<Descriptor> exts = Jenkins.getInstance().getExtensionList(Descriptor.class);
List<Tag> r = new ArrayList<Tag>(exts.size());
......@@ -778,7 +785,13 @@ public class Functions {
Descriptor d = c.getInstance();
if (d.getGlobalConfigPage()==null) continue;
r.add(new Tag(d instanceof GlobalConfiguration ? c.ordinal() : 0, d));
if (d instanceof GlobalConfiguration) {
if (predicate.apply(((GlobalConfiguration)d).getCategory()))
r.add(new Tag(c.ordinal(), d));
} else {
if (predicate.apply(GlobalConfigurationCategory.get(Unclassified.class)))
r.add(new Tag(0, d));
}
}
Collections.sort(r);
......@@ -788,22 +801,12 @@ public class Functions {
return DescriptorVisibilityFilter.apply(Jenkins.getInstance(),answer);
}
public static Collection<Descriptor> getSortedDescriptorsForGlobalSecurityConfig() {
ExtensionList<GlobalSecurityConfigurationPart> exts = Jenkins.getInstance().getExtensionList(GlobalSecurityConfigurationPart.class);
List<Tag> r = new ArrayList<Tag>(exts.size());
for (ExtensionComponent<GlobalSecurityConfigurationPart> c : exts.getComponents()) {
Descriptor d = c.getInstance();
if (d.getGlobalConfigPage()==null) continue;
r.add(new Tag(c.ordinal(), d));
}
Collections.sort(r);
List<Descriptor> answer = new ArrayList<Descriptor>(r.size());
for (Tag d : r) answer.add(d.d);
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfig() {
return getSortedDescriptorsForGlobalConfig(Predicates.<GlobalConfigurationCategory>alwaysTrue());
}
return DescriptorVisibilityFilter.apply(Jenkins.getInstance(),answer);
public static Collection<Descriptor> getSortedDescriptorsForGlobalConfigNoSecurity() {
return getSortedDescriptorsForGlobalConfig(Predicates.not(GlobalSecurityConfiguration.FILTER));
}
private static class Tag implements Comparable<Tag> {
......
......@@ -23,6 +23,7 @@
*/
package hudson.security;
import com.google.common.base.Predicate;
import hudson.BulkChange;
import hudson.Extension;
import hudson.Functions;
......@@ -38,6 +39,7 @@ import java.util.logging.Logger;
import javax.servlet.ServletException;
import jenkins.model.GlobalConfigurationCategory;
import jenkins.model.Jenkins;
import jenkins.util.ServerTcpPort;
import net.sf.json.JSONObject;
......@@ -100,7 +102,7 @@ public class GlobalSecurityConfiguration extends ManagementLink {
// persist all the additional security configs
boolean result = true;
for(Descriptor<?> d : Functions.getSortedDescriptorsForGlobalSecurityConfig()){
for(Descriptor<?> d : Functions.getSortedDescriptorsForGlobalConfig(FILTER)){
result &= configureDescriptor(req,json,d);
}
......@@ -132,11 +134,17 @@ public class GlobalSecurityConfiguration extends ManagementLink {
@Override
public String getUrlName() {
return "globalSecurity";
return "configureSecurity";
}
@Override
public Permission getRequiredPermission() {
return Jenkins.ADMINISTER;
}
public static Predicate<GlobalConfigurationCategory> FILTER = new Predicate<GlobalConfigurationCategory>() {
public boolean apply(GlobalConfigurationCategory input) {
return input instanceof GlobalConfigurationCategory.Security;
}
};
}
......@@ -24,7 +24,8 @@
package hudson.security.csrf;
import hudson.Extension;
import jenkins.model.GlobalSecurityConfigurationPart;
import jenkins.model.GlobalConfiguration;
import jenkins.model.GlobalConfigurationCategory;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
......@@ -35,7 +36,12 @@ import org.kohsuke.stapler.StaplerRequest;
* @author Kohsuke Kawaguchi
*/
@Extension(ordinal=195) // immediately after the security setting
public class GlobalCrumbIssuerConfiguration extends GlobalSecurityConfigurationPart {
public class GlobalCrumbIssuerConfiguration extends GlobalConfiguration {
@Override
public GlobalConfigurationCategory getCategory() {
return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Security.class);
}
@Override
public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
// for compatibility reasons, the actual value is stored in Jenkins
......
......@@ -31,6 +31,15 @@ public abstract class GlobalConfiguration extends Descriptor<GlobalConfiguration
return this;
}
/**
* Every {@link GlobalConfiguration} belongs to a specific category.
*
* @return never null, always the same value for a given instance of {@link GlobalConfiguration}.
*/
public GlobalConfigurationCategory getCategory() {
return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Unclassified.class);
}
/**
* Unless this object has additional web presence, display name is not used at all.
* So default to "".
......
package jenkins.model;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.model.ModelObject;
import hudson.security.*;
import hudson.security.Messages;
/**
* Grouping of related {@link GlobalConfiguration}s.
*
* <p>
* To facilitate the separation of the global configuration into multiple pages, tabs, and so on,
* {@link GlobalConfiguration}s are classified into categories (such as "security", "tools", as well
* as the catch all "unclassified".) Categories themselves are extensible &mdash; plugins may introduce
* its own category as well, although that should only happen if you are creating a big enough subsystem.
*
* <p>
* The primary purpose of this is to enable future UIs to split the global configurations to
* smaller pieces that can be individually looked at and updated.
*
* @author Kohsuke Kawaguchi
* @since 1.494
* @see GlobalConfiguration
*/
public abstract class GlobalConfigurationCategory implements ExtensionPoint, ModelObject {
/**
* One-line plain text message that explains what this category is about.
* This can be used in the UI to help the user pick the right category.
*
* The text should be longer than {@link #getDisplayName()}
*/
public abstract String getShortDescription();
/**
* Returns all the registered {@link GlobalConfiguration} descriptors.
*/
public static ExtensionList<GlobalConfigurationCategory> all() {
return Jenkins.getInstance().getExtensionList(GlobalConfigurationCategory.class);
}
public static <T extends GlobalConfigurationCategory> T get(Class<T> type) {
return all().get(type);
}
/**
* This category represents the catch-all I-dont-know-what-category-it-is instance,
* used for those {@link GlobalConfiguration}s that don't really deserve/need a separate
* category.
*
* Also used for backward compatibility. All {@link GlobalConfiguration}s without
* explicit category gets this as the category.
*
* In the current UI, this corresponds to the /configure link.
*/
@Extension
public static class Unclassified extends GlobalConfigurationCategory {
@Override
public String getShortDescription() {
return jenkins.management.Messages.ConfigureLink_Description();
}
public String getDisplayName() {
return jenkins.management.Messages.ConfigureLink_DisplayName();
}
}
/**
* Security related configurations.
*/
@Extension
public static class Security extends GlobalConfigurationCategory {
@Override
public String getShortDescription() {
return Messages.GlobalSecurityConfiguration_Description();
}
public String getDisplayName() {
return hudson.security.Messages.GlobalSecurityConfiguration_DisplayName();
}
}
}
package jenkins.model;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.model.Describable;
import hudson.model.Descriptor;
/**
* Convenient base class for extensions that contributes to the global security configuration page but nothing else.
*
* <h2>Views</h2>
* <p>
* Subtypes of this class should define <tt>config.groovy</tt> that gets pulled into the global security configuration page.
*
*
* @author Dominik Bartholdi
* @since 1.494
*/
public abstract class GlobalSecurityConfigurationPart extends Descriptor<GlobalSecurityConfigurationPart> implements ExtensionPoint, Describable<GlobalSecurityConfigurationPart> {
protected GlobalSecurityConfigurationPart() {
super(self());
}
public final Descriptor<GlobalSecurityConfigurationPart> getDescriptor() {
return this;
}
/**
* Unless this object has additional web presence, display name is not used at all.
* So default to "".
*/
public String getDisplayName() {
return "";
}
@Override
public String getGlobalConfigPage() {
return getConfigPage();
}
/**
* Returns all the registered {@link GlobalSecurityConfigurationPart} descriptors.
*/
public static ExtensionList<GlobalSecurityConfigurationPart> all() {
return Jenkins.getInstance().<GlobalSecurityConfigurationPart,GlobalSecurityConfigurationPart>getDescriptorList(GlobalSecurityConfigurationPart.class);
// pointless type parameters help work around bugs in javac in earlier versions http://codepad.org/m1bbFRrH
}
}
package hudson.security.GlobalSecurityConfiguration
import hudson.security.SecurityRealm
import hudson.security.AuthorizationStrategy
def f=namespace(lib.FormTagLib)
f.optionalBlock( field:"useSecurity", title:_("Enable security"), checked:app.useSecurity) {
f.entry (title:_("TCP port for JNLP slave agents"), field:"slaveAgentPort") {
f.serverTcpPort()
}
f.dropdownDescriptorSelector(title:_("Markup Formatter"),descriptors: hudson.markup.MarkupFormatterDescriptor.all(), field: 'markupFormatter')
f.entry(title:_("Access Control")) {
table(style:"width:100%") {
f.descriptorRadioList(title:_("Security Realm"),varName:"realm", instance:app.securityRealm, descriptors:SecurityRealm.all())
f.descriptorRadioList(title:_("Authorization"), varName:"authorization", instance:app.authorizationStrategy, descriptors:AuthorizationStrategy.all())
}
}
}
package hudson.security.GlobalSecurityConfiguration
import hudson.security.SecurityRealm
import hudson.markup.MarkupFormatterDescriptor
import hudson.security.AuthorizationStrategy
import jenkins.model.GlobalConfiguration
import hudson.Functions
import hudson.model.Descriptor
def f=namespace(lib.FormTagLib)
def l=namespace(lib.LayoutTagLib)
def st=namespace("jelly:stapler")
l.layout(norefresh:true, permission:app.ADMINISTER, title:my.displayName) {
l.main_panel {
h1 {
img(src:"${imagesURL}/48x48/secure.png", height:48,width:48)
text(my.displayName)
}
p()
div(class:"behavior-loading", _("LOADING"))
f.form(method:"post",name:"config",action:"configure") {
set("instance",my);
f.optionalBlock( field:"useSecurity", title:_("Enable security"), checked:app.useSecurity) {
f.entry (title:_("TCP port for JNLP slave agents"), field:"slaveAgentPort") {
f.serverTcpPort()
}
f.dropdownDescriptorSelector(title:_("Markup Formatter"),descriptors: MarkupFormatterDescriptor.all(), field: 'markupFormatter')
f.entry(title:_("Access Control")) {
table(style:"width:100%") {
f.descriptorRadioList(title:_("Security Realm"),varName:"realm", instance:app.securityRealm, descriptors:SecurityRealm.all())
f.descriptorRadioList(title:_("Authorization"), varName:"authorization", instance:app.authorizationStrategy, descriptors:AuthorizationStrategy.all())
}
}
}
Functions.getSortedDescriptorsForGlobalConfig(my.FILTER).each { Descriptor descriptor ->
set("descriptor",descriptor)
set("instance",descriptor)
f.rowSet(name:descriptor.jsonSafeClassName) {
st.include(from:descriptor, page:descriptor.globalConfigPage)
}
}
f.block {
div(id:"bottom-sticker") {
div(class:"bottom-sticker-inner") {
f.submit(value:_("Save"))
f.apply()
}
}
}
}
}
}
<!-- The MIT License Copyright (c) 2012, Dominik Bartholdi,
Inc. Permission is hereby
granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions: The above copyright notice and this
permission notice shall be included in all copies or substantial portions
of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. -->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:layout norefresh="true" permission="${app.ADMINISTER}"
title="${it.displayName}">
<l:main-panel>
<h1><img src="${imagesURL}/48x48/secure.png" height="48" width="48" /> ${it.displayName}</h1>
<p>
</p>
<div class="behavior-loading">${%LOADING}</div>
<f:form method="post" name="config" action="configure">
<j:set var="instance" value="${it}" />
<j:set var="descriptor" value="${instance.descriptor}" />
<st:include page="config.groovy" />
<!-- global security configuration from everyone -->
<j:forEach var="descriptor" items="${h.getSortedDescriptorsForGlobalSecurityConfig()}">
<j:set var="instance" value="${descriptor}" /><!-- this makes the <f:textbox field=.../> work -->
<f:rowSet name="${descriptor.jsonSafeClassName}">
<st:include page="${descriptor.globalConfigPage}" from="${descriptor}" />
</f:rowSet>
</j:forEach>
<f:block>
<div id="bottom-sticker">
<div class="bottom-sticker-inner">
<f:submit value="${%Save}" />
<f:apply />
</div>
</div>
</f:block>
</f:form>
</l:main-panel>
</l:layout>
</j:jelly>
......@@ -53,7 +53,7 @@ THE SOFTWARE.
</f:entry>
<!-- global configuration from everyone -->
<j:forEach var="descriptor" items="${h.getSortedDescriptorsForGlobalConfig()}">
<j:forEach var="descriptor" items="${h.getSortedDescriptorsForGlobalConfigNoSecurity()}">
<j:set var="instance" value="${descriptor}" /><!-- this makes the <f:textbox field=.../> work -->
<f:rowSet name="${descriptor.jsonSafeClassName}">
<st:include page="${descriptor.globalConfigPage}" from="${descriptor}" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册