Expand my Community achievements bar.

Don’t miss the AEM Skill Exchange in SF on Nov 14—hear from industry leaders, learn best practices, and enhance your AEM strategy with practical tips.

OSGI config edited in repo not applying during runtime

Avatar

Level 4

Hello, I have a servlet that is modifying an osgi config in the repo, which is working as intended and reflecting correctly in crx/de, but the new config is not being reflected in the console until I do a restart, and it isn't taking precedence over the previous config either when I hit the page that it applies to. Is there a way to tell the console to check for this new config? Or something missing in my code? Any help is appreciated!

 

Servlet code:

 

@component(service= Servlet.class,
		property={
				Constants.SERVICE_DESCRIPTION + "=Changes the home to maintenance page.",
				"sling.servlet.methods=" + HttpConstants.METHOD_GET,
				"sling.servlet.resourceTypes="+ "sling/servlet/default",
				"sling.servlet.paths=/bin/my/portal-maintenance"
		})
public class PortalMaintenanceServlet extends SlingAllMethodsServlet {

	@Reference
	private ResourceResolverFactory resourceResolverFactory;

	@Reference
	PortalConfigHelper portalConfigHelper;

	@Reference
	private ConfigurationAdmin configAdmin;

	private static Logger logger = LoggerFactory.getLogger(PortalMaintenanceServlet.class);

	/**
	 * Obtains the necessary parameters
	 * 
	 * @Param request the request
	 * @Param response the response
	 * @throws IOException
	 */

	protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException{

		logger.info("Starting servlet");
		ResourceResolver resolver = null;
		try {
			Map<String, Object> param = new HashMap<>();
			param.put(ResourceResolverFactory.USER, MyConstants.MY_SYSTEM_USER);
			resolver = resourceResolverFactory.getServiceResourceResolver(param);
			Map<String, String> reportParams = new HashMap<>();
			reportParams.put("publishInstances", StringUtils.join(portalConfigHelper.getPublishDetails(), ';'));
			String[] publishInstances = reportParams.get("publishInstances").split(";");
			try {
				for(String each : publishInstances) {
					Jcr2davRepositoryFactory repoFactory = new Jcr2davRepositoryFactory();
					String[] publishDetails = each.split("--");
					String userName = publishDetails[1].split(":")[0];
					String password = publishDetails[1].split(":")[1];
					String urlString = String.format("http://%s/crx/server", publishDetails[0]);
					Map<String, String> params = new HashMap<String, String>();
					params.put("org.apache.jackrabbit.repository.uri", urlString);
					logger.info("trying to connect to repo at: " + publishDetails[0]);
					Repository repository = repoFactory.getRepository(params);
					logger.info("logging in to repo");
					Session session = repository.login(new SimpleCredentials(userName,password.toCharArray()),"crx.default");

					Node root = null;
					Node contentNode = null;
					Properties properties = new Properties();
					boolean success = false;

					try {
						root = session.getRootNode();
						contentNode = root.getNode("apps/system/config/com.my.aem.dam.core.configs.PortalConfigHelperImpl.config/jcr:content");
						if(contentNode == null){
							logger.info("unable to get node");
						}
						logger.info("getting input stream");
						InputStream content = contentNode.getProperty("jcr:data").getBinary().getStream();
						logger.info("loading content");
						properties.load(content);
						logger.info("setting homepage");
						properties.setProperty("homePageUrl", "/content/my-portal/maintenance-page.html");
						logger.info("Set homepage to " + properties.getProperty("homePageUrl"));
						success = true;
						if(success) {
							logger.info("saving settings");
							ByteArrayOutputStream fileOut = new ByteArrayOutputStream();
							properties.store(fileOut, "Maintenance Page");
							ByteArrayInputStream is = new ByteArrayInputStream(fileOut.toByteArray());
							ValueFactory valueFactory = resolver.adaptTo(Session.class).getValueFactory();
							Binary contentValue = valueFactory.createBinary(is);
							contentNode.setProperty("jcr:data", contentValue);
							contentValue.dispose();
							is.close();
							session.save();
							fileOut.close();
							content.close();
						}

					}
					catch(Exception e) {
						System.out.println("Could not set maintenance page" + e);

					}

				}

			}catch (RepositoryException e){
				logger.error("Check the permission and the settings of the service user for this service.\n", e);
				response.sendError(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR, MyConstants.MY_SERVER_ERROR
						+ "\n" + e.getMessage());

			}
			} catch (LoginException e) {
			logger.error("Error while trying to login.\n", e);
			response.sendError(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR, MyConstants.MY_SERVER_ERROR
					+ "\n" + e.getMessage());

			if(resolver != null)
				resolver.close();
			
		}
		}
	}
14 Replies

Avatar

Community Advisor

