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