/* * The MIT License * * Copyright 2015 CloudBees, 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. */ package com.cloudbees.hudson.plugins.folder; import com.cloudbees.hudson.plugins.folder.computed.ComputedFolder; import com.cloudbees.hudson.plugins.folder.health.FolderHealthMetricDescriptor; import hudson.model.DescriptorVisibilityFilter; import hudson.model.Item; import hudson.model.TopLevelItem; import hudson.model.TopLevelItemDescriptor; import hudson.views.ViewsTabBar; import java.io.File; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckForNull; import jenkins.model.Jenkins; import jenkins.model.ProjectNamingStrategy; import org.apache.commons.jelly.Script; import org.apache.commons.jelly.XMLOutput; import org.jenkins.ui.icon.IconSpec; import org.kohsuke.stapler.MetaClass; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.WebApp; import org.kohsuke.stapler.jelly.DefaultScriptInvoker; import org.kohsuke.stapler.jelly.JellyClassTearOff; /** * Category of {@link AbstractFolder}. * @since 4.11-beta-1 */ public abstract class AbstractFolderDescriptor extends TopLevelItemDescriptor implements IconSpec { /** * Explicit constructor. * * @param clazz the explicit {@link AbstractFolder} sub-class that this descriptor is for. * @see TopLevelItemDescriptor#TopLevelItemDescriptor(Class) */ protected AbstractFolderDescriptor(Class<? extends AbstractFolder> clazz) { super(clazz); } /** * Default constructor. * * @see TopLevelItemDescriptor#TopLevelItemDescriptor() */ protected AbstractFolderDescriptor() { } @Override public String getDisplayName() { return Messages.Folder_DisplayName(); } /** * Needed if it wants AbstractFolderDescriptor implementations are categorized in Jenkins 2.x. * * TODO: Override when the baseline is upgraded to 2.x * TODO: Replace to {@code NestedProjectsCategory.ID} * * @return A string it represents a ItemCategory identifier. */ public String getCategoryId() { return "nested-projects"; } /** * Properties that can be configured for this type of {@link AbstractFolder} subtype. */ public List<AbstractFolderPropertyDescriptor> getPropertyDescriptors() { return AbstractFolderPropertyDescriptor.getApplicableDescriptors(clazz.asSubclass(AbstractFolder.class)); } /** * Health metrics that can be configured for this type of {@link AbstractFolder} subtype. */ public List<FolderHealthMetricDescriptor> getHealthMetricDescriptors() { List<FolderHealthMetricDescriptor> r = new ArrayList<FolderHealthMetricDescriptor>(); for (FolderHealthMetricDescriptor d : FolderHealthMetricDescriptor.all()) { if (d.isApplicable(clazz.asSubclass(AbstractFolder.class))) { r.add(d); } } return r; } /** * Gets the {@link FolderIconDescriptor}s applicable for this folder type. * * @since FIXME */ public List<FolderIconDescriptor> getIconDescriptors() { List<FolderIconDescriptor> r = new ArrayList<FolderIconDescriptor>(); for (FolderIconDescriptor p : FolderIconDescriptor.all()) { if (p.isApplicable(clazz.asSubclass(AbstractFolder.class))) { r.add(p); } } StaplerRequest request = Stapler.getCurrentRequest(); if (request != null) { AbstractFolder folder = request.findAncestorObject(AbstractFolder.class); if (folder != null) { return DescriptorVisibilityFilter.apply(folder, r); } } return r; } // TODO remove once baseline 2.0 public String getDescription() { Stapler stapler = Stapler.getCurrent(); if (stapler != null) { try { WebApp webapp = WebApp.getCurrent(); MetaClass meta = webapp.getMetaClass(this); Script s = meta.loadTearOff(JellyClassTearOff.class).findScript("newInstanceDetail"); if (s == null) { return ""; } DefaultScriptInvoker dsi = new DefaultScriptInvoker(); StringWriter sw = new StringWriter(); XMLOutput xml = dsi.createXMLOutput(sw, true); dsi.invokeScript(Stapler.getCurrentRequest(), Stapler.getCurrentResponse(), s, this, xml); return sw.toString(); } catch (Exception e) { Logger.getLogger(clazz.getName()).log(Level.WARNING, e.getMessage(), e); return ""; } } else { return ""; } } /** * Needed if it wants Folder are categorized in Jenkins 2.x. * * TODO: Override when the baseline is upgraded to 2.x * * @return A string it represents a URL pattern to get the Item icon in different sizes. */ public String getIconFilePathPattern() { return "plugin/cloudbees-folder/images/:size/folder.png"; } /** * {@inheritDoc} */ @Override public String getIconClassName() { return "icon-folder"; } public boolean isIconConfigurable() { return getIconDescriptors().size() > 1; } public boolean isTabBarConfigurable() { return Jenkins.getActiveInstance().getDescriptorList(ViewsTabBar.class).size() > 1; } public boolean isLookAndFeelConfigurable(AbstractFolder<?> folder) { return isIconConfigurable() || (isTabBarConfigurable() && folder.getFolderViews().isTabBarModifiable()) || (folder.getViews().size() > 1 && folder.getFolderViews().isPrimaryModifiable()); } /** * Folders, especially computed folders, may have requirements for using a different on-disk file name for child * items than the url-segment name. Typically this is to work around filesystem naming rules. * Regular folders typically would leave the naming of child items to {@link ProjectNamingStrategy} and thereby * prevent users from creating child items with names that do not comply with the {@link ProjectNamingStrategy}. * <p> * <strong>However,</strong> {@link ComputedFolder} instances may not have that luxury. The children of a * {@link ComputedFolder} may have names that come from an external system and the matching item must be created, * always. The obvious solution is that the {@link ComputedFolder} should mangle the supplied {@link Item#getName()} * but the side-effect is that the URLs of the child items will now be mangled. Additionally the filename * requirements can be rather onerous. Here is the most portable list of filename specification: * <ul> * <li>Assume case insensitive</li> * <li>Assume no filename can be longer than 32 characters</li> * <li>Assume that only the characters {@code A-Za-z0-9_.-} are available</li> * <li>Assume that there are some special reserved names such as {@code .} and {@code ..}</li> * <li>Assume that there are some problematic names to avoid such as {@code AUX}, {@code CON}, {@code NUL}, etc. * See <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx"> * Microsoft's page on "Naming Files, Paths, and Namespaces"</a> * (What's that you say, "Oh but we only run on Linux", perhaps but users may want to migrate from one OS * to an other by <strong>moving</strong> their {@code JENKINS_HOME} (or even parts of it) so if you are * mangling names, be sure to ensure that the mangled name is the same on all OSes * </li> * <li><a href="http://unicode.org/reports/tr15/">NFC vs. NFD</a> may be a concern as different filesystems * apply different rules and normalization to the filenames. This is primarily a concern when migrating from * before having a {@link ChildNameGenerator} to having a {@link ChildNameGenerator} as the migration will * require inference of the un-mangled name from the filesystem, which may or may not match the un-mangled * name from the source of the computation. Now POSIX does not specify how the filesystem is supposed to handle * encoding of filenames and there can be strange behaviours, e.g. * <a href="http://stackoverflow.com/a/32663908/1589700">{@link File#listFiles()} is rumoured to always return * NFC on OS-X</a> * </li> * </ul> * The {@link ChildNameGenerator} at least allows an {@link AbstractFolder} to apply an on-disk naming strategy * that differs from the names used for the URLs. * <p> * If you implement a {@link ChildNameGenerator} it is strongly recommended to return a singleton for performance * reasons. * * @param <I> A wildcard parameter to assist type matching. * @return a (ideally singleton) instance of {@link ChildNameGenerator} for all instances of the concrete * {@link AbstractFolder} class or {@code null} if no name mangling will be performed. * @since 5.17 */ // TODO figure out how one could un-wind name mangling if one ever wanted to // TODO move the name mangling code from branch-api to this plugin so that everyone can use it @CheckForNull public <I extends TopLevelItem> ChildNameGenerator<AbstractFolder<I>,I> childNameGenerator() { return null; } }