Hi @DNest19, I see you have ConfigurationAdmin object but you are not using it. I think that is the way to go. It will simplify your code a lot. I did quick check on my local instance, you could use code like below:

Configuration config = configAdmin.getConfiguration("com.my.aem.dam.core.configs.PortalConfigHelperImpl");
Dictionary dictionary = config.getProperties();
dictionary.put("homePageUrl", "/content/my-portal/maintenance-page.html");
config.update(dictionary);

In general it will get OSGi configuration for given PID, read current configuration and update it with new value. Configuration is updated and applied immediately - changes can be seen in OSGi console as well. Instance restart is not needed.

If I good understand your code, you are accessing repository of some remote instance from the other instance - I think this approach is over complicated. If you will run your code on each instance separately complexity of your code will decrease significantly, and your servlet could look like that.

@Component(service= Servlet.class,
        property={
                Constants.SERVICE_DESCRIPTION + "=Changes the home to maintenance page.",
                "sling.servlet.methods=" + HttpConstants.METHOD_GET,
                "sling.servlet.resourceTypes="+ "sling/servlet/default",
                "sling.servlet.paths=/bin/my/portal-maintenance"
        })
public class PortalMaintenanceServlet extends SlingAllMethodsServlet {

    @Reference
    private ConfigurationAdmin configAdmin;

    @Override
    protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws ServletException, IOException {
        Configuration config = configAdmin
                .getConfiguration("com.my.aem.dam.core.configs.PortalConfigHelperImpl");
        Dictionary dictionary = config.getProperties();
        dictionary.put("homePageUrl", "/content/my-portal/maintenance-page.html");
        config.update(dictionary);
    }
} 

 

Avatar

Level 4

Yeah that configadmin was leftover from an older version of the code. The reason I'm accessing the repos remotely is that I'm trying to make a button for a tools page on the author instance that changes these settings on two publisher instances. The goal is to give someone other than myself the ability to do this without having to access the system console or crx/de.

Avatar

Community Advisor

Ok, I got the case, but I still think that even if you would like to control it from author, then you should go in different direction, e.g.

  • you can expose an endpoint (servlet on each publish server, and by clicking on the button in UI simple sent a request - this will run the logic that will modify OSGi config - of course you can add some additional security layer related to that servlet)
  • or you could use replication, and trigger some action on publish that will be related to that.

Using OSGi API - for config modification in my opinion is the right approach, of course how this will be triggered is a different story.

Avatar

Level 4

I am avoiding replication because some of the settings might differ between the author and publisher, but exposing the endpoint sounds like a good alternative! Can you point me to any resources on this method? If I can't get this code to work soon I'll try that instead, thank you!

Avatar

Community Advisor

Hi @DNest19, do you have the config stored under /apps/<project_structure>/config? Can you try changing the value there as well and see if the OSGi console is updating the value after making the change?

Jineet

Avatar

Level 4

I tried that and it doesn't reflect in the console either.

Avatar

Level 4

According to that article, my implementation should take effect immediately:

 

Resolution Order at Runtime

Configuration changes made while the system is running trigger a reload with the modified configuration.

Then the following order of precedence applies:

  1. Modifying a configuration in the Web console will take immediate effect as it takes precedence at runtime.
  2. Modifying a configuration in /apps will take immediate effect.
  3. Modifying a configuration in /libs will take immediate effect, unless it is masked by a configuration in /apps.

i am modifying the config in /apps

Avatar

Level 4

I found one issue and it was really simple and dumb lol. For some reason this:

 

properties.setProperty("homePageUrl", "/content/my-portal/maintenance-page.html");

 

wasn't actually getting picked up as a string in the datastream. I added a pair of escaped quotes around the url and now it picks up the new string, but only after I manually hit "save all" in crx/de. Is there a way to programmatically perform that action? I thought session.save() should, but that's not working out.

 

properties.setProperty("homePageUrl", "\"/content/my-portal/maintenance-page.html\"");

 

Avatar

Level 4

Edited my response. I though this had solved the issue, but I still have to hit save all in the repo to get the changes to pick up. Is there a way to perform that action programmatically? I thought session.save() is more or less equivalent, but it doesn't seem to be. 

Avatar

Community Advisor

@DNest19, this line could be the root cause of your current problem:

ValueFactory valueFactory = resolver.adaptTo(Session.class).getValueFactory();

You are using local resolver and local session instead of remote session that you have created here:

Session session = repository.login(new SimpleCredentials(userName,password.toCharArray()),"crx.default");

So you could try something like this:

ValueFactory valueFactory = resolver.adaptTo(Session.class).getValueFactory();
// replace with ValueFactory valueFactory = session.getValueFactory();

If this will work then you will not need the resolver and resolver related code at all.

Avatar

Level 4

I tried this fix and it's still not getting picked up until I hit save all manually.

