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