1 /*
   2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.ws.assembler;
  27 
  28 import com.sun.istack.internal.NotNull;
  29 import com.sun.istack.internal.logging.Logger;
  30 import com.sun.xml.internal.ws.api.ResourceLoader;
  31 import com.sun.xml.internal.ws.api.server.Container;
  32 import com.sun.xml.internal.ws.resources.TubelineassemblyMessages;
  33 import com.sun.xml.internal.ws.runtime.config.MetroConfig;
  34 import com.sun.xml.internal.ws.runtime.config.TubeFactoryList;
  35 import com.sun.xml.internal.ws.runtime.config.TubelineDefinition;
  36 import com.sun.xml.internal.ws.runtime.config.TubelineMapping;
  37 import com.sun.xml.internal.ws.util.xml.XmlUtil;
  38 
  39 import javax.xml.bind.JAXBContext;
  40 import javax.xml.bind.JAXBElement;
  41 import javax.xml.bind.Unmarshaller;
  42 import javax.xml.stream.XMLInputFactory;
  43 import javax.xml.ws.WebServiceException;
  44 import java.io.IOException;
  45 import java.io.InputStream;
  46 import java.lang.reflect.Method;
  47 import java.net.MalformedURLException;
  48 import java.net.URI;
  49 import java.net.URISyntaxException;
  50 import java.net.URL;
  51 import java.security.AccessController;
  52 import java.security.PrivilegedExceptionAction;
  53 import java.util.logging.Level;
  54 
  55 /**
  56  * This class is responsible for locating and loading Metro configuration files
  57  * (both application jaxws-tubes.xml and default jaxws-tubes-default.xml).
  58  * <p/>
  59  * Once the configuration is loaded the class is able to resolve which tubeline
  60  * configuration belongs to each endpoint or endpoint client. This information is
  61  * then used in {@link TubelineAssemblyController} to construct the list of
  62  * {@link TubeCreator} objects that are used in the actual tubeline construction.
  63  *
  64  * @author Marek Potociar <marek.potociar at sun.com>
  65  */
  66 // TODO Move the logic of this class directly into MetroConfig class.
  67 class MetroConfigLoader {
  68 
  69     private static final Logger LOGGER = Logger.getLogger(MetroConfigLoader.class);
  70 
  71     private MetroConfigName defaultTubesConfigNames;
  72 
  73     private static interface TubeFactoryListResolver {
  74 
  75         TubeFactoryList getFactories(TubelineDefinition td);
  76     }
  77 
  78     private static final TubeFactoryListResolver ENDPOINT_SIDE_RESOLVER = new TubeFactoryListResolver() {
  79 
  80         public TubeFactoryList getFactories(TubelineDefinition td) {
  81             return (td != null) ? td.getEndpointSide() : null;
  82         }
  83     };
  84     private static final TubeFactoryListResolver CLIENT_SIDE_RESOLVER = new TubeFactoryListResolver() {
  85 
  86         public TubeFactoryList getFactories(TubelineDefinition td) {
  87             return (td != null) ? td.getClientSide() : null;
  88         }
  89     };
  90     //
  91     private MetroConfig defaultConfig;
  92     private URL defaultConfigUrl;
  93     private MetroConfig appConfig;
  94     private URL appConfigUrl;
  95 
  96     MetroConfigLoader(Container container, MetroConfigName defaultTubesConfigNames) {
  97         this.defaultTubesConfigNames = defaultTubesConfigNames;
  98         ResourceLoader spiResourceLoader = null;
  99         if (container != null) {
 100             spiResourceLoader = container.getSPI(ResourceLoader.class);
 101         }
 102         // if spi resource can't load resource, default (MetroConfigUrlLoader) is used;
 103         // it searches the classpath, so it would be most probably used
 104         // when using jaxws- or metro-defaults from jaxws libraries
 105         init(container, spiResourceLoader, new MetroConfigUrlLoader(container));
 106     }
 107 
 108     private void init(Container container, ResourceLoader... loaders) {
 109 
 110         String appFileName = null;
 111         String defaultFileName = null;
 112         if (container != null) {
 113             MetroConfigName mcn = container.getSPI(MetroConfigName.class);
 114             if (mcn != null) {
 115                 appFileName = mcn.getAppFileName();
 116                 defaultFileName = mcn.getDefaultFileName();
 117             }
 118         }
 119         if (appFileName == null) {
 120             appFileName = defaultTubesConfigNames.getAppFileName();
 121         }
 122 
 123         if (defaultFileName == null) {
 124             defaultFileName = defaultTubesConfigNames.getDefaultFileName();
 125         }
 126         this.defaultConfigUrl = locateResource(defaultFileName, loaders);
 127 
 128         // TODO-Miran: revisit
 129         //    if (defaultConfigUrl == null) {
 130         //        throw LOGGER.logSevereException(new IllegalStateException(TubelineassemblyMessages.MASM_0001_DEFAULT_CFG_FILE_NOT_FOUND(defaultFileName)));
 131         //    }
 132 
 133         LOGGER.config(TubelineassemblyMessages.MASM_0002_DEFAULT_CFG_FILE_LOCATED(defaultFileName, defaultConfigUrl));
 134         this.defaultConfig = MetroConfigLoader.loadMetroConfig(defaultConfigUrl);
 135         if (defaultConfig == null) {
 136             throw LOGGER.logSevereException(new IllegalStateException(TubelineassemblyMessages.MASM_0003_DEFAULT_CFG_FILE_NOT_LOADED(defaultFileName)));
 137         }
 138         if (defaultConfig.getTubelines() == null) {
 139             throw LOGGER.logSevereException(new IllegalStateException(TubelineassemblyMessages.MASM_0004_NO_TUBELINES_SECTION_IN_DEFAULT_CFG_FILE(defaultFileName)));
 140         }
 141         if (defaultConfig.getTubelines().getDefault() == null) {
 142             throw LOGGER.logSevereException(new IllegalStateException(TubelineassemblyMessages.MASM_0005_NO_DEFAULT_TUBELINE_IN_DEFAULT_CFG_FILE(defaultFileName)));
 143         }
 144 
 145         this.appConfigUrl = locateResource(appFileName, loaders);
 146         if (appConfigUrl != null) {
 147             LOGGER.config(TubelineassemblyMessages.MASM_0006_APP_CFG_FILE_LOCATED(appConfigUrl));
 148             this.appConfig = MetroConfigLoader.loadMetroConfig(appConfigUrl);
 149         } else {
 150             LOGGER.config(TubelineassemblyMessages.MASM_0007_APP_CFG_FILE_NOT_FOUND());
 151             this.appConfig = null;
 152         }
 153     }
 154 
 155     TubeFactoryList getEndpointSideTubeFactories(URI endpointReference) {
 156         return getTubeFactories(endpointReference, ENDPOINT_SIDE_RESOLVER);
 157     }
 158 
 159     TubeFactoryList getClientSideTubeFactories(URI endpointReference) {
 160         return getTubeFactories(endpointReference, CLIENT_SIDE_RESOLVER);
 161     }
 162 
 163     private TubeFactoryList getTubeFactories(URI endpointReference, TubeFactoryListResolver resolver) {
 164         if (appConfig != null && appConfig.getTubelines() != null) {
 165             for (TubelineMapping mapping : appConfig.getTubelines().getTubelineMappings()) {
 166                 if (mapping.getEndpointRef().equals(endpointReference.toString())) {
 167                     TubeFactoryList list = resolver.getFactories(getTubeline(appConfig, resolveReference(mapping.getTubelineRef())));
 168                     if (list != null) {
 169                         return list;
 170                     } else {
 171                         break;
 172                     }
 173                 }
 174             }
 175 
 176             if (appConfig.getTubelines().getDefault() != null) {
 177                 TubeFactoryList list = resolver.getFactories(getTubeline(appConfig, resolveReference(appConfig.getTubelines().getDefault())));
 178                 if (list != null) {
 179                     return list;
 180                 }
 181             }
 182         }
 183 
 184         for (TubelineMapping mapping : defaultConfig.getTubelines().getTubelineMappings()) {
 185             if (mapping.getEndpointRef().equals(endpointReference.toString())) {
 186                 TubeFactoryList list = resolver.getFactories(getTubeline(defaultConfig, resolveReference(mapping.getTubelineRef())));
 187                 if (list != null) {
 188                     return list;
 189                 } else {
 190                     break;
 191                 }
 192             }
 193         }
 194 
 195         return resolver.getFactories(getTubeline(defaultConfig, resolveReference(defaultConfig.getTubelines().getDefault())));
 196     }
 197 
 198     TubelineDefinition getTubeline(MetroConfig config, URI tubelineDefinitionUri) {
 199         if (config != null && config.getTubelines() != null) {
 200             for (TubelineDefinition td : config.getTubelines().getTubelineDefinitions()) {
 201                 if (td.getName().equals(tubelineDefinitionUri.getFragment())) {
 202                     return td;
 203                 }
 204             }
 205         }
 206 
 207         return null;
 208     }
 209 
 210     private static URI resolveReference(String reference) {
 211         try {
 212             return new URI(reference);
 213         } catch (URISyntaxException ex) {
 214             throw LOGGER.logSevereException(new WebServiceException(TubelineassemblyMessages.MASM_0008_INVALID_URI_REFERENCE(reference), ex));
 215         }
 216     }
 217 
 218 
 219     private static URL locateResource(String resource, ResourceLoader loader) {
 220         if (loader == null) return null;
 221 
 222         try {
 223             return loader.getResource(resource);
 224         } catch (MalformedURLException ex) {
 225             LOGGER.severe(TubelineassemblyMessages.MASM_0009_CANNOT_FORM_VALID_URL(resource), ex);
 226         }
 227         return null;
 228     }
 229 
 230     private static URL locateResource(String resource, ResourceLoader[] loaders) {
 231 
 232         for (ResourceLoader loader : loaders) {
 233             URL url = locateResource(resource, loader);
 234             if (url != null) {
 235                 return url;
 236             }
 237         }
 238         return null;
 239     }
 240 
 241     private static MetroConfig loadMetroConfig(@NotNull URL resourceUrl) {
 242         try (InputStream is = getConfigInputStream(resourceUrl)) {
 243             JAXBContext jaxbContext = createJAXBContext();
 244             Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
 245             XMLInputFactory factory = XmlUtil.newXMLInputFactory(true);
 246             JAXBElement<MetroConfig> configElement = unmarshaller.unmarshal(factory.createXMLStreamReader(is), MetroConfig.class);
 247             return configElement.getValue();
 248         } catch (Exception e) {
 249             String message = TubelineassemblyMessages.MASM_0010_ERROR_READING_CFG_FILE_FROM_LOCATION(
 250                     resourceUrl != null ? resourceUrl.toString() : null);
 251             InternalError error = new InternalError(message);
 252             LOGGER.logException(error, e, Level.SEVERE);
 253             throw error;
 254         }
 255     }
 256 
 257     private static InputStream getConfigInputStream(URL resourceUrl) throws IOException {
 258         InputStream is;
 259         if (resourceUrl != null) {
 260             is = resourceUrl.openStream();
 261         } else {
 262             is = MetroConfigLoader.class.getResourceAsStream("jaxws-tubes-default.xml");
 263         }
 264         return is;
 265     }
 266 
 267     private static JAXBContext createJAXBContext() throws Exception {
 268         if (isJDKInternal()) {
 269             // since jdk classes are repackaged, extra privilege is necessary to create JAXBContext
 270             return AccessController.doPrivileged(
 271                     new PrivilegedExceptionAction<JAXBContext>() {
 272                         @Override
 273                         public JAXBContext run() throws Exception {
 274                             return JAXBContext.newInstance(MetroConfig.class.getPackage().getName());
 275                         }
 276                     });
 277         } else {
 278             // usage from JAX-WS/Metro/Glassfish
 279             return JAXBContext.newInstance(MetroConfig.class.getPackage().getName());
 280         }
 281     }
 282 
 283     private static boolean isJDKInternal() {
 284         // avoid "string repackaging"
 285         return MetroConfigLoader.class.getName().startsWith("com." + "sun.xml.internal.ws");
 286     }
 287 
 288     private static class MetroConfigUrlLoader extends ResourceLoader {
 289 
 290         Container container; // TODO remove the field together with the code path using it (see below)
 291         ResourceLoader parentLoader;
 292 
 293         MetroConfigUrlLoader(ResourceLoader parentLoader) {
 294             this.parentLoader = parentLoader;
 295         }
 296 
 297         MetroConfigUrlLoader(Container container) {
 298             this((container != null) ? container.getSPI(ResourceLoader.class) : null);
 299             this.container = container;
 300         }
 301 
 302         @Override
 303         public URL getResource(String resource) throws MalformedURLException {
 304             LOGGER.entering(resource);
 305             URL resourceUrl = null;
 306             try {
 307                 if (parentLoader != null) {
 308                     if (LOGGER.isLoggable(Level.FINE)) {
 309                         LOGGER.fine(TubelineassemblyMessages.MASM_0011_LOADING_RESOURCE(resource, parentLoader));
 310                     }
 311 
 312                     resourceUrl = parentLoader.getResource(resource);
 313                 }
 314 
 315                 if (resourceUrl == null) {
 316                     resourceUrl = loadViaClassLoaders("com/sun/xml/internal/ws/assembler/" + resource);
 317                 }
 318 
 319                 if (resourceUrl == null && container != null) {
 320                     // TODO: we should remove this code path, the config file should be loaded using ResourceLoader only
 321                     resourceUrl = loadFromServletContext(resource);
 322                 }
 323 
 324                 return resourceUrl;
 325             } finally {
 326                 LOGGER.exiting(resourceUrl);
 327             }
 328         }
 329 
 330         private static URL loadViaClassLoaders(final String resource) {
 331             URL resourceUrl = tryLoadFromClassLoader(resource, Thread.currentThread().getContextClassLoader());
 332             if (resourceUrl == null) {
 333                 resourceUrl = tryLoadFromClassLoader(resource, MetroConfigLoader.class.getClassLoader());
 334                 if (resourceUrl == null) {
 335                     return ClassLoader.getSystemResource(resource);
 336                 }
 337             }
 338 
 339             return resourceUrl;
 340         }
 341 
 342         private static URL tryLoadFromClassLoader(final String resource, final ClassLoader loader) {
 343             return (loader != null) ? loader.getResource(resource) : null;
 344         }
 345 
 346         private URL loadFromServletContext(String resource) throws RuntimeException {
 347             Object context = null;
 348             try {
 349                 final Class<?> contextClass = Class.forName("javax.servlet.ServletContext");
 350                 context = container.getSPI(contextClass);
 351                 if (context != null) {
 352                     if (LOGGER.isLoggable(Level.FINE)) {
 353                         LOGGER.fine(TubelineassemblyMessages.MASM_0012_LOADING_VIA_SERVLET_CONTEXT(resource, context));
 354                     }
 355                     try {
 356                         final Method method = context.getClass().getMethod("getResource", String.class);
 357                         final Object result = method.invoke(context, "/WEB-INF/" + resource);
 358                         return URL.class.cast(result);
 359                     } catch (Exception e) {
 360                         throw LOGGER.logSevereException(new RuntimeException(TubelineassemblyMessages.MASM_0013_ERROR_INVOKING_SERVLET_CONTEXT_METHOD("getResource()")), e);
 361                     }
 362                 }
 363             } catch (ClassNotFoundException e) {
 364                 if (LOGGER.isLoggable(Level.FINE)) {
 365                     LOGGER.fine(TubelineassemblyMessages.MASM_0014_UNABLE_TO_LOAD_CLASS("javax.servlet.ServletContext"));
 366                 }
 367             }
 368             return null;
 369         }
 370     }
 371 
 372 }