Avatar

Community Advisor

Hi @DNest19, so I think I've found out what is the problem. Below are the details and modified version of your code.

I've identified following issues:

  • you are using incorrect session, local instead of remote
    ValueFactory valueFactory = resolver.adaptTo(Session.class).getValueFactory();
    
    // replace with
    ValueFactory valueFactory = session.getValueFactory();
  • after modifying jcr:content node by setting new binary value via jcr:data property, you did not updated jcr:lastModified property
    contentNode.setProperty("jcr:data", contentValue);
    // missing jcr:lastModified update contentNode.setProperty("jcr:lastModified", Calendar.getInstance());
  • you are saving changes after dispose
    contentValue.dispose();
    is.close();
    session.save();
    ileOut.close();
    content.close();
    
    // replaced with
    session.save();
    contentValue.dispose();
    is.close();
    ileOut.close();
    content.close();

I have applied above changes and I was able to run your code successfully, and get OSGi configuration update without doing additional save on crx/de level. Changes are also visible directly from OSGi console.

Below you can find 2 versions of code:

  1. Original version with above changes:
    @Component(service= Servlet.class,
        property={
    	    Constants.SERVICE_DESCRIPTION + "=Changes the home to maintenance page.",
    		"sling.servlet.methods=" + HttpConstants.METHOD_POST,
    		"sling.servlet.resourceTypes="+ "sling/servlet/default",
    		"sling.servlet.paths=/bin/my/portal-maintenance"
    })
    public class PortalMaintenanceServlet extends SlingAllMethodsServlet {
    
    	@Reference
    	private ResourceResolverFactory resourceResolverFactory;
    
    	@Reference
    	PortalConfigHelper portalConfigHelper;
    
    	@Reference
    	private ConfigurationAdmin configAdmin;
    
    	private static Logger logger = LoggerFactory.getLogger(PortalMaintenanceServlet.class);
    
    	/**
    	 * Obtains the necessary parameters
    	 * 
    	 * @Param request the request
    	 * @Param response the response
    	 * @throws IOException
    	 */
    	protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException{
    
    		logger.info("Starting servlet");
    		ResourceResolver resolver = null;
    		try {
    			Map<String, Object> param = new HashMap<>();
    			param.put(ResourceResolverFactory.USER, MyConstants.MY_SYSTEM_USER);
    			resolver = resourceResolverFactory.getServiceResourceResolver(param);
    			Map<String, String> reportParams = new HashMap<>();
    			reportParams.put("publishInstances", StringUtils.join(portalConfigHelper.getPublishDetails(), ';'));
    			String[] publishInstances = reportParams.get("publishInstances").split(";");
    			try {
    				for(String each : publishInstances) {
    					Jcr2davRepositoryFactory repoFactory = new Jcr2davRepositoryFactory();
    					String[] publishDetails = each.split("--");
    					String userName = publishDetails[1].split(":")[0];
    					String password = publishDetails[1].split(":")[1];
    					String urlString = String.format("http://%s/crx/server", publishDetails[0]);
    					Map<String, String> params = new HashMap<String, String>();
    					params.put("org.apache.jackrabbit.repository.uri", urlString);
    					logger.info("trying to connect to repo at: " + publishDetails[0]);
    					Repository repository = repoFactory.getRepository(params);
    					logger.info("logging in to repo");
    					Session session = repository.login(new SimpleCredentials(userName,password.toCharArray()),"crx.default");
    
    					Node root = null;
    					Node contentNode = null;
    					Properties properties = new Properties();
    					boolean success = false;
    
    					try {
    						root = session.getRootNode();
    						contentNode = root.getNode("apps/system/config/com.my.aem.dam.core.configs.PortalConfigHelperImpl.config/jcr:content");
    						if(contentNode == null){
    							logger.info("unable to get node");
    						}
    						logger.info("getting input stream");
    						InputStream content = contentNode.getProperty("jcr:data").getBinary().getStream();
    						logger.info("loading content");
    						properties.load(content);
    						logger.info("setting homepage");
    						properties.setProperty("homePageUrl", "\"/content/my-portal/maintenance-page.html\"");
    						logger.info("Set homepage to " + properties.getProperty("homePageUrl"));
    						success = true;
    						if(success) {
    							logger.info("saving settings");
    							ByteArrayOutputStream fileOut = new ByteArrayOutputStream();
    							properties.store(fileOut, "Maintenance Page");
    							ByteArrayInputStream is = new ByteArrayInputStream(fileOut.toByteArray());
    							ValueFactory valueFactory = session.getValueFactory();
    							Binary contentValue = valueFactory.createBinary(is);
    							contentNode.setProperty("jcr:data", contentValue);
                                                            contentNode.setProperty("jcr:lastModified", Calendar.getInstance());
                                                            session.save();
    							contentValue.dispose();
    							is.close();
    							fileOut.close();
    							content.close();
    						}
    
    					}
    					catch(Exception e) {
    						System.out.println("Could not set maintenance page" + e);
    
    					}
    
    				}
    
    			} catch (RepositoryException e){
    				logger.error("Check the permission and the settings of the service user for this service.\n", e);
    				response.sendError(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR, MyConstants.MY_SERVER_ERROR
    						+ "\n" + e.getMessage());
    
    			}
    		} catch (LoginException e) {
    			logger.error("Error while trying to login.\n", e);
    			response.sendError(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR, MyConstants.MY_SERVER_ERROR
    					+ "\n" + e.getMessage());
    
    			if(resolver != null)
    				resolver.close();
    			
    		}
    	}
    }
  2. Optimized version with above changes, and without resource resolver related code - as it is not needed:
    @Component(service= Servlet.class,
        property={
            Constants.SERVICE_DESCRIPTION + "=Changes the home to maintenance page.",
            "sling.servlet.methods=" + HttpConstants.METHOD_POST,
            "sling.servlet.resourceTypes="+ "sling/servlet/default",
            "sling.servlet.paths=/bin/my/portal-maintenance"})
    public class PortalMaintenanceServlet extends SlingAllMethodsServlet {
    
        @Reference
        PortalConfigHelper portalConfigHelper;
    
        private static Logger logger = LoggerFactory.getLogger(PortalMaintenanceServlet.class);
    
        /**
         * Obtains the necessary parameters
         *
         * @Param request the request
         * @Param response the response
         * @throws IOException
         */
        protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException{
            logger.info("Starting servlet");
            try {
                Map<String, String> reportParams = new HashMap<>();
                reportParams.put("publishInstances", StringUtils.join(portalConfigHelper.getPublishDetails(), ';'));
                String[] publishInstances = reportParams.get("publishInstances").split(";");
                try {
                    for(String each : publishInstances) {
                        Jcr2davRepositoryFactory repoFactory = new Jcr2davRepositoryFactory();
                        String[] publishDetails = each.split("--");
                        String userName = publishDetails[1].split(":")[0];
                        String password = publishDetails[1].split(":")[1];
                        String urlString = String.format("http://%s/crx/server", publishDetails[0]);
                        Map<String, String> params = new HashMap<String, String>();
                        params.put("org.apache.jackrabbit.repository.uri", urlString);
                        logger.info("trying to connect to repo at: " + publishDetails[0]);
                        Repository repository = repoFactory.getRepository(params);
                        logger.info("logging in to repo");
                        Session session = repository.login(new SimpleCredentials(userName,password.toCharArray()),"crx.default");
    
                        Node root = null;
                        Node contentNode = null;
                        Properties properties = new Properties();
                        boolean success = false;
    
                        try {
                            root = session.getRootNode();
                            contentNode = root.getNode("apps/system/config/com.my.aem.dam.core.configs.PortalConfigHelperImpl.config/jcr:content");
                            if (contentNode == null){
                                logger.info("unable to get node");
                            }
                            logger.info("getting input stream");
                            InputStream content = contentNode.getProperty("jcr:data").getBinary().getStream();
                            logger.info("loading content");
                            properties.load(content);
                            logger.info("setting homepage");
                            properties.setProperty("homePageUrl", "\"/content/my-portal/maintenance-page.html\"");
                            logger.info("Set homepage to " + properties.getProperty("homePageUrl"));
                            success = true;
                            if (success) {
                                logger.info("saving settings");
                                ByteArrayOutputStream fileOut = new ByteArrayOutputStream();
                                properties.store(fileOut, "Maintenance Page");
                                ByteArrayInputStream is = new ByteArrayInputStream(fileOut.toByteArray());
                                ValueFactory valueFactory = session.getValueFactory();
                                Binary contentValue = valueFactory.createBinary(is);
                                contentNode.setProperty("jcr:data", contentValue);
                                contentNode.setProperty("jcr:lastModified", Calendar.getInstance());
                                session.save();
                                contentValue.dispose();
                                is.close();
                                fileOut.close();
                                content.close();
                            }
                        }
                        catch (Exception e) {
                            System.out.println("Could not set maintenance page" + e);
                        }
                    }
                } catch (RepositoryException e){
                    logger.error("Check the permission and the settings of the service user for this service.\n", e);
                    response.sendError(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR, MyConstants.MY_SERVER_ERROR
                            + "\n" + e.getMessage());
                }
            } catch (LoginException e) {
                logger.error("Error while trying to login.\n", e);
                response.sendError(SlingHttpServletResponse.SC_INTERNAL_SERVER_ERROR, MyConstants.MY_SERVER_ERROR
                        + "\n" + e.getMessage());
            }
        }
    }