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