001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.attachment; 020 021import net.sf.ehcache.Cache; 022import net.sf.ehcache.CacheManager; 023import net.sf.ehcache.Element; 024import org.apache.log4j.Logger; 025import org.apache.wiki.api.core.Attachment; 026import org.apache.wiki.api.core.Context; 027import org.apache.wiki.api.core.Engine; 028import org.apache.wiki.api.core.Page; 029import org.apache.wiki.api.exceptions.NoRequiredPropertyException; 030import org.apache.wiki.api.exceptions.ProviderException; 031import org.apache.wiki.api.providers.AttachmentProvider; 032import org.apache.wiki.api.spi.Wiki; 033import org.apache.wiki.pages.PageManager; 034import org.apache.wiki.parser.MarkupParser; 035import org.apache.wiki.references.ReferenceManager; 036import org.apache.wiki.search.SearchManager; 037import org.apache.wiki.util.ClassUtil; 038import org.apache.wiki.util.TextUtil; 039 040import java.io.IOException; 041import java.io.InputStream; 042import java.util.ArrayList; 043import java.util.Collection; 044import java.util.Comparator; 045import java.util.Date; 046import java.util.List; 047import java.util.Properties; 048 049 050/** 051 * Default implementation for {@link AttachmentManager} 052 * 053 * {@inheritDoc} 054 * 055 * @since 1.9.28 056 */ 057public class DefaultAttachmentManager implements AttachmentManager { 058 059 /** List of attachment types which are forced to be downloaded */ 060 private String[] m_forceDownloadPatterns; 061 062 private static final Logger log = Logger.getLogger( DefaultAttachmentManager.class ); 063 private AttachmentProvider m_provider; 064 private Engine m_engine; 065 private CacheManager m_cacheManager = CacheManager.getInstance(); 066 private Cache m_dynamicAttachments; 067 068 /** 069 * Creates a new AttachmentManager. Note that creation will never fail, but it's quite likely that attachments do not function. 070 * <p><b>DO NOT CREATE</b> an AttachmentManager on your own, unless you really know what you're doing. Just use 071 * Wikiengine.getManager( AttachmentManager.class ) if you're making a module for JSPWiki. 072 * 073 * @param engine The wikiengine that owns this attachment manager. 074 * @param props A list of properties from which the AttachmentManager will seek its configuration. Typically this is the "jspwiki.properties". 075 */ 076 // FIXME: Perhaps this should fail somehow. 077 public DefaultAttachmentManager( final Engine engine, final Properties props ) { 078 m_engine = engine; 079 080 // If user wants to use a cache, then we'll use the CachingProvider. 081 final boolean useCache = "true".equals( props.getProperty( PageManager.PROP_USECACHE ) ); 082 083 final String classname; 084 if( useCache ) { 085 classname = "org.apache.wiki.providers.CachingAttachmentProvider"; 086 } else { 087 classname = props.getProperty( PROP_PROVIDER ); 088 } 089 090 // If no class defined, then will just simply fail. 091 if( classname == null ) { 092 log.info( "No attachment provider defined - disabling attachment support." ); 093 return; 094 } 095 096 // Create and initialize the provider. 097 final String cacheName = engine.getApplicationName() + "." + CACHE_NAME; 098 try { 099 if( m_cacheManager.cacheExists( cacheName ) ) { 100 m_dynamicAttachments = m_cacheManager.getCache( cacheName ); 101 } else { 102 log.info( "cache with name " + cacheName + " not found in ehcache.xml, creating it with defaults." ); 103 m_dynamicAttachments = new Cache( cacheName, DEFAULT_CACHECAPACITY, false, false, 0, 0 ); 104 m_cacheManager.addCache( m_dynamicAttachments ); 105 } 106 107 final Class< ? > providerclass = ClassUtil.findClass( "org.apache.wiki.providers", classname ); 108 109 m_provider = ( AttachmentProvider )providerclass.newInstance(); 110 m_provider.initialize( m_engine, props ); 111 } catch( final ClassNotFoundException e ) { 112 log.error( "Attachment provider class not found",e); 113 } catch( final InstantiationException e ) { 114 log.error( "Attachment provider could not be created", e ); 115 } catch( final IllegalAccessException e ) { 116 log.error( "You may not access the attachment provider class", e ); 117 } catch( final NoRequiredPropertyException e ) { 118 log.error( "Attachment provider did not find a property that it needed: " + e.getMessage(), e ); 119 m_provider = null; // No, it did not work. 120 } catch( final IOException e ) { 121 log.error( "Attachment provider reports IO error", e ); 122 m_provider = null; 123 } 124 125 final String forceDownload = TextUtil.getStringProperty( props, PROP_FORCEDOWNLOAD, null ); 126 if( forceDownload != null && forceDownload.length() > 0 ) { 127 m_forceDownloadPatterns = forceDownload.toLowerCase().split( "\\s" ); 128 } else { 129 m_forceDownloadPatterns = new String[ 0 ]; 130 } 131 } 132 133 /** {@inheritDoc} */ 134 @Override 135 public boolean attachmentsEnabled() { 136 return m_provider != null; 137 } 138 139 /** {@inheritDoc} */ 140 @Override 141 public String getAttachmentInfoName( final Context context, final String attachmentname ) { 142 final Attachment att; 143 try { 144 att = getAttachmentInfo( context, attachmentname ); 145 } catch( final ProviderException e ) { 146 log.warn( "Finding attachments failed: ", e ); 147 return null; 148 } 149 150 if( att != null ) { 151 return att.getName(); 152 } else if( attachmentname.indexOf( '/' ) != -1 ) { 153 return attachmentname; 154 } 155 156 return null; 157 } 158 159 /** {@inheritDoc} */ 160 @Override 161 public Attachment getAttachmentInfo( final Context context, String attachmentname, final int version ) throws ProviderException { 162 if( m_provider == null ) { 163 return null; 164 } 165 166 Page currentPage = null; 167 168 if( context != null ) { 169 currentPage = context.getPage(); 170 } 171 172 // Figure out the parent page of this attachment. If we can't find it, we'll assume this refers directly to the attachment. 173 final int cutpt = attachmentname.lastIndexOf( '/' ); 174 if( cutpt != -1 ) { 175 String parentPage = attachmentname.substring( 0, cutpt ); 176 parentPage = MarkupParser.cleanLink( parentPage ); 177 attachmentname = attachmentname.substring( cutpt + 1 ); 178 179 // If we for some reason have an empty parent page name; this can't be an attachment 180 if( parentPage.length() == 0 ) { 181 return null; 182 } 183 184 currentPage = m_engine.getManager( PageManager.class ).getPage( parentPage ); 185 186 // Go check for legacy name 187 // FIXME: This should be resolved using CommandResolver, not this adhoc way. This also assumes that the 188 // legacy charset is a subset of the full allowed set. 189 if( currentPage == null ) { 190 currentPage = m_engine.getManager( PageManager.class ).getPage( MarkupParser.wikifyLink( parentPage ) ); 191 } 192 } 193 194 // If the page cannot be determined, we cannot possibly find the attachments. 195 if( currentPage == null || currentPage.getName().length() == 0 ) { 196 return null; 197 } 198 199 // Finally, figure out whether this is a real attachment or a generated attachment. 200 Attachment att = getDynamicAttachment( currentPage.getName() + "/" + attachmentname ); 201 if( att == null ) { 202 att = m_provider.getAttachmentInfo( currentPage, attachmentname, version ); 203 } 204 205 return att; 206 } 207 208 /** {@inheritDoc} */ 209 @Override 210 public List< Attachment > listAttachments( final Page wikipage ) throws ProviderException { 211 if( m_provider == null ) { 212 return new ArrayList<>(); 213 } 214 215 final List< Attachment > atts = new ArrayList<>( m_provider.listAttachments( wikipage ) ); 216 atts.sort( Comparator.comparing( Attachment::getName, m_engine.getManager( PageManager.class ).getPageSorter() ) ); 217 218 return atts; 219 } 220 221 /** {@inheritDoc} */ 222 @Override 223 public boolean forceDownload( String name ) { 224 if( name == null || name.length() == 0 ) { 225 return false; 226 } 227 228 name = name.toLowerCase(); 229 if( name.indexOf( '.' ) == -1 ) { 230 return true; // force download on attachments without extension or type indication 231 } 232 233 for( final String forceDownloadPattern : m_forceDownloadPatterns ) { 234 if( name.endsWith( forceDownloadPattern ) && forceDownloadPattern.length() > 0 ) { 235 return true; 236 } 237 } 238 239 return false; 240 } 241 242 /** {@inheritDoc} */ 243 @Override 244 public InputStream getAttachmentStream( final Context ctx, final Attachment att ) throws ProviderException, IOException { 245 if( m_provider == null ) { 246 return null; 247 } 248 249 if( att instanceof DynamicAttachment ) { 250 return ( ( DynamicAttachment )att ).getProvider().getAttachmentData( ctx, att ); 251 } 252 253 return m_provider.getAttachmentData( att ); 254 } 255 256 /** {@inheritDoc} */ 257 @Override 258 public void storeDynamicAttachment( final Context ctx, final DynamicAttachment att ) { 259 m_dynamicAttachments.put( new Element( att.getName(), att ) ); 260 } 261 262 /** {@inheritDoc} */ 263 @Override 264 public DynamicAttachment getDynamicAttachment( final String name ) { 265 final Element element = m_dynamicAttachments.get( name ); 266 if( element != null ) { 267 return ( DynamicAttachment )element.getObjectValue(); 268 } else { 269 // Remove from cache, it has expired. 270 m_dynamicAttachments.put( new Element( name, null ) ); 271 return null; 272 } 273 } 274 275 /** {@inheritDoc} */ 276 @Override 277 public void storeAttachment( final Attachment att, final InputStream in ) throws IOException, ProviderException { 278 if( m_provider == null ) { 279 return; 280 } 281 282 // Checks if the actual, real page exists without any modifications or aliases. We cannot store an attachment to a non-existent page. 283 if( !m_engine.getManager( PageManager.class ).pageExists( att.getParentName() ) ) { 284 // the caller should catch the exception and use the exception text as an i18n key 285 throw new ProviderException( "attach.parent.not.exist" ); 286 } 287 288 m_provider.putAttachmentData( att, in ); 289 m_engine.getManager( ReferenceManager.class ).updateReferences( att.getName(), new ArrayList<>() ); 290 291 final Page parent = Wiki.contents().page( m_engine, att.getParentName() ); 292 m_engine.getManager( ReferenceManager.class ).updateReferences( parent ); 293 m_engine.getManager( SearchManager.class ).reindexPage( att ); 294 } 295 296 /** {@inheritDoc} */ 297 @Override 298 public List< Attachment > getVersionHistory( final String attachmentName ) throws ProviderException { 299 if( m_provider == null ) { 300 return null; 301 } 302 303 final Attachment att = getAttachmentInfo( null, attachmentName ); 304 if( att != null ) { 305 return m_provider.getVersionHistory( att ); 306 } 307 308 return null; 309 } 310 311 /** {@inheritDoc} */ 312 @Override 313 public Collection<Attachment> getAllAttachments() throws ProviderException { 314 if( attachmentsEnabled() ) { 315 return m_provider.listAllChanged( new Date( 0L ) ); 316 } 317 318 return new ArrayList<>(); 319 } 320 321 /** {@inheritDoc} */ 322 @Override 323 public AttachmentProvider getCurrentProvider() { 324 return m_provider; 325 } 326 327 /** {@inheritDoc} */ 328 @Override 329 public void deleteVersion( final Attachment att ) throws ProviderException { 330 if( m_provider == null ) { 331 return; 332 } 333 334 m_provider.deleteVersion( att ); 335 } 336 337 /** {@inheritDoc} */ 338 @Override 339 // FIXME: Should also use events! 340 public void deleteAttachment( final Attachment att ) throws ProviderException { 341 if( m_provider == null ) { 342 return; 343 } 344 345 m_provider.deleteAttachment( att ); 346 m_engine.getManager( SearchManager.class ).pageRemoved( att ); 347 m_engine.getManager( ReferenceManager.class ).clearPageEntries( att.getName() ); 348 } 349 350}