/scalate-core/src/main/scala/org/fusesource/scalate/osgi/BundleClassPathBuilder.scala

http://github.com/scalate/scalate · Scala · 305 lines · 194 code · 44 blank · 67 comment · 67 complexity · dca400f19723a9ad0e4bd3702a45caad MD5 · raw file

  1. /**
  2. * Copyright (C) 2009-2011 the original author or authors.
  3. * See the notice.md file distributed with this work for additional
  4. * information regarding copyright ownership.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.fusesource.scalate.osgi
  19. import java.io.{ InputStream, IOException, File }
  20. import scala.reflect.io.AbstractFile
  21. import java.net.URL
  22. import java.lang.String
  23. import org.osgi.framework.{ ServiceReference, Bundle }
  24. import collection.mutable.{ ListBuffer, LinkedHashSet }
  25. import org.osgi.service.packageadmin.PackageAdmin
  26. import org.fusesource.scalate.util.{ Log, Strings }
  27. /**
  28. * Helper methods to transform OSGi bundles into {@link AbstractFile} implementations
  29. * suitable for use with the Scala compiler
  30. */
  31. object BundleClassPathBuilder {
  32. val log = Log(getClass); import log._
  33. // These were removed in Scala 2.11. We still use them.
  34. private trait AbstractFileCompatibility { this: AbstractFile =>
  35. def lookupPath(path: String, directory: Boolean): AbstractFile = {
  36. lookup((f, p, dir) => f.lookupName(p, dir), path, directory)
  37. }
  38. private def lookup(
  39. getFile: (AbstractFile, String, Boolean) => AbstractFile,
  40. path0: String,
  41. directory: Boolean): AbstractFile = {
  42. val separator = java.io.File.separatorChar
  43. // trim trailing '/'s
  44. val path: String = if (path0.last == separator) path0 dropRight 1 else path0
  45. val length = path.length()
  46. assert(length > 0 && !(path.last == separator), path)
  47. var file: AbstractFile = this
  48. var start = 0
  49. while (true) {
  50. val index = path.indexOf(separator, start)
  51. assert(index < 0 || start < index, ((path, directory, start, index)))
  52. val name = path.substring(start, if (index < 0) length else index)
  53. file = getFile(file, name, if (index < 0) directory else true)
  54. if ((file eq null) || index < 0) return file
  55. start = index + 1
  56. }
  57. file
  58. }
  59. }
  60. /**
  61. * Create a list of AbstractFile instances, representing the bundle and its wired dependencies
  62. */
  63. def fromBundle(bundle: Bundle): List[AbstractFile] = {
  64. require(bundle != null, "Bundle should not be null")
  65. // add the bundle itself
  66. val files = ListBuffer(create(bundle))
  67. // also add all bundles that have exports wired to imports from this bundle
  68. files.appendAll(fromWires(bundle))
  69. files.toList
  70. }
  71. /**
  72. * Find bundles that have exports wired to the given and bundle
  73. */
  74. def fromWires(bundle: Bundle): List[AbstractFile] = {
  75. debug("Checking OSGi bundle wiring for %s", bundle)
  76. val context = bundle.getBundleContext
  77. val ref: ServiceReference[_] = context.getServiceReference(classOf[PackageAdmin].getName)
  78. if (ref == null) {
  79. warn("PackageAdmin service is unavailable - unable to check bundle wiring information")
  80. return List()
  81. }
  82. try {
  83. var admin: PackageAdmin = context.getService(ref).asInstanceOf[PackageAdmin]
  84. if (admin == null) {
  85. warn("PackageAdmin service is unavailable - unable to check bundle wiring information")
  86. List()
  87. } else {
  88. fromWires(admin, bundle)
  89. }
  90. } finally {
  91. context.ungetService(ref)
  92. }
  93. }
  94. def fromWires(admin: PackageAdmin, bundle: Bundle): List[AbstractFile] = {
  95. val exported = admin.getExportedPackages(null: Bundle)
  96. val set = new LinkedHashSet[Bundle]
  97. for (pkg <- exported; if pkg.getExportingBundle.getBundleId != 0) {
  98. val bundles = pkg.getImportingBundles();
  99. if (bundles != null) {
  100. for (b <- bundles; if b.getBundleId == bundle.getBundleId) {
  101. debug("Bundle imports %s from %s", pkg, pkg.getExportingBundle)
  102. if (b.getBundleId == 0) {
  103. debug("Ignoring system bundle")
  104. } else {
  105. set += pkg.getExportingBundle
  106. }
  107. }
  108. }
  109. }
  110. set.map(create(_)).toList
  111. }
  112. /**
  113. * Create a new { @link AbstractFile } instance representing an
  114. * { @link org.osgi.framework.Bundle }
  115. *
  116. * @param bundle the bundle
  117. */
  118. def create(bundle: Bundle): AbstractFile = {
  119. require(bundle != null, "Bundle should not be null")
  120. abstract class BundleEntry(url: URL, parent: DirEntry) extends AbstractFile with AbstractFileCompatibility {
  121. require(url != null, "url must not be null")
  122. lazy val (path: String, name: String) = getPathAndName(url)
  123. lazy val fullName: String = (path :: name :: Nil).filter(n => !Strings.isEmpty(n)).mkString("/")
  124. /**
  125. * @return null
  126. */
  127. def file: File = null
  128. /**
  129. * @return last modification time or 0 if not known
  130. */
  131. def lastModified: Long =
  132. try { url.openConnection.getLastModified }
  133. catch { case _: Exception => 0 }
  134. @throws(classOf[IOException])
  135. def container: AbstractFile =
  136. valueOrElse(parent) {
  137. throw new IOException("No container")
  138. }
  139. @throws(classOf[IOException])
  140. def input: InputStream = url.openStream
  141. /**
  142. * Not supported. Always throws an IOException.
  143. * @throws IOException
  144. */
  145. @throws(classOf[IOException])
  146. def output = throw new IOException("not supported: output")
  147. private def getPathAndName(url: URL): (String, String) = {
  148. val u = url.getPath
  149. var k = u.length
  150. while ((k > 0) && (u(k - 1) == '/'))
  151. k = k - 1
  152. var j = k
  153. while ((j > 0) && (u(j - 1) != '/'))
  154. j = j - 1
  155. (u.substring(if (j > 0) 1 else 0, if (j > 1) j - 1 else j), u.substring(j, k))
  156. }
  157. override def toString = fullName
  158. }
  159. class DirEntry(url: URL, parent: DirEntry) extends BundleEntry(url, parent) {
  160. /**
  161. * @return true
  162. */
  163. def isDirectory: Boolean = true
  164. override def iterator: Iterator[AbstractFile] = {
  165. new Iterator[AbstractFile]() {
  166. val dirs = bundle.getEntryPaths(fullName)
  167. var nextEntry = prefetch()
  168. def hasNext = {
  169. if (nextEntry == null)
  170. nextEntry = prefetch()
  171. nextEntry != null
  172. }
  173. def next() = {
  174. if (hasNext) {
  175. val entry = nextEntry
  176. nextEntry = null
  177. entry
  178. } else {
  179. throw new NoSuchElementException()
  180. }
  181. }
  182. private def prefetch() = {
  183. if (dirs.hasMoreElements) {
  184. val entry = dirs.nextElement
  185. var entryUrl = bundle.getResource("/" + entry)
  186. // Bundle.getResource seems to be inconsistent with respect to requiring
  187. // a trailing slash
  188. if (entryUrl == null)
  189. entryUrl = bundle.getResource("/" + removeTralingSlash(entry))
  190. // If still null OSGi wont let use load that resource for some reason
  191. if (entryUrl == null) {
  192. null
  193. } else {
  194. if (entry.endsWith(".class"))
  195. new FileEntry(entryUrl, DirEntry.this)
  196. else
  197. new DirEntry(entryUrl, DirEntry.this)
  198. }
  199. } else
  200. null
  201. }
  202. private def removeTralingSlash(s: String): String =
  203. if (s == null || s.length == 0)
  204. s
  205. else if (s.last == '/')
  206. removeTralingSlash(s.substring(0, s.length - 1))
  207. else
  208. s
  209. }
  210. }
  211. def lookupName(name: String, directory: Boolean): AbstractFile = {
  212. val entry = bundle.getEntry(fullName + "/" + name)
  213. nullOrElse(entry) { entry =>
  214. if (directory)
  215. new DirEntry(entry, DirEntry.this)
  216. else
  217. new FileEntry(entry, DirEntry.this)
  218. }
  219. }
  220. override def lookupPathUnchecked(path: String, directory: Boolean) = lookupPath(path, directory)
  221. def lookupNameUnchecked(name: String, directory: Boolean) = lookupName(path, directory)
  222. def absolute = unsupported("absolute() is unsupported")
  223. def create = unsupported("create() is unsupported")
  224. def delete = unsupported("create() is unsupported")
  225. }
  226. class FileEntry(url: URL, parent: DirEntry) extends BundleEntry(url, parent) {
  227. /**
  228. * @return false
  229. */
  230. def isDirectory: Boolean = false
  231. override def sizeOption: Option[Int] = Some(bundle.getEntry(fullName).openConnection().getContentLength())
  232. def lookupName(name: String, directory: Boolean): AbstractFile = null
  233. override def lookupPathUnchecked(path: String, directory: Boolean) = lookupPath(path, directory)
  234. def lookupNameUnchecked(name: String, directory: Boolean) = lookupName(path, directory)
  235. def iterator = Iterator.empty
  236. def absolute = unsupported("absolute() is unsupported")
  237. def create = unsupported("create() is unsupported")
  238. def delete = unsupported("create() is unsupported")
  239. }
  240. new DirEntry(bundle.getResource("/"), null) {
  241. override def toString = "AbstractFile[" + bundle + "]"
  242. }
  243. }
  244. /**
  245. * Evaluate <code>f</code> on <code>s</code> if <code>s</code> is not null.
  246. * @param s
  247. * @param f
  248. * @return <code>f(s)</code> if s is not <code>null</code>, <code>null</code> otherwise.
  249. */
  250. def nullOrElse[S, T](s: S)(f: S => T): T =
  251. if (s == null) null.asInstanceOf[T]
  252. else f(s)
  253. /**
  254. * @param t
  255. * @param default
  256. * @return <code>t</code> or <code>default</code> if <code>null</code>.
  257. */
  258. def valueOrElse[T](t: T)(default: => T) =
  259. if (t == null) default
  260. else t
  261. }