/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
*
* 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 hudson.model.listeners;
import com.google.common.base.Function;
import hudson.ExtensionPoint;
import hudson.ExtensionList;
import hudson.Extension;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.Items;
import hudson.security.ACL;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.security.NotReallyRoleSensitiveCallable;
/**
* Receives notifications about CRUD operations of {@link Item}.
*
* @since 1.74
* @author Kohsuke Kawaguchi
*/
public class ItemListener implements ExtensionPoint {
private static final Logger LOGGER = Logger.getLogger(ItemListener.class.getName());
/**
* Called after a new job is created and added to {@link jenkins.model.Jenkins},
* before the initial configuration page is provided.
* <p>
* This is useful for changing the default initial configuration of newly created jobs.
* For example, you can enable/add builders, etc.
*/
public void onCreated(Item item) {
}
/**
* Called after a new job is created by copying from an existing job.
*
* For backward compatibility, the default implementation of this method calls {@link #onCreated(Item)}.
* If you choose to handle this method, think about whether you want to call super.onCopied or not.
*
*
* @param src
* The source item that the new one was copied from. Never null.
* @param item
* The newly created item. Never null.
*
* @since 1.325
* Before this version, a copy triggered {@link #onCreated(Item)}.
*/
public void onCopied(Item src, Item item) {
onCreated(item);
}
/**
* Called after all the jobs are loaded from disk into {@link jenkins.model.Jenkins}
* object.
*/
public void onLoaded() {
}
/**
* Called right before a job is going to be deleted.
*
* At this point the data files of the job is already gone.
*/
public void onDeleted(Item item) {
}
/**
* Called after a job is renamed.
* Most implementers should rather use {@link #onLocationChanged}.
* @param item
* The job being renamed.
* @param oldName
* The old name of the job.
* @param newName
* The new name of the job. Same as {@link Item#getName()}.
* @since 1.146
*/
public void onRenamed(Item item, String oldName, String newName) {
}
/**
* Called after an item’s fully-qualified location has changed.
* This might be because:
* <ul>
* <li>This item was renamed.
* <li>Some ancestor folder was renamed.
* <li>This item was moved between folders (or from a folder to Jenkins root or vice-versa).
* <li>Some ancestor folder was moved.
* </ul>
* Where applicable, {@link #onRenamed} will already have been called on this item or an ancestor.
* And where applicable, {@link #onLocationChanged} will already have been called on its ancestors.
* <p>This method should be used (instead of {@link #onRenamed}) by any code
* which seeks to keep (absolute) references to items up to date:
* if a persisted reference matches {@code oldFullName}, replace it with {@code newFullName}.
* @param item an item whose absolute position is now different
* @param oldFullName the former {@link Item#getFullName}
* @param newFullName the current {@link Item#getFullName}
* @see Items#computeRelativeNamesAfterRenaming
* @since 1.548
*/
public void onLocationChanged(Item item, String oldFullName, String newFullName) {}
/**
* Called after a job has its configuration updated.
*
* @since 1.460
*/
public void onUpdated(Item item) {
}
/**
* @since 1.446
* Called at the begenning of the orderly shutdown sequence to
* allow plugins to clean up stuff
*/
public void onBeforeShutdown() {
}
/**
* Registers this instance to Hudson and start getting notifications.
*
* @deprecated as of 1.286
* put {@link Extension} on your class to have it auto-registered.
*/
@Deprecated
public void register() {
all().add(this);
}
/**
* All the registered {@link ItemListener}s.
*/
public static ExtensionList<ItemListener> all() {
return ExtensionList.lookup(ItemListener.class);
}
// TODO JENKINS-21224 generalize this to a method perhaps in ExtensionList and use consistently from all listeners
private static void forAll(final /* java.util.function.Consumer<ItemListener> */Function<ItemListener,Void> consumer) {
for (ItemListener l : all()) {
try {
consumer.apply(l);
} catch (RuntimeException x) {
LOGGER.log(Level.WARNING, "failed to send event to listener of " + l.getClass(), x);
}
}
}
public static void fireOnCopied(final Item src, final Item result) {
forAll(new Function<ItemListener,Void>() {
@Override public Void apply(ItemListener l) {
l.onCopied(src, result);
return null;
}
});
}
public static void fireOnCreated(final Item item) {
forAll(new Function<ItemListener,Void>() {
@Override public Void apply(ItemListener l) {
l.onCreated(item);
return null;
}
});
}
public static void fireOnUpdated(final Item item) {
forAll(new Function<ItemListener,Void>() {
@Override public Void apply(ItemListener l) {
l.onUpdated(item);
return null;
}
});
}
/** @since 1.548 */
public static void fireOnDeleted(final Item item) {
forAll(new Function<ItemListener,Void>() {
@Override public Void apply(ItemListener l) {
l.onDeleted(item);
return null;
}
});
}
/**
* Calls {@link #onRenamed} and {@link #onLocationChanged} as appropriate.
* @param rootItem the topmost item whose location has just changed
* @param oldFullName the previous {@link Item#getFullName}
* @since 1.548
*/
public static void fireLocationChange(final Item rootItem, final String oldFullName) {
String prefix = rootItem.getParent().getFullName();
if (!prefix.isEmpty()) {
prefix += '/';
}
final String newFullName = rootItem.getFullName();
assert newFullName.startsWith(prefix);
int prefixS = prefix.length();
if (oldFullName.startsWith(prefix) && oldFullName.indexOf('/', prefixS) == -1) {
final String oldName = oldFullName.substring(prefixS);
final String newName = rootItem.getName();
assert newName.equals(newFullName.substring(prefixS));
forAll(new Function<ItemListener, Void>() {
@Override public Void apply(ItemListener l) {
l.onRenamed(rootItem, oldName, newName);
return null;
}
});
}
forAll(new Function<ItemListener, Void>() {
@Override public Void apply(ItemListener l) {
l.onLocationChanged(rootItem, oldFullName, newFullName);
return null;
}
});
if (rootItem instanceof ItemGroup) {
for (final Item child : ACL.impersonate(ACL.SYSTEM, new NotReallyRoleSensitiveCallable<List<Item>,RuntimeException>() {
@Override public List<Item> call() {
return Items.getAllItems((ItemGroup) rootItem, Item.class);
}
})) {
final String childNew = child.getFullName();
assert childNew.startsWith(newFullName);
assert childNew.charAt(newFullName.length()) == '/';
final String childOld = oldFullName + childNew.substring(newFullName.length());
forAll(new Function<ItemListener, Void>() {
@Override public Void apply(ItemListener l) {
l.onLocationChanged(child, childOld, childNew);
return null;
}
});
}
}
}
}