1 /*
2  * JBoss, Home of Professional Open Source
3  *
4  * Copyright 2008 Red Hat, Inc. and/or its affiliates.
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
19 package org.xnio;
20
21 import java.io.Closeable;
22 import java.io.File;
23 import java.io.FileNotFoundException;
24 import java.io.IOException;
25 import java.lang.management.ManagementFactory;
26 import java.net.InetSocketAddress;
27 import java.nio.channels.FileChannel;
28 import java.nio.file.NoSuchFileException;
29 import java.nio.file.Path;
30 import java.nio.file.StandardOpenOption;
31 import java.security.AccessController;
32 import java.security.GeneralSecurityException;
33 import java.security.PrivilegedAction;
34 import java.util.EnumMap;
35 import java.util.EnumSet;
36 import java.util.Iterator;
37 import java.util.ServiceLoader;
38 import java.util.concurrent.atomic.AtomicBoolean;
39
40 import javax.management.MBeanServer;
41 import javax.management.ObjectName;
42
43 import org.osgi.framework.Bundle;
44 import org.osgi.framework.BundleContext;
45 import org.osgi.framework.FrameworkUtil;
46 import org.osgi.framework.ServiceReference;
47 import org.xnio.management.XnioProviderMXBean;
48 import org.xnio.management.XnioServerMXBean;
49 import org.xnio.management.XnioWorkerMXBean;
50 import org.xnio.ssl.JsseSslUtils;
51 import org.xnio.ssl.JsseXnioSsl;
52 import org.xnio.ssl.XnioSsl;
53
54 import javax.net.ssl.KeyManager;
55 import javax.net.ssl.TrustManager;
56
57 import static java.security.AccessController.doPrivileged;
58 import static org.xnio._private.Messages.msg;
59
60 /**
61  * The XNIO provider class.
62  *
63  * @apiviz.landmark
64  */

