/******************************************************************************* * * Copyright (c) 2013 Oracle Corporation. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Roy Varghese * *******************************************************************************/ package hudson.model; import hudson.PermalinkList; import hudson.XmlFile; import hudson.search.Search; import hudson.search.SearchIndex; import hudson.security.ACL; import hudson.security.Permission; import hudson.tasks.LogRotator; import hudson.util.RunList; import java.io.File; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.SortedMap; import org.eclipse.hudson.api.model.IJob; import org.eclipse.hudson.graph.Graph; import org.kohsuke.stapler.StaplerProxy; import org.springframework.security.access.AccessDeniedException; /** * A decorator around a top-level job that loads the real job lazily and hangs * onto it with a weak reference. * * @author Roy Varghese */ final class LazyTopLevelItem implements TopLevelItem, IJob, StaplerProxy { // Parameters to the Item class Key { final XmlFile configFile; final ItemGroup parent; final String name; private boolean loadError = false; Key(XmlFile configFile, ItemGroup parent, String name) { this.configFile = configFile; this.parent = parent; this.name = name; } public boolean equals(Object o) { boolean equal = false; if ( o.getClass() == Key.class) { Key other = (Key) o; equal = name.equals(other.name) && configFile.equals(other.configFile) && parent.equals(other.parent); } return equal; } @Override public int hashCode() { return name.hashCode(); } public void setLoadErrorFlag() { loadError = true; } public void clearLoadErrorFlag() { loadError = false; } } private final Key key; private WeakReference<TopLevelItem> ref; // The cache reference private final TopLevelItemsCache itemsCache; // Log non-loadable Item only once. private boolean loggedError = false; // Type of the item. This is used to satisfy 'instanceof' checks against // LazyTopLevelItem for backward compatibility. Such checks are really intended // for the wrapped object, not for the wrapper. private Class itemType = null; LazyTopLevelItem(XmlFile configFile, ItemGroup parent, String name, TopLevelItem item) { this.key = new Key(configFile, parent, name); itemsCache = Hudson.getInstance().itemsCache(); // Add to cache if value is already created/available. if ( item != null ) { itemsCache.put(key, item); } } private Class itemType() { if ( itemType == null) { Item item = item(); if ( item != null) { itemType = item.getClass(); } } return itemType; } /** * Unwrap and return the decorated item from a LazyTopLevelItem. * * @return null if item is not LazyTopLevelItem, or if neither the * decorated nor the item itself can be cast into requested type. */ static <T> T getIfInstanceOf(Item item, Class<T> clazz) { if ( item.getClass() == LazyTopLevelItem.class) { LazyTopLevelItem lazyItem = (LazyTopLevelItem) item; TopLevelItem realItem = lazyItem.item(); if (clazz.isAssignableFrom(realItem.getClass())) { return (T) realItem; } } return clazz.isInstance(item)? clazz.cast(item): null; } /** * Load the item if not already loaded. * * @return */ private synchronized TopLevelItem item() { TopLevelItem item = (ref != null? ref.get(): null); if ( item == null ) { item = itemsCache.get(key); ref = new WeakReference( item ); } return item; } private IJob job() { final Item item = item(); assert IJob.class.isAssignableFrom(item.getClass()); return (IJob) item; } @Override public TopLevelItemDescriptor getDescriptor() { return item().getDescriptor(); } @Override public ItemGroup<? extends Item> getParent() { return key.parent; } @Override public Collection<? extends Job> getAllJobs() { return item().getAllJobs(); } @Override public String getName() { if ( key.loadError ) { return key.name +"[In Error]"; } else { return key.name; } } @Override public String getFullName() { return item().getFullName(); } @Override public String getDisplayName() { return item().getDisplayName(); } @Override public String getFullDisplayName() { return item().getFullDisplayName(); } @Override public String getUrl() { return item().getUrl(); } @Override public String getShortUrl() { return item().getShortUrl(); } @Override public String getAbsoluteUrl() { return item().getAbsoluteUrl(); } @Override public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException { // No-op; } @Override public void onCopiedFrom(Item src) { item().onCopiedFrom(src); } @Override public void onCreatedFromScratch() { item().onCreatedFromScratch(); } @Override public void save() throws IOException { item().save(); } @Override public void delete() throws IOException, InterruptedException { item().delete(); } @Override public File getRootDir() { return item().getRootDir(); } @Override public Search getSearch() { return item().getSearch(); } @Override public String getSearchName() { return item().getSearchName(); } @Override public String getSearchUrl() { return item().getSearchUrl(); } @Override public SearchIndex getSearchIndex() { return item().getSearchIndex(); } @Override public ACL getACL() { return item().getACL(); } @Override public void checkPermission(Permission permission) throws AccessDeniedException { item().checkPermission(permission); } @Override public boolean hasPermission(Permission permission) { return item().hasPermission(permission); } @Override public Object getTarget() { return item(); } @Override public boolean isNameEditable() { return job().isNameEditable(); } @Override public LogRotator getLogRotator() { return job().getLogRotator(); } @Override public void setLogRotator(LogRotator logRotator) { job().setLogRotator(logRotator); } @Override public boolean supportsLogRotator() { return job().supportsLogRotator(); } @Override public Map getProperties() { return job().getProperties(); } @Override public List getAllProperties() { return job().getAllProperties(); } @Override public boolean isInQueue() { return job().isInQueue(); } @Override public Queue.Item getQueueItem() { return job().getQueueItem(); } @Override public boolean isBuilding() { return job().isBuilding(); } @Override public boolean isKeepDependencies() { return job().isKeepDependencies(); } @Override public int assignBuildNumber() throws IOException { return job().assignBuildNumber(); } @Override public int getNextBuildNumber() { return job().getNextBuildNumber(); } @Override public void updateNextBuildNumber(int next) throws IOException { job().updateNextBuildNumber(next); } @Override public void logRotate() throws IOException, InterruptedException { job().logRotate(); } @Override public List getWidgets() { return job().getWidgets(); } @Override public boolean isBuildable() { return job().isBuildable(); } @Override public PermalinkList getPermalinks() { return job().getPermalinks(); } @Override public RunList getBuilds() { return job().getBuilds(); } @Override public List getBuilds(Fingerprint.RangeSet rs) { return job().getBuilds(rs); } @Override public SortedMap getBuildsAsMap() { return job().getBuildsAsMap(); } @Override public Run getBuildByNumber(int n) { return job().getBuildByNumber(n); } @Override public Run getNearestBuild(int n) { return job().getNearestBuild(n); } @Override public Run getNearestOldBuild(int n) { return job().getNearestOldBuild(n); } @Override public Run getLastBuild() { return job().getLastBuild(); } @Override public Run getFirstBuild() { return job().getFirstBuild(); } @Override public Run getLastSuccessfulBuild() { return job().getLastSuccessfulBuild(); } @Override public Run getLastUnsuccessfulBuild() { return job().getLastUnsuccessfulBuild(); } @Override public Run getLastUnstableBuild() { return job().getLastUnstableBuild(); } @Override public Run getLastStableBuild() { return job().getLastStableBuild(); } @Override public Run getLastFailedBuild() { return job().getLastFailedBuild(); } @Override public Run getLastCompletedBuild() { return job().getLastCompletedBuild(); } @Override public List getLastBuildsOverThreshold(int numberOfBuilds, Result threshold) { return job().getLastBuildsOverThreshold(numberOfBuilds, threshold); } @Override public String getBuildStatusUrl() { return job().getBuildStatusUrl(); } @Override public BallColor getIconColor() { return job().getIconColor(); } @Override public HealthReport getBuildHealth() { return job().getBuildHealth(); } @Override public List getBuildHealthReports() { return job().getBuildHealthReports(); } @Override public Graph getBuildTimeGraph() { return job().getBuildTimeGraph(); } @Override public BuildTimelineWidget getTimeline() { return job().getTimeline(); } @Override public String getCreatedBy() { return job().getCreatedBy(); } @Override public long getCreationTime() { return job().getCreationTime(); } }