Skip to content

Instantly share code, notes, and snippets.

@chetanmeh
Created October 5, 2017 19:03
Show Gist options
  • Save chetanmeh/3cf69a690e67f9ceb9c1abcbfadc495b to your computer and use it in GitHub Desktop.
Save chetanmeh/3cf69a690e67f9ceb9c1abcbfadc495b to your computer and use it in GitHub Desktop.
Script to analyze the OSGi metadata specifically Declarative Services and Metatype xml and generates a json report. Which can be diff for changes. See OAK-6741 for usage
import groovy.json.JsonOutput
import groovy.time.TimeCategory
import groovy.transform.Field
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import groovy.json.JsonSlurper
import org.json.JSONObject
@Grab(group='org.json', module='json', version='20140107')
import org.json.XML
rootDir = args ? args[0] : "."
def dir = new File(rootDir)
def out = new File('osgi-metadata.json')
@Field Map ds = [:]
@Field def metadata = [:]
@Field def errors = new File('error.log')
@Field def warnings = [] as HashSet
def combined = [ds: ds, metadata: metadata]
def start = System.currentTimeMillis()
println "Scanning ${dir.absolutePath}"
dir.eachFileRecurse {file ->
if (file.isFile() && file.name.endsWith("jar")) {
println ("Processing $file")
ZipFile zf = new ZipFile(file)
try{
zf.entries().each {ZipEntry ze ->
String path = ze.name
if (path.startsWith('OSGI-INF/') && path.endsWith('.xml')){
String text = zf.getInputStream(ze).text
if (path.startsWith('OSGI-INF/metatype/')){
processMetadata(path, text)
} else {
processDS(path, text)
}
}
}
} finally {
zf.close()
}
}
}
def end = System.currentTimeMillis()
out.text = toPrettyJson(toSortedMap(combined))
//out.text = toPrettyJson(combined)
if (warnings) {
println(">>> Possible errors found")
warnings.each {println it}
println("Refer to ${errors.absolutePath}")
}
println "Combined metadata written to ${out.absolutePath} " +
"in ${TimeCategory.getMillisecond((int)(end-start))}"
@Field def metadataKeysToRemove = ['localization', 'xmlns:metatype']
def processMetadata(String path, String content) {
println " $path"
Map m = toMap(content)
m = m['metatype:MetaData'] ?: m
m.keySet().removeAll(metadataKeysToRemove)
//For now only support one OCD per xml
if (m.'OCD' instanceof List) {
warnings << "Multiple OCD found in $path. Skipping"
return
}
def designate = m.remove('Designate') //Get rid of Designate element as that has changed
if (!designate){
errors << "No 'Designate' found in $path \n$content\n ${toPrettyJson(m)}"
warnings << "No 'Designate' found in $path"
return
}
assert m.'OCD' : "No 'OCD' found in $path \n$content\n ${toPrettyJson(m)}"
m.'OCD'?.remove('id') // id of OCD refers to designate. Remove this also
m.'OCD'?.remove('name')
m.'OCD'?.remove('description')
//Get rid of name and description also as in earlier case they were refered
//from external file
performOperationOnMap(m.'OCD'.'AD'){o -> o.remove('name'); o.remove('description')}
metadata[designate.pid] = m
}
def processDS(String path, String content) {
println " $path"
Map m = toMap(content)
//A DS xml depending on version and format may have following elements
//Note this logic only support 1 component per xml
m = m.'scr:component' ?: m
m = m.'components'?.'scr:component' ?: m
m = m.'component' ?: m
assert m.name : "No 'name' found in $path \n$content\n ${toPrettyJson(m)}"
m.remove('xmlns:scr')
ds[m.name] = m
}
static def performOperationOnMap(def o, Closure c) {
if (!o) return
//If an xml element has single entry
//its converted to map. Otherwise its a list of map
if (o instanceof List) {
o.each {c.call(it)}
} else {
c.call(o)
}
}
static def toMap(String text) {
JSONObject jo = XML.toJSONObject(text)
return new JsonSlurper().parseText(jo.toString())
}
/**
* Converts a map of map to sorted map i.e. where each map entry is sorted by key
* and for all nested map
*/
def toSortedMap(Map m){
TreeMap tm = new TreeMap()
toSortedMap(tm, m)
return tm
}
def toSortedMap(Map dest, Map src) {
//A json value can be map, list or simple type
src.each {k, v ->
if (v instanceof Map) {
Map m = new TreeMap()
toSortedMap(m, v)
dest[k] = m
} else if (v instanceof List){
def l = []
def keySet = [] as HashSet
//Check if its list of maps then sort the map entries
v.each { listEntry ->
if (listEntry instanceof Map) {
listEntry = toSortedMap(listEntry)
keySet.addAll(listEntry.keySet())
}
l << listEntry
}
//Sort list of map on the basis of some key
if (keySet) {
String sortKey = getSortKey(keySet)
if (sortKey) {
l = l.sort{it[sortKey]}
}
}
dest[k] = l
} else {
dest[k] = v
}
}
}
@Field def sortKeyCandidates = ['id', 'name', 'label']
def getSortKey(Set<String> keys) {
for (String k : sortKeyCandidates) {
if (keys.contains(k)) {
return k
}
}
return null
}
static def toPrettyJson(Map m){
return JsonOutput.prettyPrint(JsonOutput.toJson(m))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment