PageRenderTime 31ms CodeModel.GetById 12ms app.highlight 15ms RepoModel.GetById 2ms app.codeStats 0ms

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