+ *
+ * @see ServiceManager
*/
-public class ServiceContext extends BasicContext {
+public class ServiceContext extends BasicContext implements AutoCloseable {
+ /**
+ * ServiceContext is managed as a thread locale using setAsThreadLocal, clearAsThreadLocal and clear methods.
+ * ThreadLocalPolicy defines the behaviour of these methods double checking that they are being used correctly.
+ */
+ public static enum ThreadLocalPolicy {
+ /** Direct management of thread local with no checking. */
+ DIRECT,
+ /** Check behavior and log any unexpected use. */
+ TRACE,
+ /** Raise any {@link IllegalStateException} for unexpected behaviour */
+ STRICT };
+ /**
+ * Use -Djeeves.server.context.service.policy to define policy:
+ *
+ *
direct: direct management of thread local with no checking
+ *
trace: check behavior and log unusal use
+ *
strict: raise illegal state exception for unusual behavior
+ *
+ */
+ private static final ThreadLocalPolicy POLICY;
+ static {
+ String property = System.getProperty("jeeves.server.context.policy", "TRACE");
+ ThreadLocalPolicy policy;
+ try {
+ policy = ThreadLocalPolicy.valueOf(property.toUpperCase());
+ }
+ catch (IllegalArgumentException defaultToDirect) {
+ policy = ThreadLocalPolicy.DIRECT;
+ }
+ POLICY = policy;
+ }
+
+ /**
+ * Be careful with thread local to avoid leaking resources, set POLICY above to trace allocation.
+ */
private static final InheritableThreadLocal THREAD_LOCAL_INSTANCE = new InheritableThreadLocal();
- private UserSession _userSession = new UserSession();
- private InputMethod _input;
- private OutputMethod _output;
- private Map _headers;
- private String _language;
- private String _service;
- private String _ipAddress;
- private int _maxUploadSize;
- private JeevesServlet _servlet;
- private boolean _startupError = false;
- private Map _startupErrors;
+
+ /**
+ * Simple data structure recording service details.
+ *
+ * Lightweight data structure used logging service details such as name.
+ */
+ public static class ServiceDetails {
+ private String ipAddress;
+ private String service;
+ private String language;
+ public ServiceDetails(ServiceContext context){
+ this.ipAddress = context.getIpAddress();
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ public String getService() {
+ return service;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ServiceDetails that = (ServiceDetails) o;
+ return Objects.equals(ipAddress, that.ipAddress) && service.equals(that.service) && Objects.equals(language, that.language);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ipAddress, service, language);
+ }
+ }
+
+ /**
+ * Shared service context offering limited functionality.
+ *
+ * Use of service context has been assumed in many parts of the codebase. This shared "AppHandler" service context
+ * is used during Jeeves startup and has some protection from being cleared. Additional managers such HarvestManager
+ * also use a shared service context to support background processes.
+ */
+ public static class AppHandlerServiceContext extends ServiceContext {
+
+ /**
+ * Shared AppHandler service context associated with application lifecycle.
+ *
+ * See factory method {@link ServiceManager#createAppHandlerServiceContext(ConfigurableApplicationContext, String)} and
+ * {@link ServiceManager#createAppHandlerServiceContext(ConfigurableApplicationContext)}.
+ *
+ * @param service Service name
+ * @param jeevesApplicationContext Application context
+ * @param contexts Handler context
+ * @param entityManager
+ */
+ public AppHandlerServiceContext(final String service, final ConfigurableApplicationContext jeevesApplicationContext,
+ final Map contexts, final EntityManager entityManager) {
+ super( service, jeevesApplicationContext, contexts, entityManager );
+ _language = "?";
+ _userSession = null;
+ _ipAddress = "?";
+ }
+
+ @Override
+ public void setUserSession(UserSession session) {
+ if (session != null) {
+ warning("Shared service context \"" + _service + "\" context should not be configured with user session");
+ }
+ super.setUserSession(session);
+ }
+ @Override
+ public void setIpAddress(String address) {
+ if( address != null && !"?".equals(address)) {
+ warning("Shared service context \""+_service+"\" should not be associated with an ip address");
+ }
+ super.setIpAddress(address);
+ }
+
+ public void clear() {
+ warning("Shared service context \""+_service+"\" context is shared, and should not be cleared");
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("AppHandlerServiceContext ");
+ sb.append("'").append(_service).append('\'');
+
+ return sb.toString();
+ }
+
+ };
+ /**
+ * Trace allocation via {@link #setAsThreadLocal()}.
+ *
+ * Recording where the service context was assigned to the thread local to aid in debugging.
+ */
+ protected Throwable allocation = null;
+
+ /**
+ * Trace deallocation via {@link #clear()} method.
+ *
+ * Recording where the service context clear() was called to aid in debugging.
+ */
+ protected Throwable deAllocation = null;
+
+ protected UserSession _userSession = new UserSession();
+ protected InputMethod _input;
+ protected OutputMethod _output;
+ protected Map _headers;
+ protected String _language;
+ protected String _service;
+ protected String _ipAddress;
+ protected int _maxUploadSize;
+ protected JeevesServlet _servlet;
+ protected boolean _startupError = false;
+ protected Map _startupErrors;
/**
* Property to be able to add custom response headers depending on the code (and not the xml of
* Jeeves)
@@ -76,7 +235,7 @@ public class ServiceContext extends BasicContext {
*
* @see #_statusCode
*/
- private Map _responseHeaders;
+ protected Map _responseHeaders;
/**
* Property to be able to add custom http status code headers depending on the code (and not the
* xml of Jeeves)
@@ -86,7 +245,16 @@ public class ServiceContext extends BasicContext {
*
* @see #_responseHeaders
*/
- private Integer _statusCode;
+ protected Integer _statusCode;
+
+ /**
+ * Context for service execution.
+ *
+ * @param service Service name
+ * @param jeevesApplicationContext Application context
+ * @param contexts Handler context
+ * @param entityManager
+ */
public ServiceContext(final String service, final ConfigurableApplicationContext jeevesApplicationContext,
final Map contexts, final EntityManager entityManager) {
super(jeevesApplicationContext, contexts, entityManager);
@@ -104,9 +272,47 @@ public ServiceContext(final String service, final ConfigurableApplicationContext
*/
@CheckForNull
public static ServiceContext get() {
- return THREAD_LOCAL_INSTANCE.get();
+ ServiceContext context = THREAD_LOCAL_INSTANCE.get();
+ if(context != null && context.isCleared()) {
+ context.checkCleared("Thread local access");
+ }
+ return context;
}
+ /**
+ * Auto closable for try-with-resources support.
+ *
+ * For use when creating service context for use as a parameter, will check and handle {@link #clear()} if needed:
+ *
+ */
+ public void clear(){
+ if( this._service != null) {
+ deAllocation = new Throwable("ServiceContext "+_service+" cleared");
+ this._service = null;
+ this._headers = null;
+ this._responseHeaders = null;
+ this._servlet = null;
+ this._userSession = null;
+ }
+ else {
+ debug("Service context unexpectedly cleared twice, previously cleared by "+deAllocation.getStackTrace()[1]);
+ }
}
//--------------------------------------------------------------------------
@@ -127,27 +516,65 @@ public void setAsThreadLocal() {
//---
//--------------------------------------------------------------------------
+ /**
+ * Language code, or "?" if undefined.
+ * @return language code, or "?" if undefined.
+ */
public String getLanguage() {
+ // checkCleared("language not available");
return _language;
}
+ /**
+ * Language code, or "?" if undefined.
+ * @param lang language code, or "?" if undefined.
+ */
public void setLanguage(final String lang) {
_language = lang;
}
+ /**
+ * True if {@link #clear()} has been called to reclaim resources.
+ *
+ * @return true if service context has been cleared
+ */
+ public boolean isCleared(){
+ return _service == null;
+ }
+
+
+ /**
+ * Service name, or null if service context is no longer in use.
+ *
+ * @return service name, or null if service is no longer in use
+ */
public String getService() {
return _service;
}
- public void setService(final String service) {
+ public void setService(String service) {
+ if( service == null ){
+ service = "internal";
+ }
this._service = service;
logger = Log.createLogger(Log.WEBAPP + "." + service);
}
+ /**
+ * IP address of request, or "?" for local loopback request.
+ *
+ * @return ip address, or "?" for loopback request.
+ */
public String getIpAddress() {
+ checkCleared("ip address not available");
return _ipAddress;
}
+ /**
+ * IP address of request, or "?" for local loopback request.
+ *
+ * @param address ip, address or "?" for loopback request.
+ */
public void setIpAddress(final String address) {
_ipAddress = address;
}
@@ -157,6 +584,7 @@ public Path getUploadDir() {
}
public int getMaxUploadSize() {
+ checkCleared("max upload size not available");
return _maxUploadSize;
}
@@ -170,6 +598,7 @@ public void setMaxUploadSize(final int size) {
* @return the user session stored on httpsession
*/
public UserSession getUserSession() {
+ checkCleared("user session not available");
return _userSession;
}
@@ -177,6 +606,23 @@ public void setUserSession(final UserSession session) {
_userSession = session;
}
+ /**
+ * Safely look up user name, or anonymous.
+ *
+ * This is a quick null safe lookup of user name suitable for use in logging and error messages.
+ *
+ * @return username, or anonymous if unavailable.
+ */
+ public String userName(){
+ if (_userSession == null || _userSession.getUsername() == null ){
+ return "anonymous";
+ }
+ if( _userSession.getProfile() != null ){
+ return _userSession.getUsername() + "/" + _userSession.getProfile();
+ }
+ return _userSession.getUsername();
+ }
+
public ProfileManager getProfileManager() {
return getBean(ProfileManager.class);
}
@@ -184,6 +630,7 @@ public ProfileManager getProfileManager() {
//--------------------------------------------------------------------------
public InputMethod getInputMethod() {
+ checkCleared("input method not available");
return _input;
}
@@ -192,6 +639,7 @@ public void setInputMethod(final InputMethod m) {
}
public OutputMethod getOutputMethod() {
+ checkCleared("output method not available");
return _output;
}
@@ -200,6 +648,7 @@ public void setOutputMethod(final OutputMethod m) {
}
public Map getStartupErrors() {
+ checkCleared("startup errors not available");
return _startupErrors;
}
@@ -209,6 +658,7 @@ public void setStartupErrors(final Map errs) {
}
public boolean isStartupError() {
+ checkCleared("is startup error not available");
return _startupError;
}
@@ -234,6 +684,7 @@ public void setLogger(final Logger l) {
* @return The map of headers from the request
*/
public Map getHeaders() {
+ checkCleared("headers not available");
return _headers;
}
@@ -249,6 +700,7 @@ public void setHeaders(Map headers) {
}
public JeevesServlet getServlet() {
+ checkCleared("servlet not available");
return _servlet;
}
@@ -269,20 +721,27 @@ public void executeOnly(final LocalServiceRequest request) throws Exception {
new TransactionTask() {
@Override
public Void doInTransaction(TransactionStatus transaction) throws Throwable {
- final ServiceContext context = new ServiceContext(request.getService(), getApplicationContext(), htContexts, getEntityManager());
- UserSession session = ServiceContext.this._userSession;
- if (session == null) {
- session = new UserSession();
- }
+ final ServiceManager serviceManager = getApplicationContext().getBean(ServiceManager.class);
+ final ServiceContext localServiceContext = serviceManager.createServiceContext(request.getService(), getApplicationContext());
+
try {
- final ServiceManager serviceManager = context.getBean(ServiceManager.class);
- serviceManager.dispatch(request, session, context);
+ UserSession session = ServiceContext.this._userSession;
+ if (session == null) {
+ session = new UserSession();
+ }
+ localServiceContext.setUserSession(session);
+
+ serviceManager.dispatch(request, session, localServiceContext);
} catch (Exception e) {
Log.error(Log.XLINK_PROCESSOR, "Failed to parse result xml" + request.getService());
throw new ServiceExecutionFailedException(request.getService(), e);
} finally {
- // set old context back as thread local
- setAsThreadLocal();
+ if( localServiceContext == ServiceContext.get()){
+ // dispatch failed to clear cleanup localServiceContext
+ // restoring back as thread local
+ ServiceContext.this.setAsThreadLocal();
+ }
+ localServiceContext.clear();
}
return null;
}
@@ -320,6 +779,17 @@ public void setStatusCode(Integer statusCode) {
this._statusCode = statusCode;
}
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ServiceContext ");
+ sb.append("'").append(_service).append('\'');
+ sb.append(" ").append(_input).append(" --> ").append(_output);
+ sb.append(" { _language='").append(_language).append('\'');
+ sb.append(", _ipAddress='").append(_ipAddress).append('\'');
+ sb.append('}');
+
+ return sb.toString();
+ }
}
//=============================================================================
diff --git a/core/src/main/java/jeeves/server/dispatchers/ServiceManager.java b/core/src/main/java/jeeves/server/dispatchers/ServiceManager.java
index 0bc08f24274..37b64e69325 100644
--- a/core/src/main/java/jeeves/server/dispatchers/ServiceManager.java
+++ b/core/src/main/java/jeeves/server/dispatchers/ServiceManager.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2005 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -49,12 +49,14 @@
import org.fao.geonet.Constants;
import org.fao.geonet.NodeInfo;
import org.fao.geonet.Util;
+import org.fao.geonet.domain.User;
import org.fao.geonet.exceptions.JeevesException;
import org.fao.geonet.exceptions.NotAllowedEx;
import org.fao.geonet.exceptions.ServiceNotFoundEx;
import org.fao.geonet.exceptions.ServiceNotMatchedEx;
import org.fao.geonet.kernel.GeonetworkDataDirectory;
import org.fao.geonet.kernel.setting.SettingManager;
+import org.fao.geonet.repository.UserRepository;
import org.fao.geonet.util.XslUtil;
import org.fao.geonet.utils.BLOB;
import org.fao.geonet.utils.BinaryFile;
@@ -64,6 +66,9 @@
import org.fao.geonet.utils.Xml;
import org.jdom.Element;
import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@@ -78,6 +83,10 @@
import java.util.Map.Entry;
//=============================================================================
+
+/**
+ * Handles operations on services.
+ */
public class ServiceManager {
private Map> htServices = new HashMap>(100);
private Map htContexts = new HashMap();
@@ -342,6 +351,106 @@ public void addErrorPage(Element err) throws Exception {
vErrorPipe.add(buildErrorPage(err));
}
+ /**
+ * Used to create an appContext placeholder service context used for initialization, background tasks and activities.
+ *
+ * This ServiceContext is used during initialization and is independent of any user session.
+ * This instance is the responsibility of a specific manager and will produce a warning if an attempt is made
+ * to clear or assign a user session.
+ *
+ * @param appContext GeoNetwork Application Context
+ * @return new service context with limited functionality
+ */
+ public ServiceContext.AppHandlerServiceContext createAppHandlerServiceContext(ConfigurableApplicationContext appContext) {
+ ServiceContext.AppHandlerServiceContext context = new ServiceContext.AppHandlerServiceContext("AppHandler", appContext, htContexts, entityManager);
+ context.setBaseUrl(baseUrl);
+ context.setMaxUploadSize(maxUploadSize);
+ context.setServlet(servlet);
+
+ return context;
+ }
+
+ /**
+ * Used to create an appContext placeholder service context for a specific manager used for initialization, background tasks and activities.
+ *
+ * Previously a single AppHandler service context was shared managed by Jeeves.
+ *
+ * This instance is the responsibility of a specific manager and will produce a warning if an attempt is made
+ * to clear or assign a user session.
+ *
+ * @param manager Manager name such as AppContext or harvester
+ * @return new service context with limited functionality
+ */
+ public ServiceContext.AppHandlerServiceContext createAppHandlerServiceContext(String manager, ServiceContext parent) {
+ ServiceContext.AppHandlerServiceContext context = new ServiceContext.AppHandlerServiceContext(manager, parent.getApplicationContext(), htContexts, entityManager);
+ context.setBaseUrl(baseUrl);
+ context.setMaxUploadSize(maxUploadSize);
+ context.setServlet(servlet);
+
+ return context;
+ }
+
+
+ /**
+ * Used to create a serviceContext for later use, the object provided the new serviceContext is responsible
+ * for cleanup.
+ *
+ *
+ * @param name
+ * @param parent
+ * @return new service context
+ */
+ public ServiceContext createServiceContext(String name, ServiceContext parent ){
+ ServiceContext context = createServiceContext( name, parent.getApplicationContext());
+ context.setBaseUrl(parent.getBaseUrl());
+ context.setLanguage(parent.getLanguage());
+ context.setUserSession(null); // because this is intended for later use user session not included
+ context.setIpAddress(parent.getIpAddress());
+ context.setMaxUploadSize(parent.getMaxUploadSize());
+ context.setServlet(parent.getServlet());
+
+ return context;
+ }
+ /**
+ * Create an internal service context, not associated with a user or ip address.
+ *
+ * When creating a ServiceContext you are responsible for manging its use on the current thread and any cleanup.
+ *
+ * Using auto closable:
+ *
+ *
+ * @param name context name
+ * @param appContext application context
+ * @return ServiceContext
+ */
public ServiceContext createServiceContext(String name, ConfigurableApplicationContext appContext) {
ServiceContext context = new ServiceContext(name, appContext, htContexts,
entityManager);
@@ -356,6 +465,36 @@ public ServiceContext createServiceContext(String name, ConfigurableApplicationC
return context;
}
+ /**
+ * Used to create a ServiceContext.
+ *
+ * When creating a ServiceContext you are responsible for manging its use on the current thread and any cleanup.
+ *
+ * Using auto closable:
+ *
+ *
+ * The serviceContext is creating using the ApplicationContext from {@link ApplicationContextHolder}.
+ *
+ * @param name context name
+ * @param lang
+ * @param request servlet request
+ * @return ServiceContext
+ */
public ServiceContext createServiceContext(String name, String lang, HttpServletRequest request) {
ServiceContext context = new ServiceContext(name, ApplicationContextHolder.get(), htContexts, entityManager);
@@ -381,10 +520,80 @@ public ServiceContext createServiceContext(String name, String lang, HttpServlet
return context;
}
+ /**
+ * Create a transitory service context for use in a single try-with-resources block.
+ *
+ * Makes use of current http session if available (the usual case), or a temporary user session using the provided
+ * userId (when used from a background task or job).
+ *
+ * Code creating a service context is responsible for handling resources and cleanup.
+ *
+ *
+ * try( ServiceContext context = createServiceContext("approve_record", event.getUser()){
+ * ... utility methods can now use ServiceContext.get() ...
+ * }
+ *
+ * @param name service context name for approval record handling
+ * @param defaultUserId If a user session is not available, this id is used to create a temporary user session
+ * @return service context for approval record event handling
+ */
+ public ServiceContext createServiceContext(String name, int defaultUserId){
+ // If this implementation is generally useful it should migrate to ServiceManager, rather than cut and paste
+ ConfigurableApplicationContext applicationContext = ApplicationContextHolder.get();
+
+ ServiceContext context;
+
+ HttpServletRequest request = getCurrentHttpRequest();
+ if( request != null ) {
+ // reuse user session from http request
+ context = createServiceContext(name, "?", request);
+ }
+ else {
+ // Not in an http request, creating a temporary user session wiht provided userId
+ context = createServiceContext(name, applicationContext);
+
+ UserRepository userRepository = applicationContext.getBean(UserRepository.class);
+ Optional user = userRepository.findById( defaultUserId );
+ if( user.isPresent() ){
+ UserSession session = new UserSession();
+ session.loginAs(user.get());
+ context.setUserSession(session);
+ }
+ }
+ context.setAsThreadLocal();
+
+ return context;
+ }
+
+ /**
+ * Look up current HttpServletRequest if running in a servlet dispatch.
+ *
+ * @return http request, or null if running in a background task
+ */
+ private static HttpServletRequest getCurrentHttpRequest(){
+ RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+ if (requestAttributes instanceof ServletRequestAttributes) {
+ HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
+ return request;
+ }
+ return null; // not called during http request
+ }
+
+
+ /**
+ * Dispatch service request, creating a service context with the provided user session.
+ *
+ * @param req service request
+ * @param session user session
+ */
public void dispatch(ServiceRequest req, UserSession session) {
ServiceContext context = new ServiceContext(req.getService(), ApplicationContextHolder.get(),
htContexts, entityManager);
- dispatch(req, session, context);
+ try {
+ dispatch(req, session, context);
+ } finally {
+ context.clear();
+ }
}
//---------------------------------------------------------------------------
@@ -396,6 +605,14 @@ public void dispatch(ServiceRequest req, UserSession session) {
//--- Dispatching methods
//---
//---------------------------------------------------------------------------
+
+ /**
+ * Dispatch service request, configuring context with the provided user session.
+ *
+ * @param req service request
+ * @param session user session
+ * @param context service context
+ */
public void dispatch(ServiceRequest req, UserSession session, ServiceContext context) {
context.setBaseUrl(baseUrl);
context.setLanguage(req.getLanguage());
@@ -408,6 +625,11 @@ public void dispatch(ServiceRequest req, UserSession session, ServiceContext con
context.setServlet(servlet);
if (startupError) context.setStartupErrors(startupErrors);
+ ServiceContext priorContext = ServiceContext.get();
+ if( priorContext != null){
+ priorContext.debug("ServiceManger dispatch replacing current ServiceContext");
+ priorContext.clearAsThreadLocal();
+ }
context.setAsThreadLocal();
//--- invoke service and build result
@@ -505,6 +727,20 @@ public void dispatch(ServiceRequest req, UserSession session, ServiceContext con
handleError(req, response, context, srvInfo, e);
}
}
+ finally {
+ ServiceContext checkContext = ServiceContext.get();
+ if( checkContext == context ) {
+ context.clearAsThreadLocal();
+ }
+ else {
+ context.debug("ServiceManager dispatch context was replaced before cleanup");
+ }
+ context.clearAsThreadLocal();
+ if( priorContext != null){
+ priorContext.debug("ServiceManger dispatch restoring ServiceContext");
+ priorContext.setAsThreadLocal();
+ }
+ }
}
//---------------------------------------------------------------------------
@@ -844,7 +1080,7 @@ else if (outPage.isBLOB()) {
} finally {
timerContext.stop();
}
-
+
if (outPage.getContentType() != null
&& outPage.getContentType().startsWith("text/plain")) {
req.beginStream(outPage.getContentType(), -1, "attachment;", cache);
diff --git a/core/src/main/java/jeeves/server/sources/ServiceRequest.java b/core/src/main/java/jeeves/server/sources/ServiceRequest.java
index 063025dbf29..5f9ef351d8c 100644
--- a/core/src/main/java/jeeves/server/sources/ServiceRequest.java
+++ b/core/src/main/java/jeeves/server/sources/ServiceRequest.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2005 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -196,21 +196,36 @@ public boolean hasJSONOutput() {
return jsonOutput;
}
+ /**
+ * Write provided response element, ending the stream.
+ *
+ * @param response
+ * @throws IOException
+ */
public void write(Element response) throws IOException {
Xml.writeResponse(new Document(response), outStream);
endStream();
}
/**
- * called when the system starts streaming data
+ * Called when the system starts streaming data
+ * @param contentType mime type
+ * @param cache true if content can be cached, false to disable caching for dynamic content
*/
-
public void beginStream(String contentType, boolean cache) {
}
//---------------------------------------------------------------------------
- public void beginStream(String contentType, int contentLength, String contentDisp,
+ /**
+ * Called when the system starts streaming data, filling in appropriate header details supported by protocol.
+ *
+ * @param contentType mime type
+ * @param contentLength content length in bytes if known, -1 if unknown
+ * @param contentDisposition content disposition (inline|attachment|attachment;filename="filename.jpg")
+ * @param cache true if content can be cached, false to disable caching for dynamic content
+ */
+ public void beginStream(String contentType, int contentLength, String contentDisposition,
boolean cache) {
}
@@ -219,7 +234,6 @@ public void beginStream(String contentType, int contentLength, String contentDis
/**
* called when the system ends streaming data
*/
-
public void endStream() throws IOException {
}
diff --git a/core/src/main/java/org/fao/geonet/ContextContainer.java b/core/src/main/java/org/fao/geonet/ContextContainer.java
index e6f7022df0f..eb607d5e7e1 100644
--- a/core/src/main/java/org/fao/geonet/ContextContainer.java
+++ b/core/src/main/java/org/fao/geonet/ContextContainer.java
@@ -37,8 +37,10 @@
public class ContextContainer implements ApplicationContextAware {
- //private GeonetContext geoctx;
+ /** Shared service context for GeoNetwork application */
private ServiceContext srvctx;
+
+ /** Shared application context provided during initialization */
private ApplicationContext ctx;
public ContextContainer() {
@@ -46,30 +48,44 @@ public ContextContainer() {
}
- /*
- public GeonetContext getGeoctx() {
- return geoctx;
- }
-
- public void setGeoctx(GeonetContext geoctx) {
-
- this.geoctx = geoctx;
- }
- */
-
+ /**
+ * Service context shared for GeoNetwork application.
+ *
+ * This is an application wide service context with limited functionality, no user session for example.
+ *
+ * Use of {@link ServiceContext#get()} is recommended in most situations for access
+ * to current user session.
+ *
+ * @return Shared service context for GeoNetwork application
+ */
public ServiceContext getSrvctx() {
return srvctx;
}
+ /**
+ * Service context shared for GeoNetwork application.
+ *
+ * @param srvctx Shared app handler service context for GeoNetwork application
+ */
public void setSrvctx(ServiceContext srvctx) {
this.srvctx = srvctx;
}
+ /**
+ * GeoNetwork application context provided during initialization.
+ *
+ * @return geonetwork application context
+ */
public ApplicationContext getApplicationContext() {
if (ctx == null) throw new RuntimeException("applicationcontext not yet initialized");
return ctx;
}
+ /**
+ * GeoNetwork application context provided during initialization.
+ * @param arg0
+ * @throws BeansException
+ */
public void setApplicationContext(ApplicationContext arg0)
throws BeansException {
diff --git a/core/src/main/java/org/fao/geonet/GeonetContext.java b/core/src/main/java/org/fao/geonet/GeonetContext.java
index 9453be1f80b..615876d94d3 100644
--- a/core/src/main/java/org/fao/geonet/GeonetContext.java
+++ b/core/src/main/java/org/fao/geonet/GeonetContext.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2007 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -25,10 +25,12 @@
import com.google.common.annotations.VisibleForTesting;
-import org.fao.geonet.kernel.metadata.StatusActions;
import org.fao.geonet.util.ThreadPool;
import org.springframework.context.ApplicationContext;
+/**
+ * GeoNetwork context managing application context and a shared thread pool.
+ */
public class GeonetContext {
private final ApplicationContext _springAppContext;
private final ThreadPool _threadPool;
diff --git a/core/src/main/java/org/fao/geonet/api/records/attachments/AbstractStore.java b/core/src/main/java/org/fao/geonet/api/records/attachments/AbstractStore.java
index 575105f2102..4e05a2fc29f 100644
--- a/core/src/main/java/org/fao/geonet/api/records/attachments/AbstractStore.java
+++ b/core/src/main/java/org/fao/geonet/api/records/attachments/AbstractStore.java
@@ -1,27 +1,26 @@
-/*
- * =============================================================================
- * === Copyright (C) 2019 Food and Agriculture Organization of the
- * === United Nations (FAO-UN), United Nations World Food Programme (WFP)
- * === and United Nations Environment Programme (UNEP)
- * ===
- * === This program is free software; you can redistribute it and/or modify
- * === it under the terms of the GNU General Public License as published by
- * === the Free Software Foundation; either version 2 of the License, or (at
- * === your option) any later version.
- * ===
- * === This program is distributed in the hope that it will be useful, but
- * === WITHOUT ANY WARRANTY; without even the implied warranty of
- * === MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * === General Public License for more details.
- * ===
- * === You should have received a copy of the GNU General Public License
- * === along with this program; if not, write to the Free Software
- * === Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- * ===
- * === Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
- * === Rome - Italy. email: geonetwork@osgeo.org
- * ==============================================================================
- */
+///=============================================================================
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
+//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
+//=== and United Nations Environment Programme (UNEP)
+//===
+//=== This program is free software; you can redistribute it and/or modify
+//=== it under the terms of the GNU General Public License as published by
+//=== the Free Software Foundation; either version 2 of the License, or (at
+//=== your option) any later version.
+//===
+//=== This program is distributed in the hope that it will be useful, but
+//=== WITHOUT ANY WARRANTY; without even the implied warranty of
+//=== MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+//=== General Public License for more details.
+//===
+//=== You should have received a copy of the GNU General Public License
+//=== along with this program; if not, write to the Free Software
+//=== Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+//===
+//=== Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
+//=== Rome - Italy. email: geonetwork@osgeo.org
+//==============================================================================
+
package org.fao.geonet.api.records.attachments;
import jeeves.server.context.ServiceContext;
@@ -125,23 +124,34 @@ protected int canEdit(ServiceContext context, String metadataUuid, MetadataResou
boolean canEdit = getAccessManager(context).canEdit(context, String.valueOf(metadataId));
if ((visibility == null && !canEdit) || (visibility == MetadataResourceVisibility.PRIVATE && !canEdit)) {
throw new SecurityException(String.format("User '%s' does not have privileges to access '%s' resources for metadata '%s'.",
- context.getUserSession() != null ?
- context.getUserSession().getUsername() + "/" + context.getUserSession()
- .getProfile() :
- "anonymous", visibility == null ? "any" : visibility, metadataUuid));
+ context.userName(), visibility == null ? "any" : visibility, metadataUuid));
}
return metadataId;
}
+ /**
+ *
+ * @param context Service context used to determine user
+ * @param metadataUuid UUID of metadata record to check
+ * @param visibility resource visibility
+ * @param approved
+ * @return The metadata id used to access resources, obtained and approved from provided metadataUuid
+ * @throws Exception A security exception if the content is not allowed to access these resources
+ */
protected int canDownload(ServiceContext context, String metadataUuid, MetadataResourceVisibility visibility, Boolean approved)
throws Exception {
int metadataId = getAndCheckMetadataId(metadataUuid, approved);
if (visibility == MetadataResourceVisibility.PRIVATE) {
- boolean canDownload = getAccessManager(context).canDownload(context, String.valueOf(metadataId));
- if (!canDownload) {
- throw new SecurityException(String.format(
+ if(context instanceof ServiceContext.AppHandlerServiceContext) {
+ // internal access granted
+ }
+ else {
+ boolean canDownload = getAccessManager(context).canDownload(context, String.valueOf(metadataId));
+ if (!canDownload) {
+ throw new SecurityException(String.format(
"Current user can't download resources for metadata '%s' and as such can't access the requested resource.",
metadataUuid));
+ }
}
}
return metadataId;
diff --git a/core/src/main/java/org/fao/geonet/kernel/AccessManager.java b/core/src/main/java/org/fao/geonet/kernel/AccessManager.java
index 5ae066e1c55..ea45a49a857 100644
--- a/core/src/main/java/org/fao/geonet/kernel/AccessManager.java
+++ b/core/src/main/java/org/fao/geonet/kernel/AccessManager.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2007 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -99,13 +99,27 @@ public class AccessManager {
* perform on that metadata (an set of OPER_XXX as keys). If the user is authenticated the
* permissions are taken from the groups the user belong. If the user is not authenticated, a
* dynamic group is assigned depending on user location (0 for internal and 1 for external).
+ *
+ * @param context service context
+ * @param mdId metadata record to check
+ * @param ip IP Address used to determine base operations
+ * @return set of base operations along with any additional reserved operations available to user
*/
public Set getOperations(ServiceContext context, String mdId, String ip) throws Exception {
return getOperations(context, mdId, ip, null);
}
/**
- * TODO javadoc.
+ * Given a user(session) a list of groups and a metadata returns all operations that user can
+ * perform on that metadata (an set of OPER_XXX as keys). If the user is authenticated the
+ * permissions are taken from the groups the user belong. If the user is not authenticated, a
+ * dynamic group is assigned depending on user location (0 for internal and 1 for external).
+ *
+ * @param context service context
+ * @param mdId metadata record to check
+ * @param ip IP Address used to determine base operations (if not provided)
+ * @param operations base operations
+ * @return set of base operations along with any additional reserved operations available to user
*/
public Set getOperations(ServiceContext context, String mdId, String ip, Collection operations) throws Exception {
Set results;
diff --git a/core/src/main/java/org/fao/geonet/kernel/DataManager.java b/core/src/main/java/org/fao/geonet/kernel/DataManager.java
index 500e5ab882c..f76579667c0 100644
--- a/core/src/main/java/org/fao/geonet/kernel/DataManager.java
+++ b/core/src/main/java/org/fao/geonet/kernel/DataManager.java
@@ -3,7 +3,7 @@
//=== DataManager
//===
//=============================================================================
-//=== Copyright (C) 2001-2007 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -122,24 +122,33 @@ public static void setNamespacePrefix(final Element md) {
}
/**
- * Init Data manager and refresh index if needed. Can also be called after GeoNetwork startup in order to rebuild the lucene index
+ * Init data manager components.
*
- * @param force Force reindexing all from scratch
+ * @param context Service context used for setup
**/
- public void init(ServiceContext context, Boolean force) throws Exception {
- this.metadataIndexer.init(context, force);
- this.metadataManager.init(context, force);
- this.metadataUtils.init(context, force);
+ public void init(ServiceContext context) throws Exception {
+ this.metadataIndexer.init(context);
+ this.metadataManager.init(context);
+ this.metadataUtils.init(context);
// FIXME this shouldn't login automatically ever!
- if (context.getUserSession() == null) {
- LOGGER_DATA_MANAGER.debug("Automatically login in as Administrator. Who is this? Who is calling this?");
- UserSession session = new UserSession();
- context.setUserSession(session);
- session.loginAs(new User().setUsername("admin").setId(-1).setProfile(Profile.Administrator));
- LOGGER_DATA_MANAGER.debug("Hopefully this is cron job or routinely background task. Who called us?",
- new Exception("Dummy Exception to know the stacktrace"));
- }
+// if (context.getUserSession() == null) {
+// LOGGER_DATA_MANAGER.debug("Automatically login in as Administrator. Who is this? Who is calling this?");
+// UserSession session = new UserSession();
+// context.setUserSession(session);
+// session.loginAs(new User().setUsername("admin").setId(-1).setProfile(Profile.Administrator));
+// LOGGER_DATA_MANAGER.debug("Hopefully this is cron job or routinely background task. Who called us?",
+// new Exception("Dummy Exception to know the stacktrace"));
+// }
+ }
+
+ /**
+ * Clean up data manager during application shutdown.
+ */
+ public void destroy() throws Exception {
+ this.metadataIndexer.destroy();
+ this.metadataManager.destroy();
+ this.metadataUtils.destroy();
}
@Deprecated
@@ -153,8 +162,8 @@ public synchronized void rebuildIndexForSelection(final ServiceContext context,
}
@Deprecated
- public void batchIndexInThreadPool(ServiceContext context, List> metadataIds) {
- metadataIndexer.batchIndexInThreadPool(context, metadataIds);
+ public void batchIndexInThreadPool(List> metadataIds) {
+ metadataIndexer.batchIndexInThreadPool(metadataIds);
}
@Deprecated
diff --git a/core/src/main/java/org/fao/geonet/kernel/IndexMetadataTask.java b/core/src/main/java/org/fao/geonet/kernel/IndexMetadataTask.java
index ac7db5535f3..9ad233a780a 100644
--- a/core/src/main/java/org/fao/geonet/kernel/IndexMetadataTask.java
+++ b/core/src/main/java/org/fao/geonet/kernel/IndexMetadataTask.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2001-2016 Food and Agriculture Organization of the
+ * Copyright (C) 2001-2021 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
@@ -25,11 +25,13 @@
import jeeves.server.context.ServiceContext;
+import jeeves.server.dispatchers.ServiceManager;
import org.fao.geonet.Util;
import org.fao.geonet.constants.Geonet;
import org.fao.geonet.domain.User;
import org.fao.geonet.kernel.search.EsSearchManager;
import org.fao.geonet.utils.Log;
+import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.transaction.TransactionStatus;
import java.io.IOException;
@@ -45,28 +47,37 @@
*/
public final class IndexMetadataTask implements Runnable {
- private final ServiceContext _context;
+ private final String serviceName;
+ private final ServiceManager serviceManager;
private final List> _metadataIds;
private final TransactionStatus _transactionStatus;
private final Set _batchIndex;
private final EsSearchManager searchManager;
+ private final ConfigurableApplicationContext appContext;
private final AtomicInteger indexed;
private User _user;
/**
- * Constructor.
+ * Setup index metadata task to be run.
*
- * @param context context object
+ * The context is used to look up beans for setup and configuration only. The task will create its own serviceContext
+ * to be used during indexing.
+ *
+ * @param context context object responsible for starting the activity
* @param metadataIds the metadata ids to index (either integers or strings)
+ * @param batchIndex Set used to track outstanding tasks
* @param transactionStatus if non-null, wait for the transaction to complete before indexing
+ * @param indexed Used to track number of indexed records
*/
public IndexMetadataTask(@Nonnull ServiceContext context, @Nonnull List> metadataIds, Set batchIndex,
@Nullable TransactionStatus transactionStatus, @Nonnull AtomicInteger indexed) {
this.indexed = indexed;
this._transactionStatus = transactionStatus;
- this._context = context;
+ this.serviceName = context.getService();
this._metadataIds = metadataIds;
this._batchIndex = batchIndex;
+ this.serviceManager = context.getBean(ServiceManager.class);
+ this.appContext = context.getApplicationContext();
this.searchManager = context.getBean(EsSearchManager.class);
batchIndex.add(this);
@@ -76,9 +87,17 @@ public IndexMetadataTask(@Nonnull ServiceContext context, @Nonnull List> metad
}
}
+ /**
+ * Perform index task in a seperate thread.
+ *
+ * Task waits for transactionStatus (if available) to be completed, and for servlet to be initialized.
+ *
+ */
+ @Override
public void run() {
- try {
- _context.setAsThreadLocal();
+ try (ServiceContext indexMedataContext = serviceManager.createServiceContext(serviceName+":IndexTask", appContext)) {
+ indexMedataContext.setAsThreadLocal();
+ indexMedataContext.setAsThreadLocal();
while (_transactionStatus != null && !_transactionStatus.isCompleted()) {
try {
Thread.sleep(100);
@@ -87,7 +106,7 @@ public void run() {
}
}
// poll context to see whether servlet is up yet
- while (!_context.isServletInitialized()) {
+ while (!indexMedataContext.isServletInitialized()) {
if (Log.isDebugEnabled(Geonet.DATA_MANAGER)) {
Log.debug(Geonet.DATA_MANAGER, "Waiting for servlet to finish initializing..");
}
@@ -98,7 +117,7 @@ public void run() {
}
}
- DataManager dataManager = _context.getBean(DataManager.class);
+ DataManager dataManager = indexMedataContext.getBean(DataManager.class);
// servlet up so safe to index all metadata that needs indexing
for (Object metadataId : _metadataIds) {
this.indexed.incrementAndGet();
@@ -113,8 +132,8 @@ public void run() {
+ "\n" + Util.getStackTrace(e));
}
}
- if (_user != null && _context.getUserSession().getUserId() == null) {
- _context.getUserSession().loginAs(_user);
+ if (_user != null && indexMedataContext.getUserSession().getUserId() == null) {
+ indexMedataContext.getUserSession().loginAs(_user);
}
searchManager.forceIndexChanges();
} finally {
diff --git a/core/src/main/java/org/fao/geonet/kernel/SvnManager.java b/core/src/main/java/org/fao/geonet/kernel/SvnManager.java
index 798b4b07814..620df3e7eac 100644
--- a/core/src/main/java/org/fao/geonet/kernel/SvnManager.java
+++ b/core/src/main/java/org/fao/geonet/kernel/SvnManager.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2011 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -90,11 +90,19 @@
import static org.fao.geonet.kernel.setting.Settings.METADATA_VCS;
+/**
+ * Subversion manager.
+ */
public class SvnManager implements AfterCommitTransactionListener, BeforeRollbackTransactionListener {
private static String username = "geonetwork";
private static String password = "geonetwork";
// configure via setter in Geonetwork app
- private ServiceContext context;
+ /**
+ * Shared application handler service context provided by GeoNetwork application for bean lookup.
+ *
+ * In most cases a ServiceContext is provided for access ot the user session.
+ */
+ private ServiceContext appHandlerContext;
// configure in init method
private SVNURL repoUrl;
// configure in spring configuration of bean
@@ -279,7 +287,7 @@ public void setSubversionPath(String subversionPath) {
}
public void setContext(ServiceContext context) {
- this.context = context;
+ this.appHandlerContext = context;
}
/**
@@ -553,7 +561,7 @@ public void commitMetadata(String id, ISVNEditor editor) throws Exception {
return;
}
- DataManager dataMan = context.getBean(DataManager.class);
+ DataManager dataMan = appHandlerContext.getBean(DataManager.class);
try {
// get the metadata record and if different commit changes
@@ -694,8 +702,8 @@ private void commitMetadataOwner(ISVNEditor editor, String id) throws Exception
// get owner from the database
Set ids = new HashSet();
ids.add(Integer.valueOf(id));
- AbstractMetadata metadata = this.context.getBean(IMetadataUtils.class).findOne(id);
- User user = this.context.getBean(UserRepository.class).findById(metadata.getSourceInfo().getOwner()).get();
+ AbstractMetadata metadata = appHandlerContext.getBean(IMetadataUtils.class).findOne(id);
+ User user = appHandlerContext.getBean(UserRepository.class).findById(metadata.getSourceInfo().getOwner()).get();
// Backwards compatibility. Format the metadata as XML in same format as previous versions.
Element xml = new Element("results").addContent(
new Element("record")
diff --git a/core/src/main/java/org/fao/geonet/kernel/ThesaurusManager.java b/core/src/main/java/org/fao/geonet/kernel/ThesaurusManager.java
index f86346c9b33..ff0cfe1060f 100644
--- a/core/src/main/java/org/fao/geonet/kernel/ThesaurusManager.java
+++ b/core/src/main/java/org/fao/geonet/kernel/ThesaurusManager.java
@@ -1,4 +1,4 @@
-//=== Copyright (C) 2001-2005 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -43,6 +43,7 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import jeeves.server.dispatchers.ServiceManager;
import org.fao.geonet.Util;
import org.fao.geonet.constants.Geonet;
import org.fao.geonet.domain.AbstractMetadata;
@@ -73,6 +74,7 @@
import jeeves.server.context.ServiceContext;
import jeeves.xlink.Processor;
+import org.springframework.context.ConfigurableApplicationContext;
public class ThesaurusManager implements ThesaurusFinder {
@@ -149,7 +151,17 @@ private void batchBuildTable(boolean synchRun, ServiceContext context, Path thes
try {
Runnable worker = new InitThesauriTableTask(context, thesauriDir);
if (synchRun) {
- worker.run();
+ ServiceContext restore = ServiceContext.get();
+ try {
+ if( restore != null){
+ restore.clearAsThreadLocal();
+ }
+ worker.run();
+ } finally {
+ if( restore != null){
+ restore.setAsThreadLocal();
+ }
+ }
} else {
executor = Executors.newFixedThreadPool(1);
executor.execute(worker);
@@ -546,17 +558,20 @@ public Element buildResultfromThTable(ServiceContext context) throws SQLExceptio
*/
final class InitThesauriTableTask implements Runnable {
- private final ServiceContext context;
+ //private final ServiceContext context;
private final Path thesauriDir;
+ private final ServiceManager serviceManager;
+ private final ConfigurableApplicationContext appContext;
InitThesauriTableTask(ServiceContext context, Path thesauriDir) {
- this.context = context;
+ //this.context = context;
+ this.serviceManager = context.getBean(ServiceManager.class);
+ this.appContext = context.getApplicationContext();
this.thesauriDir = thesauriDir;
}
public void run() {
- context.setAsThreadLocal();
- try {
+ try (ServiceContext context = serviceManager.createServiceContext(Geonet.THESAURUS_MAN, appContext)) {
// poll context to see whether servlet is up yet
while (!context.isServletInitialized()) {
if (Log.isDebugEnabled(Geonet.THESAURUS_MAN)) {
diff --git a/core/src/main/java/org/fao/geonet/kernel/backup/ArchiveAllMetadataJob.java b/core/src/main/java/org/fao/geonet/kernel/backup/ArchiveAllMetadataJob.java
index a0edb9b06e4..1a707308c5e 100644
--- a/core/src/main/java/org/fao/geonet/kernel/backup/ArchiveAllMetadataJob.java
+++ b/core/src/main/java/org/fao/geonet/kernel/backup/ArchiveAllMetadataJob.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2001-2016 Food and Agriculture Organization of the
+ * Copyright (C) 2001-2021 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
@@ -93,22 +93,25 @@ public class ArchiveAllMetadataJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobContext) throws JobExecutionException {
- ServiceContext serviceContext = serviceManager.createServiceContext("backuparchive", context);
- serviceContext.setLanguage("eng");
- serviceContext.setAsThreadLocal();
+ try (ServiceContext serviceContext = serviceManager.createServiceContext("backuparchive", context)) {
+ serviceContext.setLanguage("eng");
+ serviceContext.setAsThreadLocal();
- ApplicationContextHolder.set(this.context);
+ // note: perhaps already done by setAsThreadLocal() above
+ ApplicationContextHolder.set(this.context);
- if(!settingManager.getValueAsBool(Settings.METADATA_BACKUPARCHIVE_ENABLE)) {
- Log.info(BACKUP_LOG, "Backup archive not enabled");
- return;
- }
+ if(!settingManager.getValueAsBool(Settings.METADATA_BACKUPARCHIVE_ENABLE)) {
+ Log.info(BACKUP_LOG, "Backup archive not enabled");
+ return;
+ }
- try {
- createBackup(serviceContext);
- } catch (Exception e) {
- Log.error(Geonet.GEONETWORK, "Error running " + ArchiveAllMetadataJob.class.getSimpleName(), e);
+ try {
+ createBackup(serviceContext);
+ } catch (Exception e) {
+ Log.error(Geonet.GEONETWORK, "Error running " + ArchiveAllMetadataJob.class.getSimpleName(), e);
+ }
}
+
}
public void createBackup(ServiceContext serviceContext) throws Exception {
diff --git a/core/src/main/java/org/fao/geonet/kernel/datamanager/IMetadataIndexer.java b/core/src/main/java/org/fao/geonet/kernel/datamanager/IMetadataIndexer.java
index 5c2a00a8d5b..d6cd000c775 100644
--- a/core/src/main/java/org/fao/geonet/kernel/datamanager/IMetadataIndexer.java
+++ b/core/src/main/java/org/fao/geonet/kernel/datamanager/IMetadataIndexer.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2011 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -43,12 +43,21 @@
public interface IMetadataIndexer {
/**
- * This is a hopefully soon to be deprecated when no deps on context
- *
- * @param context
+ * Setup metadata indexer using app service context.
+ *
+ * This is a hopefully soon to be deprecated when no deps on context.
+ *
+ * @param context App Service context used for initial setup
+ * @throws Exception
+ */
+ void init(ServiceContext context) throws Exception;
+
+ /**
+ * Clean up when service is not in use.
+ *
* @throws Exception
*/
- public void init(ServiceContext context, Boolean force) throws Exception;
+ void destroy() throws Exception;
/**
* Force the index to wait until all changes are processed and the next reader obtained will get the latest data.
@@ -78,10 +87,9 @@ public interface IMetadataIndexer {
* Index multiple metadata in a separate thread. Wait until the current transaction commits before starting threads (to make sure that
* all metadata are committed).
*
- * @param context context object
* @param metadataIds the metadata ids to index
*/
- void batchIndexInThreadPool(ServiceContext context, List> metadataIds);
+ void batchIndexInThreadPool(List> metadataIds);
/**
* Is the platform currently indexing?
diff --git a/core/src/main/java/org/fao/geonet/kernel/datamanager/IMetadataManager.java b/core/src/main/java/org/fao/geonet/kernel/datamanager/IMetadataManager.java
index 006b49340d7..4825bc6ea75 100644
--- a/core/src/main/java/org/fao/geonet/kernel/datamanager/IMetadataManager.java
+++ b/core/src/main/java/org/fao/geonet/kernel/datamanager/IMetadataManager.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2011 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -53,11 +53,17 @@ public interface IMetadataManager {
/**
* This is a hopefully soon to be deprecated initialization function to replace the @Autowired annotation
*
- * @param context
- * @param force
+ * @param context ServiceContext used for initialization
+ * @throws Exception
+ */
+ void init(ServiceContext context) throws Exception;
+
+ /**
+ * Clean up metadata manager during application shutdown.
+ *
* @throws Exception
*/
- public void init(ServiceContext context, Boolean force) throws Exception;
+ void destroy() throws Exception;
/**
* Removes the record with the id metadataId
diff --git a/core/src/main/java/org/fao/geonet/kernel/datamanager/IMetadataUtils.java b/core/src/main/java/org/fao/geonet/kernel/datamanager/IMetadataUtils.java
index 4dc52beb5b6..498d5b4e236 100644
--- a/core/src/main/java/org/fao/geonet/kernel/datamanager/IMetadataUtils.java
+++ b/core/src/main/java/org/fao/geonet/kernel/datamanager/IMetadataUtils.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2011 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -50,21 +50,28 @@
import jeeves.server.context.ServiceContext;
/**
- * Utility interface for records
+ * Utility interface for working with records.
*
- * @author delawen
+ * Operates as a facade with utility methods orchestrating common operations using a constellation
+ * beans drawn from across the application.
*
+ * @author delawen
*/
public interface IMetadataUtils {
/**
* This is a hopefully soon to be deprecated initialization function to replace the @Autowired annotation
*
- * @param context
- * @param force
+ * @param appHandlerContext this is the app handler context from jeeves initialization
+ * @throws Exception
+ */
+ public void init(ServiceContext appHandlerContext) throws Exception;
+
+ /**
+ * Clean up during application shutdown.
* @throws Exception
*/
- public void init(ServiceContext context, Boolean force) throws Exception;
+ public void destroy() throws Exception;
/**
* Return the uuid of the record with the defined id
diff --git a/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataIndexer.java b/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataIndexer.java
index 6bc9c08aec4..4989e7c7cd5 100644
--- a/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataIndexer.java
+++ b/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataIndexer.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2011 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -27,6 +27,7 @@
import com.google.common.collect.Multimap;
import jeeves.server.UserSession;
import jeeves.server.context.ServiceContext;
+import jeeves.server.dispatchers.ServiceManager;
import jeeves.xlink.Processor;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jetty.util.ConcurrentHashSet;
@@ -71,7 +72,15 @@
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
-
+/**
+ * Metadata indexer responsible for updating index in a background executor.
+ *
+ * Helper method exist to schedule records for reindex by id. These methods make use of the service context
+ * of the current thread if needed to access user session.
+ *
+ * This class maintains its own service context for use in the background, and does not have access
+ * to a user session.
+ */
public class BaseMetadataIndexer implements IMetadataIndexer, ApplicationEventPublisherAware {
@Autowired
@@ -110,16 +119,28 @@ public class BaseMetadataIndexer implements IMetadataIndexer, ApplicationEventPu
@Autowired
private Resources resources;
- // FIXME remove when get rid of Jeeves
- private ServiceContext servContext;
-
private ApplicationEventPublisher publisher;
+ /** Private service context managed by service init / destroy for use by metadata indexing tasks. */
+ private ServiceContext indexMetadataTaskContext;
+
public BaseMetadataIndexer() {
}
- public void init(ServiceContext context, Boolean force) throws Exception {
- servContext = context;
+ public void init(ServiceContext context) throws Exception {
+ ServiceManager serviceManager = context.getBean(ServiceManager.class);
+ if( indexMetadataTaskContext == null ) {
+ indexMetadataTaskContext = serviceManager.createServiceContext("_indexMetadataTask", context);
+ } else {
+ context.getLogger().debug("Metadata Indexer already initialized");
+ }
+ }
+
+ public void destroy(){
+ if (indexMetadataTaskContext != null) {
+ indexMetadataTaskContext.clear();
+ indexMetadataTaskContext = null;
+ }
}
@Override
@@ -210,7 +231,7 @@ public synchronized void rebuildIndexXLinkedMetadata(final ServiceContext contex
stringIds.add(id.toString());
}
// execute indexing operation
- batchIndexInThreadPool(context, stringIds);
+ batchIndexInThreadPool(stringIds);
}
}
@@ -247,7 +268,7 @@ public synchronized void rebuildIndexForSelection(final ServiceContext context,
}
// execute indexing operation
- batchIndexInThreadPool(context, listOfIdsToIndex);
+ batchIndexInThreadPool(listOfIdsToIndex);
}
}
@@ -256,11 +277,10 @@ public synchronized void rebuildIndexForSelection(final ServiceContext context,
* transaction commits before starting threads (to make sure that all metadata
* are committed).
*
- * @param context context object
* @param metadataIds the metadata ids to index
*/
@Override
- public void batchIndexInThreadPool(ServiceContext context, List> metadataIds) {
+ public void batchIndexInThreadPool(List> metadataIds) {
TransactionStatus transactionStatus = null;
try {
@@ -299,11 +319,11 @@ public void batchIndexInThreadPool(ServiceContext context, List> metadataIds)
}
// create threads to process this chunk of ids
- Runnable worker = new IndexMetadataTask(context, subList, batchIndex, transactionStatus, numIndexedTracker);
+ Runnable worker = new IndexMetadataTask(indexMetadataTaskContext, subList, batchIndex, transactionStatus, numIndexedTracker);
executor.execute(worker);
index += count;
}
-
+ // let the started threads finish in the background and then clean up executor
executor.shutdown();
}
@@ -324,6 +344,10 @@ public void indexMetadata(final String metadataId, final boolean forceRefreshRea
throws Exception {
AbstractMetadata fullMd;
+ if (searchManager == null) {
+ searchManager = getServiceContext().getBean(EsSearchManager.class);
+ }
+
try {
Multimap fields = ArrayListMultimap.create();
int id$ = Integer.parseInt(metadataId);
@@ -348,6 +372,11 @@ public void indexMetadata(final String metadataId, final boolean forceRefreshRea
}
fullMd = metadataUtils.findOne(id$);
+ if( fullMd == null){
+ // Metadata record has been subsequently deleted
+ searchManager.delete(metadataId);
+ return;
+ }
final String schema = fullMd.getDataInfo().getSchemaId();
final String createDate = fullMd.getDataInfo().getCreateDate().getDateAndTime();
@@ -614,9 +643,17 @@ public void versionMetadata(ServiceContext context, String id, Element md) throw
}
}
- private ServiceContext getServiceContext() {
+ /**
+ * Service context for the current thread if available, or the one provided during init.
+ *
+ * @return service context for current thread if available, or service context used during init.
+ */
+ protected ServiceContext getServiceContext() {
ServiceContext context = ServiceContext.get();
- return context == null ? servContext : context;
+ if( context != null ){
+ return context; // use ServiceContext from current ThreadLocal
+ }
+ return indexMetadataTaskContext; // backup ServiceContext provided during init
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
diff --git a/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataManager.java b/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataManager.java
index ef9ed62c418..70c7785faee 100644
--- a/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataManager.java
+++ b/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataManager.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2020 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -159,9 +159,15 @@ public void init() {
metadataIndexer.setMetadataManager(this);
}
- public void init(ServiceContext context, Boolean force) throws Exception {
+ /**
+ * Setup using app handler service context.
+ *
+ * @param appHandlerServiceContext
+ * @throws Exception
+ */
+ public void init(ServiceContext appHandlerServiceContext) throws Exception {
try {
- harvestInfoProvider = context.getBean(HarvestInfoProvider.class);
+ harvestInfoProvider = appHandlerServiceContext.getBean(HarvestInfoProvider.class);
} catch (Exception e) {
// If it doesn't exist, that's fine
}
@@ -170,6 +176,10 @@ public void init(ServiceContext context, Boolean force) throws Exception {
searchManager.init(false, java.util.Optional.empty());
}
+ @Override
+ public void destroy() throws Exception {
+ }
+
/**
* Refresh index if needed. Can also be called after GeoNetwork startup in
* order to rebuild the lucene index
@@ -240,7 +250,7 @@ public void synchronizeDbWithIndex(ServiceContext context, Boolean force, Boolea
context.getBean(DataManager.class),
integerList).process(false);
} else {
- metadataIndexer.batchIndexInThreadPool(context, toIndex);
+ metadataIndexer.batchIndexInThreadPool(toIndex);
}
}
diff --git a/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataUtils.java b/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataUtils.java
index fd998715326..f281fa86fdb 100644
--- a/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataUtils.java
+++ b/core/src/main/java/org/fao/geonet/kernel/datamanager/base/BaseMetadataUtils.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2011 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -68,7 +68,11 @@ public class BaseMetadataUtils implements IMetadataUtils {
@Autowired
private MetadataRepository metadataRepository;
- // FIXME Remove when get rid of Jeeves
+ /**
+ * Shared application handler service context.
+ *
+ * Used by {@link #getServiceContext()} if current service context unavailable.
+ */
private ServiceContext servContext;
@Autowired
protected IMetadataSchemaUtils metadataSchemaUtils;
@@ -103,8 +107,8 @@ public void setMetadataManager(IMetadataManager metadataManager) {
this.metadataManager = metadataManager;
}
- public void init(ServiceContext context, Boolean force) throws Exception {
- servContext = context;
+ public void init(ServiceContext appHandlerContext) throws Exception {
+ servContext = appHandlerContext;
stylePath = dataDirectory.resolveWebResource(Geonet.Path.STYLESHEETS);
}
@@ -116,6 +120,10 @@ public void init() {
this.metadataIndexer.setMetadataUtils(this);
}
+ @Override
+ public void destroy() throws Exception {
+ servContext = null;
+ }
/**
* @param id
* @return
diff --git a/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataIndexer.java b/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataIndexer.java
index 9def9ac9f4e..3da495315a9 100644
--- a/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataIndexer.java
+++ b/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataIndexer.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2011 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -39,6 +39,9 @@
import java.util.HashMap;
import java.util.Map;
+/**
+ * MetadataIndexer for indexing draft content in a background executor (see super class for details).
+ */
public class DraftMetadataIndexer extends BaseMetadataIndexer implements IMetadataIndexer {
@Autowired
@@ -48,8 +51,8 @@ public class DraftMetadataIndexer extends BaseMetadataIndexer implements IMetada
EsSearchManager searchManager;
@Override
- public void init(ServiceContext context, Boolean force) throws Exception {
- super.init(context, force);
+ public void init(ServiceContext context) throws Exception {
+ super.init(context);
metadataDraftRepository = context.getBean(MetadataDraftRepository.class);
}
diff --git a/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataManager.java b/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataManager.java
index 9872a5476bf..6256b0d31a8 100644
--- a/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataManager.java
+++ b/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataManager.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2011 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -59,9 +59,9 @@ public void init() {
super.init();
}
- public void init(ServiceContext context, Boolean force) throws Exception {
+ public void init(ServiceContext context) throws Exception {
metadataDraftRepository = context.getBean(MetadataDraftRepository.class);
- super.init(context, force);
+ super.init(context);
}
diff --git a/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataUtils.java b/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataUtils.java
index 957cf2a48c6..06156f2be46 100644
--- a/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataUtils.java
+++ b/core/src/main/java/org/fao/geonet/kernel/datamanager/draft/DraftMetadataUtils.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2011 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -88,9 +88,8 @@ public class DraftMetadataUtils extends BaseMetadataUtils {
private ServiceContext context;
- public void init(ServiceContext context, Boolean force) throws Exception {
- this.context = context;
- super.init(context, force);
+ public void init(ServiceContext appHandlerContext) throws Exception {
+ super.init(appHandlerContext);
}
@Override
@@ -249,7 +248,7 @@ public boolean existsMetadataUuid(String uuid) throws Exception {
public AbstractMetadata findOneByUuid(String uuid) {
AbstractMetadata md = super.findOneByUuid(uuid);
try {
- if (md != null && am.canEdit(context, Integer.toString(md.getId()))) {
+ if (md != null && am.canEdit(getServiceContext(), Integer.toString(md.getId()))) {
AbstractMetadata tmp = metadataDraftRepository.findOneByUuid(uuid);
if (tmp != null) {
md = tmp;
@@ -560,8 +559,8 @@ protected String createDraft(ServiceContext context, String templateId, String g
// --- use StatusActionsFactory and StatusActions class to
// --- change status and carry out behaviours for status changes
- StatusActionsFactory saf = context.getBean(StatusActionsFactory.class);
- StatusActions sa = saf.createStatusActions(context);
+ StatusActionsFactory statusActionsFactory = context.getBean(StatusActionsFactory.class);
+ StatusActions statusActions = statusActionsFactory.createStatusActions(context);
int author = context.getUserSession().getUserIdAsInt();
Integer status = Integer.valueOf(StatusValue.Status.DRAFT);
@@ -580,7 +579,7 @@ protected String createDraft(ServiceContext context, String templateId, String g
List listOfStatusChange = new ArrayList<>(1);
listOfStatusChange.add(metadataStatus);
- sa.onStatusChange(listOfStatusChange);
+ statusActions.onStatusChange(listOfStatusChange);
}
}
@@ -594,7 +593,7 @@ protected String createDraft(ServiceContext context, String templateId, String g
@Override
public void cloneFiles(AbstractMetadata original, AbstractMetadata dest) {
try {
- StoreUtils.copyDataDir(context, original.getUuid(), dest.getUuid(), false);
+ StoreUtils.copyDataDir(getServiceContext(), original.getUuid(), dest.getUuid(), false);
cloneStoreFileUploadRequests(original, dest);
} catch (Exception ex) {
@@ -614,7 +613,7 @@ public void replaceFiles(AbstractMetadata original, AbstractMetadata dest) {
oldApproved=false;
newApproved=true;
}
- StoreUtils.replaceDataDir(context, original.getUuid(), dest.getUuid(), oldApproved, newApproved);
+ StoreUtils.replaceDataDir(getServiceContext(), original.getUuid(), dest.getUuid(), oldApproved, newApproved);
cloneStoreFileUploadRequests(original, dest);
} catch (Exception ex) {
@@ -653,7 +652,7 @@ public void cancelEditingSession(ServiceContext context, String id) throws Excep
* Stores a file upload request in the MetadataFileUploads table.
*/
private void cloneStoreFileUploadRequests(AbstractMetadata original, AbstractMetadata copy) {
- MetadataFileUploadRepository repo = context.getBean(MetadataFileUploadRepository.class);
+ MetadataFileUploadRepository repo = getServiceContext().getBean(MetadataFileUploadRepository.class);
repo.deleteAll(MetadataFileUploadSpecs.hasMetadataId(copy.getId()));
diff --git a/core/src/main/java/org/fao/geonet/kernel/metadata/DefaultStatusActions.java b/core/src/main/java/org/fao/geonet/kernel/metadata/DefaultStatusActions.java
index 2f1b550e638..fcb6e782049 100644
--- a/core/src/main/java/org/fao/geonet/kernel/metadata/DefaultStatusActions.java
+++ b/core/src/main/java/org/fao/geonet/kernel/metadata/DefaultStatusActions.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2011 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -72,6 +72,8 @@
public class DefaultStatusActions implements StatusActions {
public static final Pattern metadataLuceneField = Pattern.compile("\\{\\{index:([^\\}]+)\\}\\}");
+
+ /** Externally managed service context */
protected ServiceContext context;
protected String language;
protected DataManager dm;
@@ -152,8 +154,8 @@ public void onEdit(int id, boolean minorEdit) throws Exception {
/**
* Called when a record status is added.
*
- * @param listOfStatus
- * @return
+ * @param listOfStatus List of status to update
+ * @return Ids of unchanged metadata records
* @throws Exception
*/
public Set onStatusChange(List listOfStatus) throws Exception {
@@ -195,18 +197,39 @@ public Set onStatusChange(List listOfStatus) throws Exc
status.getMetadataId(), status.getStatusValue().getId(), e.getMessage()));
}
- //Throw events
- Log.trace(Geonet.DATA_MANAGER, "Throw workflow events.");
+ // Issue events
+ Log.trace(Geonet.DATA_MANAGER, "Issue workflow events.");
+
+ List unsuccessful = new ArrayList<>();
+ Throwable statusChangeFailure = null;
for (Integer mid : listOfId) {
if (!unchanged.contains(mid)) {
- Log.debug(Geonet.DATA_MANAGER, " > Status changed for record (" + mid + ") to status " + status);
- context.getApplicationContext().publishEvent(new MetadataStatusChanged(
- metadataUtils.findOne(Integer.valueOf(mid)),
- status.getStatusValue(), status.getChangeMessage(),
- status.getUserId()));
+ try {
+ Log.debug(Geonet.DATA_MANAGER, " > Status changed for record (" + mid + ") to status " + status);
+ context.getApplicationContext().publishEvent(new MetadataStatusChanged(
+ metadataUtils.findOne(Integer.valueOf(mid)),
+ status.getStatusValue(), status.getChangeMessage(),
+ status.getUserId()));
+ } catch (Exception error) {
+ Log.error(Geonet.DATA_MANAGER,
+ String.format("Failed to update metadata %s to status %s. Error is: %s",
+ mid, status.getStatusValue(), error.getMessage()),
+ error
+ );
+ unsuccessful.add(String.valueOf(mid));
+ if( statusChangeFailure == null ){
+ statusChangeFailure = error;
+ }
+ }
}
}
-
+ if (!unsuccessful.isEmpty()){
+ throw new Exception(
+ "Unable to change status for metadata records: "+
+ String.join(",", unsuccessful),
+ statusChangeFailure
+ );
+ }
}
return unchanged;
diff --git a/core/src/main/java/org/fao/geonet/kernel/metadata/StatusActions.java b/core/src/main/java/org/fao/geonet/kernel/metadata/StatusActions.java
index 21655585738..f3e2395041a 100644
--- a/core/src/main/java/org/fao/geonet/kernel/metadata/StatusActions.java
+++ b/core/src/main/java/org/fao/geonet/kernel/metadata/StatusActions.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2011 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -31,12 +31,32 @@
import org.fao.geonet.domain.ISODate;
import org.fao.geonet.domain.MetadataStatus;
+/**
+ * Facade performing actions with record status.
+ */
public interface StatusActions {
- public void init(ServiceContext context) throws Exception;
-
- public void onEdit(int id, boolean minorEdit) throws Exception;
-
- public Set onStatusChange(List status) throws Exception;
+ /**
+ * Setup using provided externally managed service context.
+ *
+ * @param context Externally managed service context.
+ */
+ void init(ServiceContext context) throws Exception;
+
+ /**
+ * Called when a record is edited to set/reset status.
+ *
+ * @param id The metadata id that has been edited.
+ * @param minorEdit If true then the edit was a minor edit.
+ */
+ void onEdit(int id, boolean minorEdit) throws Exception;
+
+ /**
+ * Called when a record status is added.
+ *
+ * @param statusList List of status to update
+ * @return Ids of unchanged metadata records
+ */
+ Set onStatusChange(List statusList) throws Exception;
}
diff --git a/core/src/main/java/org/fao/geonet/kernel/metadata/StatusActionsFactory.java b/core/src/main/java/org/fao/geonet/kernel/metadata/StatusActionsFactory.java
index 3d9222958ac..4fcb52f7914 100644
--- a/core/src/main/java/org/fao/geonet/kernel/metadata/StatusActionsFactory.java
+++ b/core/src/main/java/org/fao/geonet/kernel/metadata/StatusActionsFactory.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2011 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -30,29 +30,39 @@
import java.lang.reflect.Constructor;
+/**
+ * Handle creation of {@link StatusActions} instances.
+ */
public class StatusActionsFactory {
Class statusRules;
+ /**
+ * StatusAction implementation configured for creation.
+ */
private String className;
private static final String DEFAULT_STATUS_ACTION_CLASS = "org.fao.geonet.kernel.metadata.DefaultStatusActions";
/**
- * Constructor.
- *
+ * Setup factory with default StatusActions implementation.
*/
public StatusActionsFactory() {
new StatusActionsFactory(DEFAULT_STATUS_ACTION_CLASS);
}
+
+ /**
+ * Setup factory to use named StatusActions implementation.
+ * @param className StatusActions implementation
+ */
public StatusActionsFactory(String className) {
this.className = className;
try {
this.statusRules = (Class) Class.forName(this.className);
} catch (ClassNotFoundException e) {
- Log.error(Geonet.DATA_MANAGER, String.format(
+ Log.warning(Geonet.DATA_MANAGER, String.format(
"Class name '%s' is not found. You MUST use a valid class name loaded in the classpath. " +
"The default status action class is used (ie. %s)",
- this.className
+ this.className, DEFAULT_STATUS_ACTION_CLASS
));
try {
this.statusRules = (Class) Class.forName(DEFAULT_STATUS_ACTION_CLASS);
@@ -63,7 +73,6 @@ public StatusActionsFactory(String className) {
));
}
}
-
}
/**
@@ -72,10 +81,11 @@ public StatusActionsFactory(String className) {
* @param context ServiceContext from Jeeves
*/
public StatusActions createStatusActions(ServiceContext context) throws Exception {
- Constructor ct = this.statusRules.getConstructor();
- StatusActions sa = ct.newInstance();
- sa.init(context);
- return sa;
+ Constructor constructor = this.statusRules.getConstructor();
+ StatusActions statusAction = constructor.newInstance();
+ statusAction.init(context);
+
+ return statusAction;
}
public String getClassName() {
diff --git a/core/src/main/java/org/fao/geonet/kernel/search/index/IndexingTask.java b/core/src/main/java/org/fao/geonet/kernel/search/index/IndexingTask.java
index dfb4eb305ec..ede87802b8a 100644
--- a/core/src/main/java/org/fao/geonet/kernel/search/index/IndexingTask.java
+++ b/core/src/main/java/org/fao/geonet/kernel/search/index/IndexingTask.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2014 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -83,14 +83,15 @@ private void indexRecords() {
@Override
protected void executeInternal(JobExecutionContext jobContext) throws JobExecutionException {
- ServiceContext serviceContext = serviceManager.createServiceContext("indexing", applicationContext);
- serviceContext.setLanguage("eng");
- serviceContext.setAsThreadLocal();
+ try (ServiceContext serviceContext = serviceManager.createServiceContext("indexing", applicationContext)) {
+ serviceContext.setLanguage("eng");
+ serviceContext.setAsThreadLocal();
- if (Log.isDebugEnabled(Geonet.INDEX_ENGINE)) {
- Log.debug(Geonet.INDEX_ENGINE, "Indexing task / Start at: "
- + new Date() + ". Checking if any records need to be indexed ...");
+ if (Log.isDebugEnabled(Geonet.INDEX_ENGINE)) {
+ Log.debug(Geonet.INDEX_ENGINE, "Indexing task / Start at: "
+ + new Date() + ". Checking if any records need to be indexed ...");
+ }
+ indexRecords();
}
- indexRecords();
}
}
diff --git a/core/src/main/java/org/fao/geonet/kernel/thumbnail/ThumbnailMaker.java b/core/src/main/java/org/fao/geonet/kernel/thumbnail/ThumbnailMaker.java
index 3ac0fa272d7..a80a7e533ff 100644
--- a/core/src/main/java/org/fao/geonet/kernel/thumbnail/ThumbnailMaker.java
+++ b/core/src/main/java/org/fao/geonet/kernel/thumbnail/ThumbnailMaker.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2014 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -110,6 +110,11 @@ public static BufferedImage rotate(BufferedImage image, double angle) {
return result;
}
+ /**
+ * Setup for use based on service context configuration.
+ *
+ * @param context AppService context used to look up configuration details
+ */
public void init(ServiceContext context) {
configFilePath = context.getAppPath() + File.separator + CONFIG_FILE;
initMapPrinter();
diff --git a/core/src/main/java/org/fao/geonet/lib/NetLib.java b/core/src/main/java/org/fao/geonet/lib/NetLib.java
index c47e52e34d6..15feb82f0f0 100644
--- a/core/src/main/java/org/fao/geonet/lib/NetLib.java
+++ b/core/src/main/java/org/fao/geonet/lib/NetLib.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2007 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -97,6 +97,14 @@ public void setupProxy(SettingManager sm, XmlRequest req) {
//---------------------------------------------------------------------------
+ /**
+ * Setup proxy for http client
+ *
+ * @param context Service context used to lookup settings.
+ * @param client Http implementation
+ * @param requestHost
+ * @return
+ */
public CredentialsProvider setupProxy(ServiceContext context, HttpClientBuilder client, String requestHost) {
GeonetContext gc = (GeonetContext) context.getHandlerContext(Geonet.CONTEXT_NAME);
SettingManager sm = gc.getBean(SettingManager.class);
@@ -106,6 +114,9 @@ public CredentialsProvider setupProxy(ServiceContext context, HttpClientBuilder
/**
* Setup proxy for http client
+ * @param sm settings
+ * @param client Http implementation
+ * @param requestHost
*/
public CredentialsProvider setupProxy(SettingManager sm, HttpClientBuilder client, String requestHost) {
boolean enabled = sm.getValueAsBool(Settings.SYSTEM_PROXY_USE, false);
diff --git a/core/src/main/java/org/fao/geonet/resources/Resources.java b/core/src/main/java/org/fao/geonet/resources/Resources.java
index ed9cc4f2cba..02b535e2daa 100644
--- a/core/src/main/java/org/fao/geonet/resources/Resources.java
+++ b/core/src/main/java/org/fao/geonet/resources/Resources.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2001-2016 Food and Agriculture Organization of the
+ * Copyright (C) 2001-2021 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
@@ -283,18 +283,20 @@ public void copyLogo(ServiceContext context, String icon,
try {
Path srcPath = locateResource(locateResourcesDir(context), servletContext, appDir, icon);
String extension = Files.getFileExtension(srcPath.getFileName().toString());
- try(ResourceHolder src = getImage(context, srcPath.getFileName().toString(), srcPath.getParent());
- ResourceHolder des = getWritableImage(context, destName + "." + extension,
- logosDir)) {
- if (src != null) {
- java.nio.file.Files.copy(src.getPath(), des.getPath(), REPLACE_EXISTING, NOFOLLOW_LINKS);
- } else {
- des.abort();
+ try(ResourceHolder src = getImage(context, srcPath.getFileName().toString(), srcPath.getParent())){
+ if( src == null) {
+ throw new IOException("Resource not found: "+srcPath.toString());
+ }
+ try (ResourceHolder des = getWritableImage(context, destName + "." + extension, logosDir)) {
+ if (src != null) {
+ java.nio.file.Files.copy(src.getPath(), des.getPath(), REPLACE_EXISTING, NOFOLLOW_LINKS);
+ } else {
+ des.abort();
+ }
}
}
} catch (IOException e) {
// --- we ignore exceptions here, just log them
-
context.warning("Cannot copy icon -> " + e.getMessage());
context.warning(" (C) Source : " + icon);
context.warning(" (C) Destin : " + logosDir);
diff --git a/core/src/main/java/org/fao/geonet/services/util/z3950/provider/GN/GNResultSet.java b/core/src/main/java/org/fao/geonet/services/util/z3950/provider/GN/GNResultSet.java
index cfa1c7637a9..1a41129429a 100644
--- a/core/src/main/java/org/fao/geonet/services/util/z3950/provider/GN/GNResultSet.java
+++ b/core/src/main/java/org/fao/geonet/services/util/z3950/provider/GN/GNResultSet.java
@@ -49,7 +49,8 @@
public class GNResultSet extends AbstractIRResultSet implements IRResultSet {
private GNXMLQuery query;
- private ServiceContext srvxtx;
+ /** Service context assumed to be shared */
+ private ServiceContext appHandlerContext;
private int status;
private int fragmentcount;
@@ -61,7 +62,7 @@ public GNResultSet(GNXMLQuery query, Object userInfo, Observer[] observers,
ServiceContext srvctx) throws Exception {
super(observers);
this.query = query;
- this.srvxtx = srvctx;
+ this.appHandlerContext = srvctx;
throw new NotImplementedException("Z39.50 not implemented in ES");
// try {
//
@@ -78,6 +79,16 @@ public GNResultSet(GNXMLQuery query, Object userInfo, Observer[] observers,
// }
}
+ /**
+ * Access the current ServiceContext if available.
+ *
+ * @return current service context if available, or app handler context fallback.
+ */
+ protected ServiceContext getServiceContext(){
+ ServiceContext serviceContext = ServiceContext.get();
+ return serviceContext != null ? serviceContext : appHandlerContext;
+ }
+
public int evaluate(int timeout) {
try {
if (Log.isDebugEnabled(Geonet.SRU))
@@ -96,7 +107,7 @@ public int evaluate(int timeout) {
// perform the search and save search results
- metasearcher.search(this.srvxtx, request, config);
+ metasearcher.search(getServiceContext(), request, config);
// System.out.println("summary:\n" + Xml.getString(s.getSummary()));
// // DEBUG
@@ -136,7 +147,7 @@ public InformationFragment[] getFragment(int startingFragment, int count,
Log.debug(Geonet.SRU, "Search request:\n"
+ Xml.getString(request));
// get result set
- Element result = this.metasearcher.present(this.srvxtx, request,
+ Element result = this.metasearcher.present(getServiceContext(), request,
config);
if (Log.isDebugEnabled(Geonet.SRU))
diff --git a/core/src/main/java/org/fao/geonet/services/util/z3950/provider/GN/GNSearchable.java b/core/src/main/java/org/fao/geonet/services/util/z3950/provider/GN/GNSearchable.java
index 18c53e711bd..016f1d80a6f 100644
--- a/core/src/main/java/org/fao/geonet/services/util/z3950/provider/GN/GNSearchable.java
+++ b/core/src/main/java/org/fao/geonet/services/util/z3950/provider/GN/GNSearchable.java
@@ -34,9 +34,9 @@
import java.util.Map;
import java.util.Observer;
-//import org.fao.geonet.services.util.z3950.GNSearchTask;
-
/**
+ * Present GeoNetwork search results as JZkit Searchable.
+ *
* @author 'Timo Proescholdt ' interface between JZkit and GN. not currently
* used
*/
diff --git a/core/src/test/java/org/fao/geonet/AbstractCoreIntegrationTest.java b/core/src/test/java/org/fao/geonet/AbstractCoreIntegrationTest.java
index c32bc8a1825..0581e896bed 100644
--- a/core/src/test/java/org/fao/geonet/AbstractCoreIntegrationTest.java
+++ b/core/src/test/java/org/fao/geonet/AbstractCoreIntegrationTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2001-2016 Food and Agriculture Organization of the
+ * Copyright (C) 2001-2021 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
@@ -186,6 +186,15 @@ protected String getGeonetworkNodeId() {
/**
* Create a Service context without a user session but otherwise ready to use.
+ *
+ * This method assigns the created service context to the current thread, you are responsible for managing cleanup.
+ *
* @author Jesse
*/
public enum Profile {
diff --git a/domain/src/main/java/org/fao/geonet/domain/StatusValue.java b/domain/src/main/java/org/fao/geonet/domain/StatusValue.java
index 26d5bf4ccc9..8f9a437d017 100644
--- a/domain/src/main/java/org/fao/geonet/domain/StatusValue.java
+++ b/domain/src/main/java/org/fao/geonet/domain/StatusValue.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2001-2016 Food and Agriculture Organization of the
+ * Copyright (C) 2001-2021 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
@@ -239,4 +239,13 @@ public static final class Events {
public static final String RECORDIMPORTED = "62";
public static final String RECORDRESTORED = "63";
}
+
+ @Override
+ public String toString() {
+ final StringBuffer sb = new StringBuffer("StatusValue{");
+ sb.append("_id=").append(_id);
+ sb.append(", _name='").append(_name).append('\'');
+ sb.append('}');
+ return sb.toString();
+ }
}
diff --git a/harvesters/src/main/java/org/fao/geonet/component/harvester/csw/Harvest.java b/harvesters/src/main/java/org/fao/geonet/component/harvester/csw/Harvest.java
index 24f53ee8a62..37722db1fbd 100644
--- a/harvesters/src/main/java/org/fao/geonet/component/harvester/csw/Harvest.java
+++ b/harvesters/src/main/java/org/fao/geonet/component/harvester/csw/Harvest.java
@@ -1,5 +1,5 @@
//=============================================================================
-//=== Copyright (C) 2001-2007 Food and Agriculture Organization of the
+//=== Copyright (C) 2001-2021 Food and Agriculture Organization of the
//=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
//=== and United Nations Environment Programme (UNEP)
//===
@@ -635,7 +635,7 @@ private Element createAcknowledgeResponse(Element asyncRequest) {
}
/**
- * Runs the harvester. In synchronous mode, waits for it to finish.
+ * Runs CSW harvester in synchronous mode, waiting for it to finish.
*
* @param harvester - the harvester
* @param context - everywhere in GN !
@@ -813,6 +813,7 @@ private class AsyncHarvestResponse implements RunnableFuture