2626import java .util .Set ;
2727
2828import jakarta .servlet .Filter ;
29- import jakarta .servlet .ServletException ;
29+ import jakarta .servlet .http .HttpSessionEvent ;
30+ import jakarta .servlet .http .HttpSessionListener ;
3031import org .apache .jena .atlas .io .IOX ;
3132import org .apache .jena .atlas .lib .Lib ;
3233import org .apache .jena .atlas .logging .FmtLog ;
34+ import org .apache .jena .atlas .logging .Log ;
3335import org .apache .jena .cmd .ArgDecl ;
3436import org .apache .jena .cmd .CmdException ;
3537import org .apache .jena .cmd .CmdGeneral ;
3638import org .apache .jena .fuseki .Fuseki ;
3739import org .apache .jena .fuseki .FusekiConfigException ;
38- import org .apache .jena .fuseki .FusekiException ;
3940import org .apache .jena .fuseki .main .FusekiServer ;
4041import org .apache .jena .fuseki .main .runner .ServerArgs ;
4142import org .apache .jena .fuseki .main .sys .FusekiModule ;
4243import org .apache .jena .fuseki .mgt .FusekiServerCtl ;
4344import org .apache .jena .rdf .model .Model ;
4445import org .apache .shiro .lang .io .ResourceUtils ;
45- import org .apache .shiro .web .servlet .ShiroFilter ;
4646import org .eclipse .jetty .ee11 .servlet .ServletContextHandler ;
4747import org .eclipse .jetty .ee11 .servlet .SessionHandler ;
4848import org .slf4j .Logger ;
@@ -77,12 +77,25 @@ public static FMod_Shiro create() {
7777 /** Shiro configuration file given a a command line argument. */
7878 private String argShiroFile = null ;
7979
80+ /** Control for whether to have Jetty/Jakarta sessions,
81+ * which are then used by Shiro.
82+ * But they don't get cleared up and keep hold of RAM.
83+ * See <a href="https://github.com/apache/jena/issues/4033">GH-4033</a>.
84+ * <p>
85+ * If {@code true}, then disable Shiro session creation on every request,
86+ * and don't create a Jetty sever session manager.
87+ * <p>
88+ * It the future, it is likely that this flag is dropped
89+ * and the code support only "disabled sessions".
90+ */
91+ private static final boolean DISABLE_SESSIONS = false ;
92+
8093 public FMod_Shiro () {}
8194
8295 // ---- If used from the command line
8396 @ Override
8497 public void serverArgsModify (CmdGeneral fusekiCmd , ServerArgs serverArgs ) {
85- fusekiCmd .add (argShiroIni );
98+ fusekiCmd .add (argShiroIni , "--shiro" , "Set the shiro.ini file" );
8699 argShiroFile = null ;
87100 }
88101
@@ -117,7 +130,7 @@ private String decideShiroConfiguration(FusekiServer.Builder serverBuilder) {
117130 if ( shiroConfig != null )
118131 return shiroConfig ;
119132
120- // Environment variable: FUSEKI_SHIRO
133+ // Environment variable: FUSEKI_SHIRO
121134 shiroConfig = Lib .getenv (FusekiServerCtl .envFusekiShiro );
122135 if ( shiroConfig != null )
123136 return shiroConfig ;
@@ -144,83 +157,73 @@ public void prepare(FusekiServer.Builder serverBuilder, Set<String> datasetNames
144157 if ( shiroConfig == null )
145158 return ;
146159
147- // Shiro prints stack traces and exceptions. Rather than silence
148- //checkShiroConfiguration(shiroConfig);
149-
150160 // Shiro-style.
151161 String shiroResourceName = FusekiShiro .withResourcePrefix (shiroConfig );
152162 if ( ! ResourceUtils .resourceExists (shiroResourceName ) )
153163 throw new FusekiConfigException ("Shiro resource does not exist" );
154- Filter filter = new FusekiShiroFilter (shiroResourceName );
164+
165+ Filter filter = new FusekiShiroFilter (shiroResourceName , DISABLE_SESSIONS );
155166 // This is a "before" filter.
156167 serverBuilder .addFilter ("/*" , filter );
157168 serverBuilder .setServletAttribute (Fuseki .attrShiroResource , shiroResourceName );
158-
159169 // Clear.
160170 this .argShiroFile = null ;
161171 }
162172
163- /**
164- * FusekiShiroFilter, includes Shiro initialization. Fuseki is a
165- * not a webapp so it needs to trigger off servlet initialization.
166- */
167- private static class FusekiShiroFilter extends ShiroFilter {
168-
169- private final String shiroInitializationResource ;
170-
171- FusekiShiroFilter (String shiroResourceName ) {
172- // Shiro file -- URLs are "file:<no encoding>"
173- shiroInitializationResource = shiroResourceName ;
174- }
175-
176- @ Override
177- public void init () throws Exception {
178- try {
179- // Intercept Shiro initialization.
180- List <String > locations = List .of ();
181- if ( shiroInitializationResource != null ) {
182- locations = List .of (shiroInitializationResource );
183- }
184- FusekiShiro .shiroEnvironment (getServletContext (), locations );
185- } catch (FusekiException ex ) {
186- // Wrap so ShiroFilter does not log it.
187- throw new ServletException (ex );
188- }
189- super .init ();
190- }
191- }
192-
193173 @ Override
194174 public void serverBeforeStarting (FusekiServer server ) {
195175 try {
196176 String x =(String )server .getServletContext ().getAttribute (Fuseki .attrShiroResource );
197177 if ( x != null )
198178 FmtLog .info (shiroConfigLog , "Shiro configuration: %s" , x );
199- // else
200- // FmtLog.info(shiroConfigLog, "No Shiro configuration");
179+ // else
180+ // FmtLog.info(shiroConfigLog, "No Shiro configuration");
201181 } catch (ClassCastException ex ) {
202182 FmtLog .warn (shiroConfigLog , "Unexpected Shiro configuration: %s" , server .getServletContext ().getAttribute (Fuseki .attrShiroResource ));
203183 }
204184
205- // Shiro requires a session handler.
206- // This needs the Jetty server to have been created.
207- org .eclipse .jetty .server .Server jettyServer = server .getJettyServer ();
208- try {
209- ServletContextHandler servletContextHandler = (ServletContextHandler )(jettyServer .getHandler ());
210- if ( servletContextHandler .getSessionHandler () == null ) {
211- SessionHandler sessionsHandler = new SessionHandler ();
212- servletContextHandler .setHandler (sessionsHandler );
185+ if ( ! DISABLE_SESSIONS ) {
186+ // If shiro requires a session handler.
187+ // This needs the Jetty server to have been created.
188+ // Allocate a Jakarta SessionHandler
189+ try {
190+ org .eclipse .jetty .server .Server jettyServer = server .getJettyServer ();
191+
192+ ServletContextHandler servletContextHandler = (ServletContextHandler )(jettyServer .getHandler ());
193+ if ( servletContextHandler .getSessionHandler () == null ) {
194+ SessionHandler sessionHandler = new SessionHandler ();
195+ if ( true )
196+ addHttpSessionListener (sessionHandler );
197+ servletContextHandler .setHandler (sessionHandler );
198+ }
199+ } catch (RuntimeException ex ) {
200+ shiroConfigLog .error ("Failed to set a session manager - server aborted" );
201+ throw ex ;
213202 }
214- } catch (RuntimeException ex ) {
215- shiroConfigLog .error ("Failed to set a session manager - server aborted" );
216- throw ex ;
217203 }
218204 }
219205
206+
220207 // Later:
221208 // Reload shirio.ini file and reset.
222209// // Currently, no actual - the server admin area does not move during the run of a server.
223210// /** {@inheritDoc} */
224211// @Override
225212// public void serverReload(FusekiServer server) { }
213+
214+
215+ private static void addHttpSessionListener ( SessionHandler sessionHandler ) {
216+ // Development
217+ HttpSessionListener httpSessionListener = new HttpSessionListener () {
218+ @ Override
219+ public void sessionCreated (HttpSessionEvent se ) {
220+ Log .info (Fuseki .serverLog , "Jakarta session created" );
221+ }
222+ @ Override
223+ public void sessionDestroyed (HttpSessionEvent se ) {
224+ Log .info (Fuseki .serverLog , "Jakarta session destroyed" );
225+ }
226+ };
227+ sessionHandler .addEventListener (httpSessionListener );
228+ }
226229}
0 commit comments