65 @SuppressWarnings("unused")
66 public abstract class Xnio {
67
68     static final InetSocketAddress ANY_INET_ADDRESS = new InetSocketAddress(0);
69     static final LocalSocketAddress ANY_LOCAL_ADDRESS = new LocalSocketAddress("");
70
71     private static final EnumMap<FileAccess, OptionMap> FILE_ACCESS_OPTION_MAPS;
72
73     private static final RuntimePermission ALLOW_BLOCKING_SETTING = new RuntimePermission("changeThreadBlockingSetting");
74
75     static final class MBeanHolder {
76
77         private static final MBeanServer MBEAN_SERVER;
78
79         static {
80             MBEAN_SERVER = doPrivileged(new PrivilegedAction<MBeanServer>() {
81                 public MBeanServer run() {
82                     return ManagementFactory.getPlatformMBeanServer();
83                 }
84             });
85         }
86     }
87
88     /**
89      * A flag indicating the presence of NIO.2 (JDK 7).  Always {@code true} as of XNIO version 3.5.0, which requires
90      * Java 8.
91      */

92     public static final boolean NIO2 = true;
93
94     static {
95         msg.greeting(Version.VERSION);
96         final EnumMap<FileAccess, OptionMap> map = new EnumMap<FileAccess, OptionMap>(FileAccess.class);
97         for (FileAccess access : FileAccess.values()) {
98             map.put(access, OptionMap.create(Options.FILE_ACCESS, access));
99         }
100         FILE_ACCESS_OPTION_MAPS = map;
101     }
102
103     /**
104      * The name of this provider instance.
105      */

106     private final String name;
107
108     /**
109      * Construct an XNIO provider instance.  Used by implementors only.  To get an XNIO instance,
110      * use one of the {@link org.xnio.Xnio#getInstance()} methods.
111      *
112      * @param name the provider name
113      */

114     protected Xnio(String name) {
115         if (name == null) {
116             throw msg.nullParameter("name");
117         }
118         this.name = name;
119     }
120
121     private static final ThreadLocal<Boolean> BLOCKING = new ThreadLocal<Boolean>() {
122         protected Boolean initialValue() {
123             return Boolean.TRUE;
124         }
125     };
126
127     /**
128      * Allow (or disallow) blocking I/O on the current thread.  Requires the {@code changeThreadBlockingSetting}
129      * {@link RuntimePermission}.
130      *
131      * @param newSetting {@code true} to allow blocking I/O, {@code false} to disallow it
132      * @return the previous setting
133      * @throws SecurityException if a security manager is present and disallows changing the {@code changeThreadBlockingSetting} {@code RuntimePermission}
134      */

135     public static boolean allowBlocking(boolean newSetting) throws SecurityException {
136         final SecurityManager sm = System.getSecurityManager();
137         if (sm != null) {
138             sm.checkPermission(ALLOW_BLOCKING_SETTING);
139         }
140         final ThreadLocal<Boolean> threadLocal = BLOCKING;
141         try {
142             return threadLocal.get().booleanValue();
143         } finally {
144             threadLocal.set(Boolean.valueOf(newSetting));
145         }
146     }
147
148     /**
149      * Determine whether blocking I/O is allowed from the current thread.
150      *
151      * @return {@code trueif blocking I/O is allowed, {@code false} otherwise
152      */

153     public static boolean isBlockingAllowed() {
154         return BLOCKING.get().booleanValue();
155     }
156
157     /**
158      * Perform a check for whether blocking is allowed on the current thread.
159      *
160      * @throws IllegalStateException if blocking is not allowed on the current thread
161      */

162     public static void checkBlockingAllowed() throws IllegalStateException {
163         if (! BLOCKING.get().booleanValue()) {
164             throw msg.blockingNotAllowed();
165         }
166     }
167
168     /**
169      * Get an XNIO provider instance.  If multiple providers are
170      * available, use the first one encountered.
171      *
172      * @param classLoader the class loader to search in
173      * @return the XNIO provider instance
174      *
175      * @since 3.0
176      */

177     public static Xnio getInstance(final ClassLoader classLoader) {
178         return doGetInstance(null, doPrivileged(new PrivilegedAction<ServiceLoader<XnioProvider>>() {
179             public ServiceLoader<XnioProvider> run() {
180                 return ServiceLoader.load(XnioProvider.class, classLoader);
181             }
182         }));
183     }
184
185     /**
186      * Get an XNIO provider instance from XNIO's class loader.  If multiple providers are
187      * available, use the first one encountered.
188      *
189      * @return the XNIO provider instance
190      *
191      * @since 3.0
192      */

193     public static Xnio getInstance() {
194         return doGetInstance(null, doPrivileged(new PrivilegedAction<ServiceLoader<XnioProvider>>() {
195             public ServiceLoader<XnioProvider> run() {
196                 return ServiceLoader.load(XnioProvider.class, Xnio.class.getClassLoader());
197             }
198         }));
199     }
200
201     /**
202      * Get a specific XNIO provider instance.
203      *
204      * @param provider the provider name, or {@code nullfor the first available
205      * @param classLoader the class loader to search in
206      * @return the XNIO provider instance
207      *
208      * @since 3.0
209      */

210     public static Xnio getInstance(String provider, final ClassLoader classLoader) {
211         return doGetInstance(provider, doPrivileged(new PrivilegedAction<ServiceLoader<XnioProvider>>() {
212             public ServiceLoader<XnioProvider> run() {
213                 return ServiceLoader.load(XnioProvider.class, classLoader);
214             }
215         }));
216     }
217
218     /**
219      * Get a specific XNIO provider instance from XNIO's class loader.
220      *
221      * @param provider the provider name, or {@code nullfor the first available
222      * @return the XNIO provider instance
223      *
224      * @since 3.0
225      */

226     public static Xnio getInstance(String provider) {
227         return doGetInstance(provider, doPrivileged(new PrivilegedAction<ServiceLoader<XnioProvider>>() {
228             public ServiceLoader<XnioProvider> run() {
229                 return ServiceLoader.load(XnioProvider.class, Xnio.class.getClassLoader());
230             }
231         }));
232     }
233
234     private static synchronized Xnio doGetInstance(final String provider, final ServiceLoader<XnioProvider> serviceLoader) {
235         final Iterator<XnioProvider> iterator = serviceLoader.iterator();
236         for (;;) {
237             try {
238                 if (! iterator.hasNext()) break;
239                 final XnioProvider xnioProvider = iterator.next();
240                 try {
241                     if (provider == null || provider.equals(xnioProvider.getName())) {
242                         return xnioProvider.getInstance();
243                     }
244                 } catch (Throwable t) {
245                     msg.debugf(t, "Not loading provider %s", xnioProvider.getName());
246                 }
247             } catch (Throwable t) {
248                 msg.debugf(t, "Skipping non-loadable provider");
249             }
250         }
251         try {
252             Xnio xnio = OsgiSupport.doGetOsgiService();
253             if (xnio != null) {
254                 return xnio;
255             }
256         } catch (NoClassDefFoundError t) {
257             // Ignore
258         } catch (Throwable t) {
259             msg.debugf(t, "Not using OSGi service");
260         }
261         throw msg.noProviderFound();
262     }
263
264     static class OsgiSupport {
265
266         static Xnio doGetOsgiService() {
267             Bundle bundle = FrameworkUtil.getBundle(Xnio.class);
268             BundleContext context = bundle.getBundleContext();
269             if (context == null) {
270                 throw new IllegalStateException("Bundle not started");
271             }
272             ServiceReference<Xnio> sr = context.getServiceReference(Xnio.class);
273             if (sr == null) {
274                 return null;
275             }
276             return context.getService(sr);
277         }
278
279     }
280
281     //==================================================
282     //
283     // SSL methods
284     //
285     //==================================================
286
287     /**
288      * Get an SSL provider for this XNIO provider.
289      *
290      * @param optionMap the option map to use for configuring SSL
291      * @return the SSL provider
292      * @throws GeneralSecurityException if an exception occurred configuring the SSL provider
293      */

294     public XnioSsl getSslProvider(final OptionMap optionMap) throws GeneralSecurityException {
295         return new JsseXnioSsl(this, optionMap);
296     }
297
298     /**
299      * Get an SSL provider for this XNIO provider.
300      *
301      * @param optionMap the option map to use for configuring SSL
302      * @param keyManagers the key managers to use, or {@code null} to configure from the option map
303      * @param trustManagers the trust managers to use, or {@code null} to configure from the option map
304      * @return the SSL provider
305      * @throws GeneralSecurityException if an exception occurred configuring the SSL provider
306      */

307     public XnioSsl getSslProvider(final KeyManager[] keyManagers, final TrustManager[] trustManagers, final OptionMap optionMap) throws GeneralSecurityException {
308         return new JsseXnioSsl(this, optionMap, JsseSslUtils.createSSLContext(keyManagers, trustManagers, null, optionMap));
309     }
310
311     //==================================================
312     //
313     // File system methods
314     //
315     //==================================================
316
317     /**
318      * Open a file on the filesystem.
319      *
320      * @param file the file to open
321      * @param options the file-open options
322      * @return the file channel
323      * @throws IOException if an I/O error occurs
324      */

325     public FileChannel openFile(File file, OptionMap options) throws IOException {
326         if (file == null) {
327             throw msg.nullParameter("file");
328         }
329         if (options == null) {
330             throw msg.nullParameter("options");
331         }
332         try {
333             final FileAccess fileAccess = options.get(Options.FILE_ACCESS, FileAccess.READ_WRITE);
334             final boolean append = options.get(Options.FILE_APPEND, false);
335             final boolean create = options.get(Options.FILE_CREATE, fileAccess != FileAccess.READ_ONLY);
336             final EnumSet<StandardOpenOption> openOptions = EnumSet.noneOf(StandardOpenOption.class);
337             if (create) {
338                 openOptions.add(StandardOpenOption.CREATE);
339             }
340             if (fileAccess.isRead()) {
341                 openOptions.add(StandardOpenOption.READ);
342             }
343             if (fileAccess.isWrite()) {
344                 openOptions.add(StandardOpenOption.WRITE);
345             }
346             if (append) {
347                 openOptions.add(StandardOpenOption.APPEND);
348             }
349             final Path path = file.toPath();
350             return new XnioFileChannel(path.getFileSystem().provider().newFileChannel(path, openOptions));
351         } catch (NoSuchFileException e) {
352             throw new FileNotFoundException(e.getMessage());
353         }
354     }
355
356     /**
357      * Open a file on the filesystem.
358      *
359      * @param fileName the file name of the file to open
360      * @param options the file-open options
361      * @return the file channel
362      * @throws IOException if an I/O error occurs
363      */

364     public FileChannel openFile(String fileName, OptionMap options) throws IOException {
365         if (fileName == null) {
366             throw msg.nullParameter("fileName");
367         }
368         return openFile(new File(fileName), options);
369     }
370
371     /**
372      * Open a file on the filesystem.
373      *
374      * @param file the file to open
375      * @param access the file access level to use
376      * @return the file channel
377      * @throws IOException if an I/O error occurs
378      */

379     public FileChannel openFile(File file, FileAccess access) throws IOException {
380         if (access == null) {
381             throw msg.nullParameter("access");
382         }
383         return openFile(file, FILE_ACCESS_OPTION_MAPS.get(access));
384     }
385
386     /**
387      * Open a file on the filesystem.
388      *
389      * @param fileName the file name of the file to open
390      * @param access the file access level to use
391      * @return the file channel
392      * @throws IOException if an I/O error occurs
393      */

394     public FileChannel openFile(String fileName, FileAccess access) throws IOException {
395         if (access == null) {
396             throw msg.nullParameter("access");
397         }
398         if (fileName == null) {
399             throw msg.nullParameter("fileName");
400         }
401         return openFile(new File(fileName), FILE_ACCESS_OPTION_MAPS.get(access));
402     }
403
404     /**
405      * Unwrap an XNIO-wrapped file channel.  For use by providers.
406      *
407      * @param src the possibly wrapped file channel
408      * @return the unwrapped file channel
409      */

410     protected FileChannel unwrapFileChannel(FileChannel src) {
411         if (src instanceof XnioFileChannel) {
412             return ((XnioFileChannel)src).getDelegate();
413         } else {
414             return src;
415         }
416     }
417
418     //==================================================
419     //
420     // Worker methods
421     //
422     //==================================================
423
424     /**
425      * Create a new worker builder.
426      *
427      * @return the worker builder (not {@code null})
428      */

429     public XnioWorker.Builder createWorkerBuilder() {
430         return new XnioWorker.Builder(this);
431     }
432
433     /**
434      * Construct an XNIO worker from a builder.
435      *
436      * @param builder the builder (must not be {@code null})
437      * @return the constructed worker
438      */

439     protected abstract XnioWorker build(XnioWorker.Builder builder);
440
441     /**
442      * Construct a new XNIO worker.
443      *
444      * @param optionMap the options to use to configure the worker
445      * @return the new worker
446      * @throws IOException if the worker failed to be opened
447      * @throws IllegalArgumentException if an option value is invalid for this worker
448      */

449     public XnioWorker createWorker(OptionMap optionMap) throws IOException, IllegalArgumentException {
450         return createWorker(null, optionMap);
451     }
452
453     /**
454      * Construct a new XNIO worker.
455      *
456      * @param threadGroup the thread group for worker threads
457      * @param optionMap the options to use to configure the worker
458      * @return the new worker
459      * @throws IOException if the worker failed to be opened
460      * @throws IllegalArgumentException if an option value is invalid for this worker
461      */

462     public XnioWorker createWorker(ThreadGroup threadGroup, OptionMap optionMap) throws IOException, IllegalArgumentException {
463         return createWorker(threadGroup, optionMap, null);
464     }
465
466     /**
467      * Construct a new XNIO worker.
468      *
469      * @param threadGroup the thread group for worker threads
470      * @param optionMap the options to use to configure the worker
471      * @param terminationTask the task to run after the worker has shut down
472      * @return the new worker
473      * @throws IOException if the worker failed to be opened
474      * @throws IllegalArgumentException if an option value is invalid for this worker
475      */

476     public XnioWorker createWorker(ThreadGroup threadGroup, OptionMap optionMap, Runnable terminationTask) throws IOException, IllegalArgumentException {
477         final XnioWorker.Builder workerBuilder = createWorkerBuilder();
478         workerBuilder.populateFromOptions(optionMap);
479         workerBuilder.setThreadGroup(threadGroup);
480         workerBuilder.setTerminationTask(terminationTask);
481         return workerBuilder.build();
482     }
483
484     /**
485      * Creates a file system watcher, that can be used to monitor file system changes.
486      *
487      * @param name The watcher name
488      * @param options The options to use to create the watcher
489      * @return The file system watcher
490      */

491     public FileSystemWatcher createFileSystemWatcher(final String name, final OptionMap options) {
492         int pollInterval = options.get(Options.WATCHER_POLL_INTERVAL, 5000);
493         boolean daemonThread = options.get(Options.THREAD_DAEMON, true);
494         return new PollingFileSystemWatcher(name, pollInterval, daemonThread);
495     }
496
497     //==================================================
498     //
499     // General methods
500     //
501     //==================================================
502
503     /**
504      * Get the name of this XNIO provider.
505      *
506      * @return the name
507      */

508     public final String getName() {
509         return name;
510     }
511
512     /**
513      * Get a string representation of this XNIO provider.
514      *
515      * @return the string representation
516      */

517     public final String toString() {
518         return String.format("XNIO provider \"%s\" <%s@%s>", getName(), getClass().getName(), Integer.toHexString(hashCode()));
519     }
520
521     /**
522      * Get an XNIO property.  The property name must start with {@code "xnio."}.
523      *
524      * @param name the property name
525      * @return the property value, or {@code nullif it wasn't found
526      * @since 1.2
527      */

528     protected static String getProperty(final String name) {
529         if (! name.startsWith("xnio.")) {
530             throw msg.propReadForbidden();
531         }
532         final SecurityManager sm = System.getSecurityManager();
533         if (sm != null) {
534             return AccessController.doPrivileged(new ReadPropertyAction(name, null));
535         } else {
536             return System.getProperty(name);
537         }
538     }
539
540     /**
541      * Get an XNIO property.  The property name must start with {@code "xnio."}.
542      *
543      * @param name the property name
544      * @param defaultValue the default value
545      * @return the property value, or {@code defaultValue} if it wasn't found
546      * @since 1.2
547      */

548     protected static String getProperty(final String name, final String defaultValue) {
549         if (! name.startsWith("xnio.")) {
550             throw msg.propReadForbidden();
551         }
552         final SecurityManager sm = System.getSecurityManager();
553         if (sm != null) {
554             return AccessController.doPrivileged(new ReadPropertyAction(name, defaultValue));
555         } else {
556             return System.getProperty(name, defaultValue);
557         }
558     }
559
560     /**
561      * Register an MBean.  If the MBean cannot be registered, this method will simply return.
562      *
563      * @param providerMXBean the provider MBean to register
564      * @return a handle which may be used to remove the registration
565      */

566     protected static Closeable register(XnioProviderMXBean providerMXBean) {
567         try {
568             final ObjectName objectName = new ObjectName("org.xnio", ObjectProperties.properties(ObjectProperties.property("type""Xnio"), ObjectProperties.property("provider", ObjectName.quote(providerMXBean.getName()))));
569             MBeanHolder.MBEAN_SERVER.registerMBean(providerMXBean, objectName);
570             return new MBeanCloseable(objectName);
571         } catch (Throwable ignored) {
572             return IoUtils.nullCloseable();
573         }
574     }
575
576     /**
577      * Register an MBean.  If the MBean cannot be registered, this method will simply return.
578      *
579      * @param workerMXBean the worker MBean to register
580      * @return a handle which may be used to remove the registration
581      */

582     protected static Closeable register(XnioWorkerMXBean workerMXBean) {
583         try {
584             final ObjectName objectName = new ObjectName("org.xnio", ObjectProperties.properties(ObjectProperties.property("type""Xnio"), ObjectProperties.property("provider", ObjectName.quote(workerMXBean.getProviderName())), ObjectProperties.property("worker", ObjectName.quote(workerMXBean.getName()))));
585             MBeanHolder.MBEAN_SERVER.registerMBean(workerMXBean, objectName);
586             return new MBeanCloseable(objectName);
587         } catch (Throwable ignored) {
588             return IoUtils.nullCloseable();
589         }
590     }
591
592     /**
593      * Register an MBean.  If the MBean cannot be registered, this method will simply return.
594      *
595      * @param serverMXBean the server MBean to register
596      * @return a handle which may be used to remove the registration
597      */

598     protected static Closeable register(XnioServerMXBean serverMXBean) {
599         try {
600             final ObjectName objectName = new ObjectName("org.xnio", ObjectProperties.properties(ObjectProperties.property("type""Xnio"), ObjectProperties.property("provider", ObjectName.quote(serverMXBean.getProviderName())), ObjectProperties.property("worker", ObjectName.quote(serverMXBean.getWorkerName())), ObjectProperties.property("address", ObjectName.quote(serverMXBean.getBindAddress()))));
601             MBeanHolder.MBEAN_SERVER.registerMBean(serverMXBean, objectName);
602             return new MBeanCloseable(objectName);
603         } catch (Throwable ignored) {
604             return IoUtils.nullCloseable();
605         }
606     }
607
608     static class MBeanCloseable extends AtomicBoolean implements Closeable {
609
610         private final ObjectName objectName;
611
612         MBeanCloseable(final ObjectName objectName) {
613             this.objectName = objectName;
614         }
615
616         public void close() {
617             if (! getAndSet(true)) try {
618                 MBeanHolder.MBEAN_SERVER.unregisterMBean(objectName);
619             } catch (Throwable ignored) {
620             }
621         }
622     }
623 }
624