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.api.core;
020
021import org.apache.logging.log4j.LogManager;
022import org.apache.wiki.api.engine.EngineLifecycleExtension;
023import org.apache.wiki.api.events.CustomWikiEventListener;
024import org.apache.wiki.api.exceptions.ProviderException;
025import org.apache.wiki.api.exceptions.WikiException;
026import org.apache.wiki.event.WikiEventListener;
027import org.apache.wiki.event.WikiEventManager;
028import org.apache.wiki.util.TextUtil;
029
030import javax.servlet.ServletContext;
031import java.io.File;
032import java.io.FileNotFoundException;
033import java.io.IOException;
034import java.io.InputStream;
035import java.io.OutputStream;
036import java.net.MalformedURLException;
037import java.net.URL;
038import java.nio.charset.Charset;
039import java.nio.file.Files;
040import java.util.Collection;
041import java.util.Date;
042import java.util.List;
043import java.util.Properties;
044import java.util.ServiceLoader;
045
046
047/**
048 *  Provides Wiki services to the JSP page.
049 *
050 *  <P>
051 *  This is the main interface through which everything should go.
052 *
053 *  <p>
054 *  There's basically only a single Engine for each web application, and you should always get it using either the
055 *  {@code Context#getEngine()} method or through {@code Wiki.engine().find(..)} DSL methods.
056 */
057public interface Engine {
058
059    /** The default inlining pattern.  Currently "*.png" */
060    String DEFAULT_INLINEPATTERN = "*.png";
061
062    /** The name used for the default template. The value is {@value}. */
063    String DEFAULT_TEMPLATE_NAME = "default";
064
065    /** Property for application name */
066    String PROP_APPNAME = "jspwiki.applicationName";
067
068    /** This property defines the inline image pattern.  It's current value is {@value} */
069    String PROP_INLINEIMAGEPTRN = "jspwiki.translatorReader.inlinePattern";
070
071    /** Property start for any interwiki reference. */
072    String PROP_INTERWIKIREF = "jspwiki.interWikiRef.";
073
074    /** The property name defining which packages will be searched for plugin classes. */
075    String PROP_SEARCHPATH = "jspwiki.plugin.searchPath";
076
077    /** If true, then the user name will be stored with the page data.*/
078    String PROP_STOREUSERNAME= "jspwiki.storeUserName";
079
080    /** Define the used encoding.  Currently supported are ISO-8859-1 and UTF-8 */
081    String PROP_ENCODING = "jspwiki.encoding";
082
083    /** Do not use encoding in WikiJSPFilter, default is false for most servers.
084     Double negative, cause for most servers you don't need the property */
085    String PROP_NO_FILTER_ENCODING = "jspwiki.nofilterencoding";
086
087    /** Property name for where the jspwiki work directory should be.
088     If not specified, reverts to ${java.tmpdir}. */
089    String PROP_WORKDIR = "jspwiki.workDir";
090
091    /** The name of the cookie that gets stored to the user browser. */
092    String PREFS_COOKIE_NAME = "JSPWikiUserProfile";
093
094    /** Property name for the "match english plurals" -hack. */
095    String PROP_MATCHPLURALS = "jspwiki.translatorReader.matchEnglishPlurals";
096
097    /** Property name for the template that is used. */
098    String PROP_TEMPLATEDIR = "jspwiki.templateDir";
099
100    /** Property name for the default front page. */
101    String PROP_FRONTPAGE = "jspwiki.frontPage";
102
103    /** Property name for setting the url generator instance */
104    String PROP_URLCONSTRUCTOR = "jspwiki.urlConstructor";
105
106    /** The name of the property containing the ACLManager implementing class. The value is {@value}. */
107    String PROP_ACL_MANAGER_IMPL = "jspwiki.aclManager";
108
109    /** The name of the property containing the ReferenceManager implementing class. The value is {@value}. */
110    String PROP_REF_MANAGER_IMPL = "jspwiki.refManager";
111
112    /** If this property is set to false, we don't allow the creation of empty pages */
113    String PROP_ALLOW_CREATION_OF_EMPTY_PAGES = "jspwiki.allowCreationOfEmptyPages";
114
115    /**
116     * Adapt Engine to a concrete type.
117     *
118     * @param cls class denoting the type to adapt to.
119     * @param <E> type to adapt to.
120     * @return engine instance adapted to the requested type. Might throw an unchecked exception if the instance cannot be adapted to requested type!
121     */
122    @SuppressWarnings( "unchecked" )
123    default < E extends Engine > E adapt( final Class< E > cls ) {
124        return ( E )this;
125    }
126
127    /**
128     * Retrieves the object instantiated by the Engine matching the requested type.
129     *
130     * @param manager requested object instantiated by the Engine.
131     * @param <T> type of the requested object.
132     * @return requested object instantiated by the Engine, {@code null} if not available.
133     */
134    < T > T getManager( Class< T > manager );
135
136    /**
137     * Retrieves the objects instantiated by the Engine that can be assigned to the requested type.
138     *
139     * @param manager requested objectx instantiated by the Engine.
140     * @param <T> type of the requested object.
141     * @return collection of requested objects instantiated by the Engine, {@code empty} list if none available.
142     */
143    < T > List< T > getManagers( Class< T > manager );
144
145    /**
146     * check if the Engine has been configured.
147     *
148     * @return {@code true} if it has, {@code false} otherwise.
149     */
150    boolean isConfigured();
151
152    /**
153     *  Returns the set of properties that the Engine was initialized with.  Note that this method returns a direct reference, so it's
154     *  possible to manipulate the properties.  However, this is not advised unless you really know what you're doing.
155     *
156     *  @return The wiki properties
157     */
158    Properties getWikiProperties();
159
160    /**
161     *  Returns the JSPWiki working directory set with "jspwiki.workDir".
162     *
163     *  @since 2.1.100
164     *  @return The working directory.
165     */
166    String getWorkDir();
167
168    /**
169     *  Returns the current template directory.
170     *
171     *  @since 1.9.20
172     *  @return The template directory as initialized by the engine.
173     */
174    String getTemplateDir();
175
176    /**
177     * Returns plugins' search path.
178     *
179     * @return plugins' search path.
180     */
181    default String getPluginSearchPath() {
182        return TextUtil.getStringProperty( getWikiProperties(), PROP_SEARCHPATH, null );
183    }
184
185    /**
186     *  Returns the moment when this engine was started.
187     *
188     *  @since 2.0.15.
189     *  @return The start time of this wiki.
190     */
191    Date getStartTime();
192
193    /**
194     *  Returns the base URL, telling where this Wiki actually lives.
195     *
196     *  @since 1.6.1
197     *  @return The Base URL.
198     */
199    String getBaseURL();
200
201    /**
202     *  Returns the URL of the global RSS file.  May be null, if the RSS file generation is not operational.
203     *
204     *  @since 1.7.10
205     *  @return The global RSS url
206     */
207    String getGlobalRSSURL();
208
209    /**
210     *  Returns an URL to some other Wiki that we know.
211     *
212     *  @param  wikiName The name of the other wiki.
213     *  @return null, if no such reference was found.
214     */
215    String getInterWikiURL( String wikiName );
216
217    /**
218     *  Returns an URL if a WikiContext is not available.
219     *
220     *  @param context The WikiContext (VIEW, EDIT, etc...)
221     *  @param pageName Name of the page, as usual
222     *  @param params List of parameters. May be null, if no parameters.
223     *  @return An URL (absolute or relative).
224     */
225    String getURL( String context, String pageName, String params );
226
227    /**
228     *  Returns the default front page, if no page is used.
229     *
230     *  @return The front page name.
231     */
232    String getFrontPage();
233
234    /**
235     *  Returns the ServletContext that this particular Engine was initialized with. <strong>It may return {@code null}</strong>,
236     *  if the Engine is not running inside a servlet container!
237     *
238     *  @since 1.7.10
239     *  @return ServletContext of the Engine, or {@code null}.
240     */
241    ServletContext getServletContext();
242
243    /**
244     * Looks up and obtains a configuration file inside the WEB-INF folder of a wiki webapp.
245     *
246     * @param name the file to obtain, <em>e.g.</em>, <code>jspwiki.policy</code>
247     * @return the URL to the file
248     */
249    default URL findConfigFile( final String name ) {
250        LogManager.getLogger( Engine.class ).info( "looking for " + name + " inside WEB-INF " );
251        // Try creating an absolute path first
252        File defaultFile = null;
253        if( getRootPath() != null ) {
254            defaultFile = new File( getRootPath() + "/WEB-INF/" + name );
255        }
256        if ( defaultFile != null && defaultFile.exists() ) {
257            try {
258                return defaultFile.toURI().toURL();
259            } catch ( final MalformedURLException e ) {
260                // Shouldn't happen, but log it if it does
261                LogManager.getLogger( Engine.class ).warn( "Malformed URL: " + e.getMessage() );
262            }
263        }
264
265        // Ok, the absolute path didn't work; try other methods
266        URL path = null;
267
268        if( getServletContext() != null ) {
269            final File tmpFile;
270            try {
271                tmpFile = File.createTempFile( "temp." + name, "" );
272            } catch( final IOException e ) {
273                LogManager.getLogger( Engine.class ).error( "unable to create a temp file to load onto the policy", e );
274                return null;
275            }
276            tmpFile.deleteOnExit();
277            LogManager.getLogger( Engine.class ).info( "looking for /" + name + " on classpath" );
278            //  create a tmp file of the policy loaded as an InputStream and return the URL to it
279            try( final InputStream is = Engine.class.getResourceAsStream( "/" + name );
280                final OutputStream os = Files.newOutputStream( tmpFile.toPath() ) ) {
281                if( is == null ) {
282                    throw new FileNotFoundException( name + " not found" );
283                }
284                final URL url = getServletContext().getResource( "/WEB-INF/" + name );
285                if( url != null ) {
286                    return url;
287                }
288
289                final byte[] buff = new byte[1024];
290                int bytes;
291                while( ( bytes = is.read( buff ) ) != -1 ) {
292                    os.write( buff, 0, bytes );
293                }
294
295                path = tmpFile.toURI().toURL();
296            } catch( final MalformedURLException e ) {
297                // This should never happen unless I screw up
298                LogManager.getLogger( Engine.class ).fatal( "Your code is b0rked.  You are a bad person.", e );
299            } catch( final IOException e ) {
300                LogManager.getLogger( Engine.class ).error( "failed to load security policy from file " + name + ",stacktrace follows", e );
301            }
302        }
303        return path;
304    }
305
306    /**
307     *  Returns a collection of all supported InterWiki links.
308     *
309     *  @return A Collection of Strings.
310     */
311    Collection< String > getAllInterWikiLinks();
312
313    /**
314     *  Returns a collection of all image types that get inlined.
315     *
316     *  @return A Collection of Strings with a regexp pattern.
317     */
318    Collection< String > getAllInlinedImagePatterns();
319
320    /**
321     *  <p>If the page is a special page, then returns a direct URL to that page. Otherwise returns <code>null</code>.
322     *  This method delegates requests to {@link org.apache.wiki.ui.CommandResolver#getSpecialPageReference(String)}.</p>
323     *  <p>Special pages are defined in jspwiki.properties using the jspwiki.specialPage setting. They're typically used to give Wiki page
324     *  names to e.g. custom JSP pages.</p>
325     *
326     *  @param original The page to check
327     *  @return A reference to the page, or null, if there's no special page.
328     */
329    String getSpecialPageReference( String original );
330
331    /**
332     *  Returns the name of the application.
333     *
334     *  @return A string describing the name of this application.
335     */
336    String getApplicationName();
337
338    /**
339     *  Returns the root path.  The root path is where the Engine is located in the file system.
340     *
341     *  @since 2.2
342     *  @return A path to where the Wiki is installed in the local filesystem.
343     */
344    String getRootPath();
345
346    /**
347     *  Returns the correct page name, or null, if no such page can be found.  Aliases are considered. This method simply delegates to
348     *  {@link org.apache.wiki.ui.CommandResolver#getFinalPageName(String)}.
349     *
350     *  @since 2.0
351     *  @param page Page name.
352     *  @return The rewritten page name, or null, if the page does not exist.
353     *  @throws ProviderException If something goes wrong in the backend.
354     */
355    String getFinalPageName( String page ) throws ProviderException;
356
357    /**
358     *  Turns a WikiName into something that can be called through using an URL.
359     *
360     *  @since 1.4.1
361     *  @param pagename A name. Can be actually any string.
362     *  @return A properly encoded name.
363     *  @see #decodeName(String)
364     */
365    String encodeName( String pagename );
366
367    /**
368     *  Decodes a URL-encoded request back to regular life.  This properly heeds the encoding as defined in the settings file.
369     *
370     *  @param pagerequest The URL-encoded string to decode
371     *  @return A decoded string.
372     *  @see #encodeName(String)
373     */
374    String decodeName( String pagerequest );
375
376    /**
377     *  Returns the IANA name of the character set encoding we're supposed to be using right now.
378     *
379     *  @since 1.5.3
380     *  @return The content encoding (either UTF-8 or ISO-8859-1).
381     */
382    Charset getContentEncoding();
383
384    /**
385     * Registers a WikiEventListener with this instance.
386     *
387     * @param listener the event listener
388     */
389    void addWikiEventListener( WikiEventListener listener );
390
391    /**
392     * Un-registers a WikiEventListener with this instance.
393     *
394     * @param listener the event listener
395     */
396    void removeWikiEventListener( WikiEventListener listener );
397
398    /**
399     * Adds an attribute to the engine for the duration of this engine.  The value is not persisted.
400     *
401     * @since 2.4.91
402     * @param key the attribute name
403     * @param value the value
404     */
405    void setAttribute( String key, Object value );
406
407    /**
408     *  Gets an attribute from the engine.
409     *
410     *  @param key the attribute name
411     *  @return the value
412     */
413    < T > T getAttribute( String key );
414
415    /**
416     *  Removes an attribute.
417     *
418     *  @param key The key of the attribute to remove.
419     *  @return The previous attribute, if it existed.
420     */
421    < T > T removeAttribute( String key );
422
423    /**
424     * Initializes the {@code Engine}, notifying all the {@link EngineLifecycleExtension}s.
425     *
426     * @param properties Wiki configuration properties.
427     * @throws WikiException if something happens while setting up the {@code Engine}.
428     */
429    default void start( final Properties properties ) throws WikiException {
430        final var loader = ServiceLoader.load( EngineLifecycleExtension.class );
431        for( final var extension : loader ) {
432            extension.onInit( properties );
433        }
434        initialize( properties );
435        for( final var extension : loader ) {
436            extension.onStart( this, properties );
437        }
438        final var events = ServiceLoader.load( CustomWikiEventListener.class );
439        for( final var event : events ) {
440            CustomWikiEventListener.LISTENERS.add( event );
441            event.initialize( this, getWikiProperties() );
442            WikiEventManager.addWikiEventListener( event.client(), event );
443        }
444    }
445
446    /**
447     * Shuts down the {@code Engine}, notifying all the {@link EngineLifecycleExtension}s.
448     */
449    default void stop() {
450        final ServiceLoader< EngineLifecycleExtension > loader = ServiceLoader.load( EngineLifecycleExtension.class );
451        for( final EngineLifecycleExtension extension : loader ) {
452            extension.onShutdown( this, getWikiProperties() );
453        }
454        shutdown();
455    }
456
457    /**
458     * Sets up the application's running {@code Engine}.
459     *
460     * @param properties Wiki configuration properties.
461     * @throws WikiException if something happens while setting up the {@code Engine}.
462     */
463    void initialize( Properties properties ) throws WikiException;
464
465    /**
466     * Signals that the {@code Engine} will be shut down by the servlet container.
467     */
468    void shutdown();
469
470}