Uploaded image for project: 'PUBLIC - Liferay Portal Community Edition'
  1. PUBLIC - Liferay Portal Community Edition
  2. LPS-75212

Spring properties in JARs aren't added to classpath

    Details

    • Story Points:
      3
    • Fix Priority:
      3

      Description

      Issue Overview

      For Spring MVC portlets whose JARs (in WEB-INF/lib) contain properties files, such as these ...

      • WEB-INF/src/META-INF/spring.handlers
      • WEB-INF/src/META-INF/spring.schemas
      • WEB-INF/src/META-INF/spring.tooling

      ... the ones from the last JAR listed are the only properties files put in the classpath.

      As a result, properties in files in the other JARs aren't available in the classpath.

      Steps to Reproduce

      1. Update gradle/apps/service-builder/basic/basic-service/build.gradle to include a compile-time dependency on cxf-core
        compile group: "org.apache.cxf", name: "cxf-core", version: "3.2.5"
      2. Update gradle/apps/service-builder/basic/basic-service/bnd.bnd to embed the .jar, but make all of its imports optional
        Import-Package: *;resolution:="optional"
        -includeresource: lib/cxf-core.jar=cxf-core-[0-9]*.jar;lib:=true
      3. Unplug your internet connection, to enforce using the spring.schemas file for loading schemas locally
      4. Deploy basic-api and the updated basic-service

      Expected behavior is that Liferay doesn't have any trouble loading the Spring configuration files. Actual behavior is that the spring.schemas provided by the cxf-core.jar overrides the one that will be used by the portal Spring extender, because Liferay is using only the spring.schemas file from cxf-core.jar and ignoring all others, including the one that should be visible via the portal Spring extender. As a result, you get the following exceptions:

      Caused by: org.xml.sax.SAXParseException; lineNumber: 9; columnNumber: 2; cvc-elt.1.a: Cannot find the declaration of element 'beans'.
      	at org.apache.xerces.util.ErrorHandlerWrapper.createSAXParseException(Unknown Source)
      	at org.apache.xerces.util.ErrorHandlerWrapper.error(Unknown Source)
      	at org.apache.xerces.impl.XMLErrorReporter.reportError(Unknown Source)
      	at org.apache.xerces.impl.XMLErrorReporter.reportError(Unknown Source)
      	at org.apache.xerces.impl.XMLErrorReporter.reportError(Unknown Source)
      

      Workaround 1

      Run a shell script, such as the one below, to combine the properties files from the JARs and add the combined properties files to WEB-INF/src. Here's the script:

      cat /dev/null > docroot/WEB-INF/src/META-INF/spring.handlers
      cat /dev/null > docroot/WEB-INF/src/META-INF/spring.schemas
      cat /dev/null > docroot/WEB-INF/src/META-INF/spring.tooling
      
      for jar in $(find docroot/WEB-INF/lib/ -name '*.jar'); do
      	for file in $(unzip -l $jar | grep -F META-INF/spring. | awk '{ print $4 }'); do
      		if [ "META-INF/spring.tld" != "$file" ]; then
      			unzip -p $jar $file >> docroot/WEB-INF/src/$file
      			echo >> docroot/WEB-INF/src/$file
      		fi
      	done
      done
      

      Workaround 2

      If this is a module project, add the following to each build.gradle that has any dependencies on Spring-related .jars in order to have it scan for spring.handlers, spring.schemas, and spring.tooling at build time to re-generate the file.

      import java.util.jar.JarEntry
      import java.util.jar.JarFile
      
      task writeSpringSchemas
      
      processResources {
      	finalizedBy writeSpringSchemas
      }
      
      writeSpringSchemas {
      	doLast {
      		List<String> entryNames = ["META-INF/spring.handlers", "META-INF/spring.schemas", "META-INF/spring.tooling"]
      
      		Map<String, Properties> springProperties = new HashMap<String, Properties>()
      
      		for (String entryName in entryNames) {
      			springProperties.put(entryName, new Properties())
      		}
      
      		for (File dependencyFile in configurations.compile + configurations.compileOnly) {
      			JarFile jarFile = new JarFile(dependencyFile)
      
      			jarFile.withCloseable {
      				for (String entryName in entryNames) {
      					JarEntry jarEntry = jarFile.getEntry(entryName)
      
      					if (jarEntry) {
      						InputStream inputStream = jarFile.getInputStream(jarEntry)
      
      						Properties properties = new Properties()
      
      						inputStream.withStream {
      							properties.load it
      						}
      
      						println '###' + properties
      
      						springProperties.get(entryName).putAll properties
      					}
      				}
      			}
      		}
      
      		for (String entryName in entryNames) {
      			File springFile = new File(processResources.destinationDir, entryName)
      
      			springFile.withOutputStream {
      				springProperties.get(entryName).store it, null
      			}
      		}
      	}
      }
      

      If the module is a service builder service, also add the following dependency.

      compileOnly group: "com.liferay", name: "com.liferay.portal.spring.extender", version: "2.0.0"
      

        Attachments

        1. joyce.api.jar
          23 kB
        2. joyce.service.jar
          1.25 MB
        3. joyce.sp8.api.jar
          24 kB
        4. joyce.sp8.service.jar
          1.25 MB

          Activity

            People

            • Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

              • Created:
                Updated:
                Days since last comment:
                1 year, 7 weeks, 5 days ago

                Packages

                Version Package