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.InputStream;
  45 import java.lang.reflect.Method;
  46 import java.net.MalformedURLException;
  47 import java.net.URI;
  48 import java.net.URISyntaxException;
  49 import java.net.URL;
  50 import java.security.AccessController;
  51 import java.security.PrivilegedExceptionAction;
  52 import java.util.logging.Level;
  53 
  54 /**
  55  * This class is responsible for locating and loading Metro configuration files
  56  * (both application jaxws-tubes.xml and default jaxws-tubes-default.xml).
  57  * <p/>
  58  * Once the configuration is loaded the class is able to resolve which tubeline
  59  * configuration belongs to each endpoint or endpoint client. This information is
  60  * then used in {@link TubelineAssemblyController} to construct the list of
  61  * {@link TubeCreator} objects that are used in the actual tubeline construction.
  62  *
  63  * @author Marek Potociar <marek.potociar at sun.com>
  64  */
  65 // TODO Move the logic of this class directly into MetroConfig class.
  66 class MetroConfigLoader {
  67 
  68     private static final Logger LOGGER = Logger.getLogger(MetroConfigLoader.class);
  69 
  70     private MetroConfigName defaultTubesConfigNames;
  71 
  72     private static interface TubeFactoryListResolver {
  73 
  74         TubeFactoryList getFactories(TubelineDefinition td);
  75     }
  76 
  77     private static final TubeFactoryListResolver ENDPOINT_SIDE_RESOLVER = new TubeFactoryListResolver() {
  78 
  79         public TubeFactoryList getFactories(TubelineDefinition td) {
  80             return (td != null) ? td.getEndpointSide() : null;
  81         }
  82     };
  83     private static final TubeFactoryListResolver CLIENT_SIDE_RESOLVER = new TubeFactoryListResolver() {
  84 
  85         public TubeFactoryList getFactories(TubelineDefinition td) {
  86             return (td != null) ? td.getClientSide() : null;
  87         }
  88     };
  89     //
  90     private MetroConfig defaultConfig;
  91     private URL defaultConfigUrl;
  92     private MetroConfig appConfig;
  93     private URL appConfigUrl;
  94 
  95     MetroConfigLoader(Container container, MetroConfigName defaultTubesConfigNames) {
  96         this.defaultTubesConfigNames = defaultTubesConfigNames;
  97         ResourceLoader spiResourceLoader = null;
  98         if (container != null) {
  99             spiResourceLoader = container.getSPI(ResourceLoader.class);
 100         }
 101         // if spi resource can't load resource, default (MetroConfigUrlLoader) is used;
 102         // it searches the classpath, so it would be most probably used
 103         // when using jaxws- or metro-defaults from jaxws libraries
 104         init(container, spiResourceLoader, new MetroConfigUrlLoader(container));
 105     }
 106 
 107     private void init(Container container, ResourceLoader... loaders) {
 108 
 109         String appFileName = null;
 110         String defaultFileName = null;
 111         if (container != null) {
 112             MetroConfigName mcn = container.getSPI(MetroConfigName.class);
 113             if (mcn != null) {
 114                 appFileName = mcn.getAppFileName();
 115                 defaultFileName = mcn.getDefaultFileName();
 116             }
 117         }
 118         if (appFileName == null) {
 119             appFileName = defaultTubesConfigNames.getAppFileName();
 120         }
 121 
 122         if (defaultFileName == null) {
 123             defaultFileName = defaultTubesConfigNames.getDefaultFileName();
 124         }
 125         this.defaultConfigUrl = locateResource(defaultFileName, loaders);
 126 
 127         // TODO-Miran: revisit
 128         //    if (defaultConfigUrl == null) {
 129         //        throw LOGGER.logSevereException(new IllegalStateException(TubelineassemblyMessages.MASM_0001_DEFAULT_CFG_FILE_NOT_FOUND(defaultFileName)));
 130         //    }
 131 
 132         LOGGER.config(TubelineassemblyMessages.MASM_0002_DEFAULT_CFG_FILE_LOCATED(defaultFileName, defaultConfigUrl));
 133         this.defaultConfig = MetroConfigLoader.loadMetroConfig(defaultConfigUrl);
 134         if (defaultConfig == null) {
 135             throw LOGGER.logSevereException(new IllegalStateException(TubelineassemblyMessages.MASM_0003_DEFAULT_CFG_FILE_NOT_LOADED(defaultFileName)));
 136         }
 137         if (defaultConfig.getTubelines() == null) {
 138             throw LOGGER.logSevereException(new IllegalStateException(TubelineassemblyMessages.MASM_0004_NO_TUBELINES_SECTION_IN_DEFAULT_CFG_FILE(defaultFileName)));
 139         }
 140         if (defaultConfig.getTubelines().getDefault() == null) {
 141             throw LOGGER.logSevereException(new IllegalStateException(TubelineassemblyMessages.MASM_0005_NO_DEFAULT_TUBELINE_IN_DEFAULT_CFG_FILE(defaultFileName)));
 142         }
 143 
 144         this.appConfigUrl = locateResource(appFileName, loaders);
 145         if (appConfigUrl != null) {
 146             LOGGER.config(TubelineassemblyMessages.MASM_0006_APP_CFG_FILE_LOCATED(appConfigUrl));
 147             this.appConfig = MetroConfigLoader.loadMetroConfig(appConfigUrl);
 148         } else {
 149             LOGGER.config(TubelineassemblyMessages.MASM_0007_APP_CFG_FILE_NOT_FOUND());
 150             this.appConfig = null;
 151         }
 152     }
 153 
 154     TubeFactoryList getEndpointSideTubeFactories(URI endpointReference) {
 155         return getTubeFactories(endpointReference, ENDPOINT_SIDE_RESOLVER);
 156     }
 157 
 158     TubeFactoryList getClientSideTubeFactories(URI endpointReference) {
 159         return getTubeFactories(endpointReference, CLIENT_SIDE_RESOLVER);
 160     }
 161 
 162     private TubeFactoryList getTubeFactories(URI endpointReference, TubeFactoryListResolver resolver) {
 163         if (appConfig != null && appConfig.getTubelines() != null) {
 164             for (TubelineMapping mapping : appConfig.getTubelines().getTubelineMappings()) {
 165                 if (mapping.getEndpointRef().equals(endpointReference.toString())) {
 166                     TubeFactoryList list = resolver.getFactories(getTubeline(appConfig, resolveReference(mapping.getTubelineRef())));
 167                     if (list != null) {
 168                         return list;
 169                     } else {
 170                         break;
 171                     }
 172                 }
 173             }
 174 
 175             if (appConfig.getTubelines().getDefault() != null) {
 176                 TubeFactoryList list = resolver.getFactories(getTubeline(appConfig, resolveReference(appConfig.getTubelines().getDefault())));
 177                 if (list != null) {
 178                     return list;
 179                 }
 180             }
 181         }
 182 
 183         for (TubelineMapping mapping : defaultConfig.getTubelines().getTubelineMappings()) {
 184             if (mapping.getEndpointRef().equals(endpointReference.toString())) {
 185                 TubeFactoryList list = resolver.getFactories(getTubeline(defaultConfig, resolveReference(mapping.getTubelineRef())));
 186                 if (list != null) {
 187                     return list;
 188                 } else {
 189                     break;
 190                 }
 191             }
 192         }
 193 
 194         return resolver.getFactories(getTubeline(defaultConfig, resolveReference(defaultConfig.getTubelines().getDefault())));
 195     }
 196 
 197     TubelineDefinition getTubeline(MetroConfig config, URI tubelineDefinitionUri) {
 198         if (config != null && config.getTubelines() != null) {
 199             for (TubelineDefinition td : config.getTubelines().getTubelineDefinitions()) {
 200                 if (td.getName().equals(tubelineDefinitionUri.getFragment())) {
 201                     return td;
 202                 }
 203             }
 204         }
 205 
 206         return null;
 207     }
 208 
 209     private static URI resolveReference(String reference) {
 210         try {
 211             return new URI(reference);
 212         } catch (URISyntaxException ex) {
 213             throw LOGGER.logSevereException(new WebServiceException(TubelineassemblyMessages.MASM_0008_INVALID_URI_REFERENCE(reference), ex));
 214         }
 215     }
 216 
 217 
 218     private static URL locateResource(String resource, ResourceLoader loader) {
 219         if (loader == null) return null;
 220 
 221         try {
 222             return loader.getResource(resource);
 223         } catch (MalformedURLException ex) {
 224             LOGGER.severe(TubelineassemblyMessages.MASM_0009_CANNOT_FORM_VALID_URL(resource), ex);
 225         }
 226         return null;
 227     }
 228 
 229     private static URL locateResource(String resource, ResourceLoader[] loaders) {
 230 
 231         for (ResourceLoader loader : loaders) {
 232             URL url = locateResource(resource, loader);
 233             if (url != null) {
 234                 return url;
 235             }
 236         }
 237         return null;
 238     }
 239 
 240     private static MetroConfig loadMetroConfig(@NotNull URL resourceUrl) {
 241         try {
 242             JAXBContext jaxbContext = createJAXBContext();
 243             Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
 244             XMLInputFactory factory = XmlUtil.newXMLInputFactory(true);
 245 
 246             InputStream is;
 247             if (resourceUrl != null) {
 248                 is = resourceUrl.openStream();
 249             } else {
 250                 is = MetroConfig.class.getResourceAsStream("jaxws-tubes-default.xml");
 251             }
 252             JAXBElement<MetroConfig> configElement = unmarshaller.unmarshal(factory.createXMLStreamReader(is), MetroConfig.class);
 253 
 254             return configElement.getValue();
 255         } catch (Exception e) {
 256             InternalError error = new InternalError(
 257                     TubelineassemblyMessages.MASM_0010_ERROR_READING_CFG_FILE_FROM_LOCATION(resourceUrl != null ? resourceUrl.toString() : null)
 258             );
 259             LOGGER.logException(error, e, Level.SEVERE);
 260             throw error;
 261         }
 262     }
 263 
 264     private static JAXBContext createJAXBContext() throws Exception {
 265         if (isJDKInternal()) {
 266             // since jdk classes are repackaged, extra privilege is necessary to create JAXBContext
 267             return AccessController.doPrivileged(
 268                     new PrivilegedExceptionAction<JAXBContext>() {
 269                         @Override
 270                         public JAXBContext run() throws Exception {
 271                             return JAXBContext.newInstance(MetroConfig.class.getPackage().getName());
 272                         }
 273                     });
 274         } else {
 275             // usage from JAX-WS/Metro/Glassfish
 276             return JAXBContext.newInstance(MetroConfig.class.getPackage().getName());
 277         }
 278     }
 279 
 280     private static boolean isJDKInternal() {
 281         // avoid "string repackaging"
 282         return MetroConfigLoader.class.getName().startsWith("com." + "sun.xml.internal.ws");
 283     }
 284 
 285     private static class MetroConfigUrlLoader extends ResourceLoader {
 286 
 287         Container container; // TODO remove the field together with the code path using it (see below)
 288         ResourceLoader parentLoader;
 289 
 290         MetroConfigUrlLoader(ResourceLoader parentLoader) {
 291             this.parentLoader = parentLoader;
 292         }
 293 
 294         MetroConfigUrlLoader(Container container) {
 295             this((container != null) ? container.getSPI(ResourceLoader.class) : null);
 296             this.container = container;
 297         }
 298 
 299         @Override
 300         public URL getResource(String resource) throws MalformedURLException {
 301             LOGGER.entering(resource);
 302             URL resourceUrl = null;
 303             try {
 304                 if (parentLoader != null) {
 305                     if (LOGGER.isLoggable(Level.FINE)) {
 306                         LOGGER.fine(TubelineassemblyMessages.MASM_0011_LOADING_RESOURCE(resource, parentLoader));
 307                     }
 308 
 309                     resourceUrl = parentLoader.getResource(resource);
 310                 }
 311 
 312                 if (resourceUrl == null) {
 313                     resourceUrl = loadViaClassLoaders("com/sun/xml/internal/ws/assembler/" + resource);
 314                 }
 315 
 316                 if (resourceUrl == null && container != null) {
 317                     // TODO: we should remove this code path, the config file should be loaded using ResourceLoader only
 318                     resourceUrl = loadFromServletContext(resource);
 319                 }
 320 
 321                 return resourceUrl;
 322             } finally {
 323                 LOGGER.exiting(resourceUrl);
 324             }
 325         }
 326 
 327         private static URL loadViaClassLoaders(final String resource) {
 328             URL resourceUrl = tryLoadFromClassLoader(resource, Thread.currentThread().getContextClassLoader());
 329             if (resourceUrl == null) {
 330                 resourceUrl = tryLoadFromClassLoader(resource, MetroConfigLoader.class.getClassLoader());
 331                 if (resourceUrl == null) {
 332                     return ClassLoader.getSystemResource(resource);
 333                 }
 334             }
 335 
 336             return resourceUrl;
 337         }
 338 
 339         private static URL tryLoadFromClassLoader(final String resource, final ClassLoader loader) {
 340             return (loader != null) ? loader.getResource(resource) : null;
 341         }
 342 
 343         private URL loadFromServletContext(String resource) throws RuntimeException {
 344             Object context = null;
 345             try {
 346                 final Class<?> contextClass = Class.forName("javax.servlet.ServletContext");
 347                 context = container.getSPI(contextClass);
 348                 if (context != null) {
 349                     if (LOGGER.isLoggable(Level.FINE)) {
 350                         LOGGER.fine(TubelineassemblyMessages.MASM_0012_LOADING_VIA_SERVLET_CONTEXT(resource, context));
 351                     }
 352                     try {
 353                         final Method method = context.getClass().getMethod("getResource", String.class);
 354                         final Object result = method.invoke(context, "/WEB-INF/" + resource);
 355                         return URL.class.cast(result);
 356                     } catch (Exception e) {
 357                         throw LOGGER.logSevereException(new RuntimeException(TubelineassemblyMessages.MASM_0013_ERROR_INVOKING_SERVLET_CONTEXT_METHOD("getResource()")), e);
 358                     }
 359                 }
 360             } catch (ClassNotFoundException e) {
 361                 if (LOGGER.isLoggable(Level.FINE)) {
 362                     LOGGER.fine(TubelineassemblyMessages.MASM_0014_UNABLE_TO_LOAD_CLASS("javax.servlet.ServletContext"));
 363                 }
 364             }
 365             return null;
 366         }
 367     }
 368 
 369 }