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