diff --git a/ccm-cms/src/com/arsdigita/cms/CMSConfig.java b/ccm-cms/src/com/arsdigita/cms/CMSConfig.java index 0f563cb0b..6ee5291c2 100755 --- a/ccm-cms/src/com/arsdigita/cms/CMSConfig.java +++ b/ccm-cms/src/com/arsdigita/cms/CMSConfig.java @@ -511,7 +511,7 @@ public final class CMSConfig extends AbstractConfig { // /////////////////////////////////////////// // Content Section config related parameters // /////////////////////////////////////////// -// Nolonger used, +// Nolonger used, // replaced by c.ad.cms.ContentSection.getDefaultSection().getName() // private final Parameter m_defaultSection = new StringParameter( // "com.arsdigita.cms.default_content_section", @@ -546,12 +546,12 @@ public final class CMSConfig extends AbstractConfig { // private final Parameter m_itemSearchFlatBrowsePaneEnable = new BooleanParameter( // "com.arsdigita.cms.item_search.flat_browse_pane.enable", // Parameter.REQUIRED, -// true); +// true); private final Parameter m_itemSearchFlatBrowsePanePageSize = new IntegerParameter( "com.arsdigita.cms.item_search.flat_browse_pane.page_size", Parameter.REQUIRED, 20); - + ///////////////////////////////////////////// // FolderBrowse ///////////////////////////////////////////// @@ -567,7 +567,7 @@ public final class CMSConfig extends AbstractConfig { Parameter.REQUIRED, 100); ////////////////////////////////////////////// - //If set to true the old style ItemLifecycleItemPane (allows you to + //If set to true the old style ItemLifecycleItemPane (allows you to //republish and withdraw items) is used. Otherwise the new style form is //used, which is more secure against wrong clicks. ////////////////////////////////////////////// @@ -577,9 +577,9 @@ public final class CMSConfig extends AbstractConfig { Parameter.REQUIRED, false); //////////////////////////////////////////////// - //Actives threaded publishing. If active, the publish process for + //Actives threaded publishing. If active, the publish process for //content items will run in a separate thread. May useful if you have - //large objects. + //large objects. //////////////////////////////////////////////////// private final Parameter m_threadPublishing = new BooleanParameter( "com.arsdigita.cms.lifecycle.threaded_publishing", @@ -604,15 +604,19 @@ public final class CMSConfig extends AbstractConfig { private final Parameter m_imageBrowserThumbnailMaxHeight = new IntegerParameter( "com.arsdigita.cms.image_browser.thumbnail_max_height", Parameter.REQUIRED, - 50); - + 50); + ///////////////////////////////////////////////// // ImageCache Parameter ///////////////////////////////////////////////// - private final Parameter m_imageCache = new BooleanParameter( + private final Parameter m_imageCacheEnabled = new BooleanParameter( "com.arsdigita.cms.image_cache.enable", Parameter.REQUIRED, true); + private final Parameter m_imageCachePrefetchEnabled = new BooleanParameter( + "com.arsdigita.cms.image_cache.prefetch_enable", + Parameter.REQUIRED, + false); private final Parameter m_imageCacheMaxSize = new IntegerParameter( "com.arsdigita.cms.image_cache.max_size", Parameter.REQUIRED, @@ -620,16 +624,16 @@ public final class CMSConfig extends AbstractConfig { private final Parameter m_imageCacheMaxAge = new IntegerParameter( "com.arsdigita.cms.image_cache.max_age", Parameter.REQUIRED, - 300); - - + 300); + + // /////////////////////////////////////////// // publishToFile package related parameter // /////////////////////////////////////////// // Moved to publishToFile.PublishToFileConfig as of version 6.0.2 // private final Parameter m_disableItemPfs; // private final Parameter m_publishToFileClass; - + /** * Constructor, but do NOT instantiate this class directly. * @@ -690,7 +694,7 @@ public final class CMSConfig extends AbstractConfig { register(m_hideTextAssetUploadFile); register(m_allowCategoryCreateUseContext); register(m_allowContentCreateInSectionListing); - register(m_hideLegacyPublicSiteLink); + register(m_hideLegacyPublicSiteLink); // Content Center (Workspace) config related parameters register(m_contentCenterMap); @@ -715,9 +719,10 @@ public final class CMSConfig extends AbstractConfig { // ImageBrowser register(m_imageBrowserThumbnailMaxWidth); register(m_imageBrowserThumbnailMaxHeight); - + // ImageCache Parameter - register(m_imageCache); + register(m_imageCacheEnabled); + register(m_imageCachePrefetchEnabled); register(m_imageCacheMaxSize); register(m_imageCacheMaxAge); @@ -726,9 +731,9 @@ public final class CMSConfig extends AbstractConfig { // register(m_disableItemPfs); // register(m_publishToFileClass); -// register(m_itemSearchFlatBrowsePaneEnable); +// register(m_itemSearchFlatBrowsePaneEnable); register(m_itemSearchFlatBrowsePanePageSize); - + loadInfo(); } @@ -945,7 +950,7 @@ public final class CMSConfig extends AbstractConfig { } // Store class reference so it can be recreated for each page. // This requires a fix to all components using extraXMLGenerators, - // for example see the currently only one in core/cms: GreetingItemExtraXML + // for example see the currently only one in core/cms: GreetingItemExtraXML gens.add(gen.getClass()); // XXX assumes default ctor } @@ -1115,8 +1120,8 @@ public final class CMSConfig extends AbstractConfig { } /** - * Retrieve whether to allow creation of a new Use Context in category tab - * of content sections. "Use Context" is used to constitute a category + * Retrieve whether to allow creation of a new Use Context in category tab + * of content sections. "Use Context" is used to constitute a category * hierarchy in core. It is superseded by the construct "Category Domain" * in Terms (ccm-ldn-terms). * Global parameter for all content sections. Default is false because all @@ -1176,9 +1181,13 @@ public final class CMSConfig extends AbstractConfig { public Integer getImageBrowserThumbnailMaxHeight() { return (Integer) get(m_imageBrowserThumbnailMaxHeight); } - - public Boolean getImageCacheEnable() { - return (Boolean) get(m_imageCache); + + public Boolean getImageCacheEnabled() { + return (Boolean) get(m_imageCacheEnabled); + } + + public Boolean getImageCachePrefetchEnabled() { + return (Boolean) get(m_imageCachePrefetchEnabled); } public Integer getImageCacheMaxSize() { @@ -1188,11 +1197,11 @@ public final class CMSConfig extends AbstractConfig { public Integer getImageCacheMaxAge() { return (Integer) get(m_imageCacheMaxAge); } - + // public Boolean getItemSearchFlatBrowsePaneEnable() { // return (Boolean) get(m_itemSearchFlatBrowsePaneEnable); // } - + public Integer getItemSearchFlatBrowsePanePageSize() { return (Integer) get(m_itemSearchFlatBrowsePanePageSize); } diff --git a/ccm-cms/src/com/arsdigita/cms/CMSConfig_parameter.properties b/ccm-cms/src/com/arsdigita/cms/CMSConfig_parameter.properties index 6d8afb691..2d5fc30fe 100755 --- a/ccm-cms/src/com/arsdigita/cms/CMSConfig_parameter.properties +++ b/ccm-cms/src/com/arsdigita/cms/CMSConfig_parameter.properties @@ -86,7 +86,7 @@ com.arsdigita.cms.hide_udct_ui.format=[boolean] com.arsdigita.cms.dhtml_editor_config.title=DHTML Editor Configuration com.arsdigita.cms.dhtml_editor_config.purpose=LIsts the config object name and Javascript source location for its definition -com.arsdigita.cms.dhtml_editor_config.example=HTMLArea.Config.CMSStyled,/assets/htmlarea/config/Styled.js +com.arsdigita.cms.dhtml_editor_config.example=HTMLArea.Config.CMSStyled,/assets/htmlarea/config/Styled.js com.arsdigita.cms.dhtml_editor_config.format=[string] com.arsdigita.cms.dhtml_editor_plugins.title=DHTML Editor Plugins @@ -94,7 +94,7 @@ com.arsdigita.cms.dhtml_editor_plugins.purpose=Defines which plugins to use com.arsdigita.cms.dhtml_editor_plugins.example=TableOperations,CSS com.arsdigita.cms.dhtml_editor_plugins.format=[string,string,string] -com.arsdigita.cms.dhtml_editor_hidden_buttons.title=DHTML Editor buttons to hide +com.arsdigita.cms.dhtml_editor_hidden_buttons.title=DHTML Editor buttons to hide com.arsdigita.cms.dhtml_editor_hidden_buttons.purpose=Prevent undesirable functions from being made available, eg images should only be added through the cms, not the html area com.arsdigita.cms.dhtml_editor_hidden_buttons.example=insertimage com.arsdigita.cms.dhtml_editor_hidden_buttons.format=[string,string,string] @@ -119,7 +119,7 @@ com.arsdigita.cms.save_text_cleans_word_tags.purpose=Wether the Wysiwyg editor s com.arsdigita.cms.save_text_cleans_word_tags.example=false com.arsdigita.cms.save_text_cleans_word_tags.format=[boolean] -com.arsdigita.cms.contentassets.ui.RelatedLinkPropertyForm.hideAdditionalResourceFields.title=Hide Additional Resource Fields +com.arsdigita.cms.contentassets.ui.RelatedLinkPropertyForm.hideAdditionalResourceFields.title=Hide Additional Resource Fields com.arsdigita.cms.contentassets.ui.RelatedLinkPropertyForm.hideAdditionalResourceFields.purpose=Hide Additional Resource Fields on RelatedLinkPropertyForm com.arsdigita.cms.contentassets.ui.RelatedLinkPropertyForm.hideAdditionalResourceFields.example=false com.arsdigita.cms.contentassets.ui.RelatedLinkPropertyForm.hideAdditionalResourceFields.format=[boolean] @@ -271,7 +271,7 @@ com.arsdigita.cms.lifecycle.use_old_style_item_lifecycle_item_pane.example = fal com.arsdigita.cms.lifecycle.use_old_style_item_lifecycle_item_pane.format = [Boolean] com.arsdigita.cms.lifecycle.threaded_publishing.title = Threaded publishing -com.arsdigita.cms.lifecycle.threaded_publishing.purpose = Decides if publishing is done in a thread (new behaviour) or directly (old, well tested behaviour). +com.arsdigita.cms.lifecycle.threaded_publishing.purpose = Decides if publishing is done in a thread (new behaviour) or directly (old, well tested behaviour). com.arsdigita.cms.lifecycle.threaded_publishing.example = false com.arsdigita.cms.lifecycle.threaded_com.arsdigita.cms.lifecycle.threaded_publishingpublishing.format = [boolean] @@ -300,6 +300,11 @@ com.arsdigita.cms.image_cache.enable.purpose=Enable server-side coherent image c com.arsdigita.cms.image_cache.enable.example=true|false com.arsdigita.cms.image_cache.enable.format=[boolean] +com.arsdigita.cms.image_cache.prefetch_enable.title=Enable prefetching image cache +com.arsdigita.cms.image_cache.prefetch_enable.purpose=Enable image cache to store original sized images for resized images. This will need lots of RAM and you probably won't see a difference. +com.arsdigita.cms.image_cache.prefetch_enable.example=true|false +com.arsdigita.cms.image_cache.prefetch_enable.format=[boolean] + com.arsdigita.cms.image_cache.max_size.title=Image cache max elements com.arsdigita.cms.image_cache.max_size.purpose=Max count of items in the image cache, keep this low to prevent filling your memory with unused images com.arsdigita.cms.image_cache.max_size.example=100 diff --git a/ccm-cms/src/com/arsdigita/cms/CachedImage.java b/ccm-cms/src/com/arsdigita/cms/CachedImage.java index e40c6582d..e5fc954e6 100644 --- a/ccm-cms/src/com/arsdigita/cms/CachedImage.java +++ b/ccm-cms/src/com/arsdigita/cms/CachedImage.java @@ -11,15 +11,16 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.math.BigDecimal; import javax.imageio.ImageIO; import org.apache.log4j.Logger; import org.imgscalr.Scalr; -import org.imgscalr.Scalr.*; /** + * This is an in-memory copy of an {@link ImageAsset} to be stored in the image + * cache of {@link BaseImage}. Also, this class is able to create server-side + * resized versions of ImageAssets. * * @author Sören Bernstein (quasimodo) */ @@ -34,11 +35,23 @@ public class CachedImage { private BigDecimal height; private static final Logger s_log = Logger.getLogger(CachedImage.class); - public CachedImage(ImageAsset imageAsset, int width, int height) { + /** + * Create a resized version of an ImageAsset for dispatching + * + * @param imageAsset the ImageAsset to save + * @param maxWidth the max width to resize the image to + * @param maxHeight the max height to resize the image to + */ + public CachedImage(ImageAsset imageAsset, int maxWidth, int maxHeight) { this(imageAsset); - this.resizeImage(width, height); + this.resizeImage(maxWidth, maxHeight); } + /** + * Create a original size version of an ImageAsset for dispatching + * + * @param imageAsset The ImageAsset to save + */ public CachedImage(ImageAsset imageAsset) { this.hash = imageAsset.getOID().toString(); @@ -50,11 +63,20 @@ public class CachedImage { this.height = imageAsset.getHeight(); } + /** + * Create a resized version of another CachedImage. This is a convienience + * constructor to handle the maxWidth and maxHeight param in a single + * String. + * + * @param cachedImage the cachedImage to resize + * @param resizeParam the resize paramter as + * "&maxWidth=&maxHeight=" + */ public CachedImage(CachedImage cachedImage, String resizeParam) { this(cachedImage); - int width = 0; - int height = 0; + int maxWidth = 0; + int maxHeight = 0; String[] params = resizeParam.split("&"); for (int i = 0; i < params.length; i++) { @@ -65,23 +87,35 @@ public class CachedImage { String key = params[i].substring(0, params[i].indexOf("=")); String value = params[i].substring(params[i].indexOf("=") + 1); - if (key.equalsIgnoreCase("width")) { - width = Integer.parseInt(value); + if (key.equalsIgnoreCase("maxWidth")) { + maxWidth = Integer.parseInt(value); } - if (key.equalsIgnoreCase("height")) { - height = Integer.parseInt(value); + if (key.equalsIgnoreCase("maxHeight")) { + maxHeight = Integer.parseInt(value); } } - this.resizeImage(width, height); + this.resizeImage(maxWidth, maxHeight); } - public CachedImage(CachedImage cachedImage, int width, int height) { + /** + * Create a resized version aof another CacheImage. + * + * @param cachedImage the CachedImage to resize + * @param maxWidth max width of the image after resizing + * @param height max height of the image after resizing + */ + public CachedImage(CachedImage cachedImage, int maxWidth, int height) { this(cachedImage); - this.resizeImage(width, height); + this.resizeImage(maxWidth, height); } + /** + * This is just for internal use to set all the fields. + * + * @param cachedImage the CacheImage + */ private CachedImage(CachedImage cachedImage) { this.hash = cachedImage.hash; this.name = cachedImage.getName(); @@ -92,61 +126,79 @@ public class CachedImage { this.height = cachedImage.getHeight(); } + /** + * Get filename + * + * @return the filename + */ public String getName() { return this.name; } + /** + * Get actual width of this instance + * + * @return image width + */ public BigDecimal getWidth() { return this.width; } + /** + * Get actual height of this instance + * + * @return image height + */ public BigDecimal getHeight() { return this.height; } + /** + * Get actual size of this instance + * + * @return image size + */ public int getSize() { return this.image.length; } + /** + * Get version (aka live of draft) + */ public String getVersion() { return this.version; } + /** + * Get {@link MimeType} + * + * @return the {@MimeType} + */ public MimeType getMimeType() { return this.mimetype; } + /** + * Get the image data + * + * @return the image data + */ public byte[] getImage() { return this.image; } /** - * Retrieves the Blob content. + * Write the image to an OutputStream * - * @return the Blob content + * @param os OutputStream to be written to + * + * @return number of bytes written + * + * @throws IOException */ -/* - protected byte[] getContent() { - byte[] content = null; - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - try { - ImageIO.write(image, "JPEG", out); - content = out.toByteArray(); - } catch (IOException ioEx) { - s_log.warn("Could not write byte array", ioEx); - } catch (IllegalArgumentException illEx) { - s_log.warn("image is not initialized", illEx); - } finally { - return content; - } - } -*/ public long writeBytes(OutputStream os) throws IOException { - byte[] bytes = this.getImage(); - os.write(bytes); - - return (long) (bytes.length); + os.write(this.getImage()); + return (long) (this.getSize()); } /** @@ -154,8 +206,7 @@ public class CachedImage { * * @param file The file on the server to write to. */ - public void writeToFile(File file) - throws IOException { + public void writeToFile(File file) throws IOException { FileOutputStream fs = new FileOutputStream(file); try { fs.write(this.getImage()); @@ -167,6 +218,16 @@ public class CachedImage { } } + /** + * Method to proportional resize an image into the defined boundaries. + * Either width or height can be 0. + * If height is 0, the image will be fit to width. + * If width is 0, the image will be fit to height. + * If both paramters are 0, this method will not do anything. + * + * @param width max width of the image after resizing + * @param height max height of the image after resizing + */ private void resizeImage(int width, int height) { // No valid resizing imformation @@ -182,7 +243,7 @@ public class CachedImage { } catch (IOException ioEx) { s_log.warn("Could not read image", ioEx); } - + // Resize image with imagescalr if (width > 0 && height > 0) { bufferedImage = Scalr.resize(bufferedImage, Scalr.Method.QUALITY, width, height); @@ -197,9 +258,9 @@ public class CachedImage { // Set Dimensions this.width = new BigDecimal(bufferedImage.getWidth()); this.height = new BigDecimal(bufferedImage.getHeight()); - + this.hash = this.hash + "&width=" + this.width + "&height=" + this.height; - + // Write BufferedImage to byte array ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -217,5 +278,19 @@ public class CachedImage { public int hashCode() { return this.hash.hashCode(); } - + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final CachedImage other = (CachedImage) obj; + if ((this.hash == null) ? (other.hash != null) : !this.hash.equals(other.hash)) { + return false; + } + return true; + } } diff --git a/ccm-cms/src/com/arsdigita/cms/dispatcher/BaseImage.java b/ccm-cms/src/com/arsdigita/cms/dispatcher/BaseImage.java index 1475439b1..631a2d736 100755 --- a/ccm-cms/src/com/arsdigita/cms/dispatcher/BaseImage.java +++ b/ccm-cms/src/com/arsdigita/cms/dispatcher/BaseImage.java @@ -22,8 +22,8 @@ import com.arsdigita.bebop.parameters.BigDecimalParameter; import com.arsdigita.caching.CacheTable; import com.arsdigita.cms.Asset; import com.arsdigita.cms.CMS; -import com.arsdigita.cms.ImageAsset; import com.arsdigita.cms.CachedImage; +import com.arsdigita.cms.ImageAsset; import com.arsdigita.dispatcher.DispatcherHelper; import com.arsdigita.dispatcher.RequestContext; import com.arsdigita.domain.DataObjectNotFoundException; @@ -31,21 +31,24 @@ import com.arsdigita.domain.DomainObjectFactory; import com.arsdigita.mimetypes.MimeType; import com.arsdigita.persistence.OID; import com.arsdigita.toolbox.ui.OIDParameter; - import java.io.IOException; import java.io.OutputStream; import java.math.BigDecimal; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - import org.apache.log4j.Logger; /** * A resource handler which streams out a blob from the database. + * This class can use a special image cache to speed up image dispatching. Also, + * during dispatch this class will create server-side resized images depending + * on the URL parameter. Resizing is done by ImageScalr. The image cahce can be + * activated and configured by com.arsdigita.cms.image_cache.* parameters. * * @author Stanislav Freidin (sfreidin@arsdigita.com) * @author Michael Pih (pihman@arsdigita.com) + * @author Sören Bernstein * @version $Revision: #20 $ $DateTime: 2004/08/17 23:15:09 $ * @version $Id: BaseImage.java 1571 2007-04-20 15:57:54Z apevec $ */ @@ -62,12 +65,13 @@ public class BaseImage extends ResourceHandlerImpl { private static CacheTable s_imageCache = null; static { - if (CMS.getConfig().getImageCacheEnable()) { + if (CMS.getConfig().getImageCacheEnabled()) { s_imageCache = new CacheTable("BaseImageCache", CMS.getConfig().getImageCacheMaxAge(), CMS.getConfig().getImageCacheMaxSize()); } } + private final bool IMAGE_CACHE_PREFETCH = CMS.getConfig().getImageCachePrefetchEnabled(); private static final Logger s_log = Logger.getLogger(BaseImage.class); /** @@ -142,9 +146,9 @@ public class BaseImage extends ResourceHandlerImpl { /** * Streams an image from the database. * - * @param request The servlet request object + * @param request The servlet request object * @param response the servlet response object - * @param actx The request context + * @param actx The request context */ @Override public void dispatch(HttpServletRequest request, @@ -158,8 +162,8 @@ public class BaseImage extends ResourceHandlerImpl { String resizeParam = ""; // Get URL parameters - String widthParam = request.getParameter("width"); - String heightParam = request.getParameter("height"); + String maxWidthParam = request.getParameter("maxWidth"); + String maxHeightParam = request.getParameter("maxHeight"); // Need the OID, but can work with imageId try { @@ -185,114 +189,56 @@ public class BaseImage extends ResourceHandlerImpl { // Finally, we have a valid OID // Process URL parameter - if (widthParam != null && heightParam != null) { + if (maxWidthParam != null && maxHeightParam != null) { try { - // Set width - if (!widthParam.isEmpty() && widthParam.matches("^[0-9]*$")) { - resizeParam += "&width=" + widthParam; + // Set width if supplied by URL parameter + if (!maxWidthParam.isEmpty() && maxWidthParam.matches("^[0-9]*$")) { + resizeParam += "&maxWidth=" + maxWidthParam; } } catch (NumberFormatException numberEx) { - s_log.warn("width parameter invalid " + widthParam); + s_log.warn("maxWidth parameter invalid " + maxWidthParam); } try { - // Set height - if (!heightParam.isEmpty() && heightParam.matches("^[0-9]*$")) { - resizeParam += "&height=" + heightParam; + // Set height if supplied by URL parameter + if (!maxHeightParam.isEmpty() && maxHeightParam.matches("^[0-9]*$")) { + resizeParam += "&maxHeight=" + maxHeightParam; } } catch (NumberFormatException numberEx) { - s_log.warn("height parameter invalid " + heightParam); + s_log.warn("maxHeight parameter invalid " + maxHeightParam); } } // Now, we have all information we need to proceed - if (!resizeParam.isEmpty()) { - - // Try to get the CachedImage with the OID from the imageCache - cachedImage = (CachedImage) s_imageCache.get(oid.toString() + resizeParam); - - // If cachedImage is still null, the resized version of this oid is - // not in the cache. So, we try to find the original version to - // avoid unnesseccary database access - if (cachedImage == null) { - - // Get the original version - cachedImage = (CachedImage) s_imageCache.get(oid.toString()); - - // If cachedImage is still null, it is not in the imageCache - if (cachedImage == null) { - - // Get it from the database - cachedImage = this.getImageAssetFromDB(response, oid); - - // If cachedImage is still null, we can't find the oid in the DB either - // There is something broken. Bail out. - if (cachedImage == null) { - return; - } - - // Put the CachedImage into the imageCache - s_imageCache.put(oid.toString(), cachedImage); - } - - // Create a resized version of the cachedImage - cachedImage = new CachedImage(cachedImage, resizeParam); - - // Put the CacheImageAsset into the imageCache - s_imageCache.put(oid.toString() + resizeParam, cachedImage); - } - - } else { - - // Try to get the CachedImage with the OID from the imageCache - cachedImage = (CachedImage) (s_imageCache.get(oid.toString())); - - // If cachedImage is still null, it is not in the imageCache - if (cachedImage == null) { - - // Get it from the database - cachedImage = this.getImageAssetFromDB(response, oid); - - // If cachedImage is still null, we can't find the oid in the DB either - // There is something broken. Bail out. - if (cachedImage == null) { - return; - } - } - - // Put the CacheImageAsset into the imageCache - s_imageCache.put(oid.toString(), cachedImage); + // Get the image + cachedImage = this.getImage(response, oid, resizeParam); + if (cachedImage == null) { + // ok, something is really weird now. Can't find image with this oid. Bailing out. + return; } setHeaders(response, cachedImage); send(response, cachedImage); } - private CachedImage getCachedImage(HttpServletResponse response, OID oid, String resizeParam) throws IOException { + private CachedImage getImage(HttpServletResponse response, OID oid, String resizeParam) throws IOException { CachedImage cachedImage = null; + // Test for cache if (s_imageCache != null) { - cachedImage = (CachedImage) s_imageCache.get(oid.toString() + resizeParam); - if (cachedImage == null) { + // Image cache is enabled, try to fetch images from cache + cachedImage = getImageFromCache(response, oid, resizeParam); - if (!resizeParam.isEmpty()) { - cachedImage = (CachedImage) s_imageCache.get(oid.toString()); - - // Create a resized version of the cachedImage - cachedImage = new CachedImage(cachedImage, resizeParam); - - // Put the CacheImageAsset into the imageCache - s_imageCache.put(oid.toString() + resizeParam, cachedImage); - } - } } else { - cachedImage = getImageAssetFromDB(response, oid); + // Image cache is disabled + // Get the original image from db + cachedImage = getImageFromDB(response, oid); if (cachedImage != null && !resizeParam.isEmpty()) { cachedImage = new CachedImage(cachedImage, resizeParam); } @@ -301,7 +247,61 @@ public class BaseImage extends ResourceHandlerImpl { return cachedImage; } - private CachedImage getImageAssetFromDB(HttpServletResponse response, OID oid) throws IOException { + /** + * Fetches the {@link CachedImage} from the image cache. If tge object + * could not be found in the cache, this method falls back to + * {@link #getImageFromDB(javax.servlet.http.HttpServletResponse, com.arsdigita.persistence.OID)}. + * This method will also store the CachedImage in the image cache for future + * use. + * + * @param response The HttpServletResponse + * @param oid the {@link OID} of the wanted object + * @param resizeParam the resize paramters of the wanted object + * @return the wanted {@link CachedImage} in the correct size or null, if the object could not be found + * @throws IOException + */ + private CachedImage getImageFromCache(HttpServletResponse response, OID oid, String resizeParam) throws IOException { + CachedImage cachedImage; + + cachedImage = (CachedImage) s_imageCache.get(oid.toString() + resizeParam); + + // If we coundn't find the specific version + if (cachedImage == null) { + + // If we were looking for a resized version + if (!resizeParam.isEmpty()) { + + // try to find the original version in the cache by recursion + cachedImage = this.getImageFromCache(response, oid, ""); + + if (cachedImage != null) { + cachedImage = new CachedImage(cachedImage, resizeParam); + s_imageCache.put(oid.toString() + resizeParam, cachedImage); + } + } else { + + // look for the original version in the database + cachedImage = getImageFromDB(response, oid); + + // If we found the image, put it into the image cache + if (cachedImage != null && IMAGE_CACHE_PREFETCH) { + s_imageCache.put(oid.toString(), cachedImage); + } + } + } + return cachedImage; + } + + /** + * Fetches the {@link ImageAsset} with the supplied {@link OID} from the database + * and converts it to a {@CachedImage}. + * + * @param response the HttpServletResponse + * @param oid the {@link OID} to the ImageAsset + * @return the ImageAsset with the oid as CachedImage or null, if not found + * @throws IOException + */ + private CachedImage getImageFromDB(HttpServletResponse response, OID oid) throws IOException { ImageAsset imageAsset = null; @@ -311,6 +311,7 @@ public class BaseImage extends ResourceHandlerImpl { try { Asset a = (Asset) DomainObjectFactory.newInstance(oid); + // Make sure we have an ImageAsset if (a instanceof ImageAsset) { imageAsset = (ImageAsset) a; } else { @@ -319,8 +320,7 @@ public class BaseImage extends ResourceHandlerImpl { } } } catch (DataObjectNotFoundException nfe) { - response.sendError(HttpServletResponse.SC_NOT_FOUND, - "no ImageAsset with oid " + oid); + response.sendError(HttpServletResponse.SC_NOT_FOUND, "no ImageAsset with oid " + oid); return null; } diff --git a/ccm-cms/src/com/arsdigita/cms/ui/ImageBrowser.java b/ccm-cms/src/com/arsdigita/cms/ui/ImageBrowser.java index e895fb723..01b28c61d 100755 --- a/ccm-cms/src/com/arsdigita/cms/ui/ImageBrowser.java +++ b/ccm-cms/src/com/arsdigita/cms/ui/ImageBrowser.java @@ -83,7 +83,7 @@ public class ImageBrowser extends Table { * Construct a new ImageBrowser with default mode. * * @param builder the {@link ImageBrowserModelBuilder} that will supply this - * component with its {@link ImageBrowserModel} during each request + * component with its {@link ImageBrowserModel} during each request */ public ImageBrowser(ImageBrowserModelBuilder b) { @@ -94,14 +94,14 @@ public class ImageBrowser extends Table { * Construct a new ImageBrowser with requested mode. * * @param builder the {@link ImageBrowserModelBuilder} that will supply this - * component with its {@link ImageBrowserModel} during each request - * @param mode the component mode (see {@link ImageComponent}) + * component with its {@link ImageBrowserModel} during each request + * @param mode the component mode (see {@link ImageComponent}) */ public ImageBrowser(ImageBrowserModelBuilder b, int mode) { super(new BuilderAdapter(b), HEADERS); m_mode = mode; setThumbnailSize(CMS.getConfig().getImageBrowserThumbnailMaxWidth(), - CMS.getConfig().getImageBrowserThumbnailMaxHeight()); + CMS.getConfig().getImageBrowserThumbnailMaxHeight()); m_builder = b; getHeader().setDefaultRenderer(new DefaultTableCellRenderer(false)); @@ -123,9 +123,9 @@ public class ImageBrowser extends Table { } public int getNumColumns() { - return m_numColumns; + return m_numColumns; } - + /** * @return the size, in pixels, of the thumbnail images */ @@ -151,6 +151,7 @@ public class ImageBrowser extends Table { /** * @param state The current page state + * * @return the {@link ImageBrowserModel} used in the current request */ public ImageBrowserModel getImageBrowserModel(PageState state) { @@ -189,7 +190,8 @@ public class ImageBrowser extends Table { ImageAsset a = (ImageAsset) value; String url = Service.getImageURL(a); - String resizeParam = "&width=" + new Double(m_thumbSize.getWidth()).intValue() + "&height=" + new Double(m_thumbSize.getHeight()).intValue(); + // Sets url paramter to resize the images server-side + String resizeParam = "&maxWidth=" + new Double(m_thumbSize.getWidth()).intValue() + "&maxHeight=" + new Double(m_thumbSize.getHeight()).intValue(); Image img = new Image(URL.getDispatcherPath() + url + resizeParam, a.getName()); img.setBorder("0"); @@ -249,7 +251,7 @@ public class ImageBrowser extends Table { } } - + // can delete image because it's not in use if (canDelete) { return super.getComponent(table, state, value, isSelected, key, row, column); @@ -293,7 +295,7 @@ public class ImageBrowser extends Table { @Override public int getColumnCount() { - return ((ImageBrowser)m_model).getNumColumns(); + return ((ImageBrowser) m_model).getNumColumns(); // return ImageBrowser.s_numColumns; } @@ -343,7 +345,7 @@ public class ImageBrowser extends Table { return m.getMimeType(); case ImageBrowser.LINK: - return "select"; + return "select"; case ImageBrowser.DELETE: return "delete";