001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.reifier.rest; 018 019import java.util.HashMap; 020import java.util.Map; 021 022import javax.xml.bind.JAXBContext; 023 024import org.apache.camel.ExtendedCamelContext; 025import org.apache.camel.model.rest.RestBindingDefinition; 026import org.apache.camel.model.rest.RestBindingMode; 027import org.apache.camel.processor.RestBindingAdvice; 028import org.apache.camel.reifier.AbstractReifier; 029import org.apache.camel.spi.DataFormat; 030import org.apache.camel.spi.RestConfiguration; 031import org.apache.camel.spi.RouteContext; 032import org.apache.camel.support.PropertyBindingSupport; 033 034public class RestBindingReifier extends AbstractReifier { 035 036 private final RestBindingDefinition definition; 037 038 public RestBindingReifier(RouteContext routeContext, RestBindingDefinition definition) { 039 super(routeContext); 040 this.definition = definition; 041 } 042 043 public RestBindingAdvice createRestBindingAdvice() throws Exception { 044 RestConfiguration config = camelContext.getRestConfiguration(definition.getComponent(), true); 045 046 // these options can be overridden per rest verb 047 String mode = config.getBindingMode().name(); 048 if (definition.getBindingMode() != null) { 049 mode = parse(RestBindingMode.class, definition.getBindingMode()).name(); 050 } 051 boolean cors = config.isEnableCORS(); 052 if (definition.getEnableCORS() != null) { 053 cors = parseBoolean(definition.getEnableCORS(), false); 054 } 055 boolean skip = config.isSkipBindingOnErrorCode(); 056 if (definition.getSkipBindingOnErrorCode() != null) { 057 skip = parseBoolean(definition.getSkipBindingOnErrorCode(), false); 058 } 059 boolean validation = config.isClientRequestValidation(); 060 if (definition.getClientRequestValidation() != null) { 061 validation = parseBoolean(definition.getClientRequestValidation(), false); 062 } 063 064 // cors headers 065 Map<String, String> corsHeaders = config.getCorsHeaders(); 066 067 if (mode == null || "off".equals(mode)) { 068 // binding mode is off, so create a off mode binding processor 069 return new RestBindingAdvice(camelContext, null, null, null, null, 070 parseString(definition.getConsumes()), parseString(definition.getProduces()), mode, skip, validation, cors, corsHeaders, 071 definition.getDefaultValues(), definition.getRequiredBody() != null ? definition.getRequiredBody() : false, 072 definition.getRequiredQueryParameters(), definition.getRequiredHeaders()); 073 } 074 075 // setup json data format 076 DataFormat json = null; 077 DataFormat outJson = null; 078 if (mode.contains("json") || "auto".equals(mode)) { 079 String name = config.getJsonDataFormat(); 080 if (name != null) { 081 // must only be a name, not refer to an existing instance 082 Object instance = camelContext.getRegistry().lookupByName(name); 083 if (instance != null) { 084 throw new IllegalArgumentException("JsonDataFormat name: " + name + " must not be an existing bean instance from the registry"); 085 } 086 } else { 087 name = "json-jackson"; 088 } 089 // this will create a new instance as the name was not already 090 // pre-created 091 json = camelContext.resolveDataFormat(name); 092 outJson = camelContext.resolveDataFormat(name); 093 094 if (json != null) { 095 setupJson(config, parseString(definition.getType()), parseString(definition.getOutType()), json, outJson); 096 } 097 } 098 099 // setup xml data format 100 DataFormat jaxb = null; 101 DataFormat outJaxb = null; 102 if (mode.contains("xml") || "auto".equals(mode)) { 103 String name = config.getXmlDataFormat(); 104 if (name != null) { 105 // must only be a name, not refer to an existing instance 106 Object instance = camelContext.getRegistry().lookupByName(name); 107 if (instance != null) { 108 throw new IllegalArgumentException("XmlDataFormat name: " + name + " must not be an existing bean instance from the registry"); 109 } 110 } else { 111 name = "jaxb"; 112 } 113 // this will create a new instance as the name was not already 114 // pre-created 115 jaxb = camelContext.resolveDataFormat(name); 116 outJaxb = camelContext.resolveDataFormat(name); 117 118 // is xml binding required? 119 if (mode.contains("xml") && jaxb == null) { 120 throw new IllegalArgumentException("XML DataFormat " + name + " not found."); 121 } 122 123 if (jaxb != null) { 124 setupJaxb(config, parseString(definition.getType()), parseString(definition.getOutType()), jaxb, outJaxb); 125 } 126 } 127 128 return new RestBindingAdvice(camelContext, json, jaxb, outJson, outJaxb, 129 parseString(definition.getConsumes()), parseString(definition.getProduces()), 130 mode, skip, validation, cors, corsHeaders, 131 definition.getDefaultValues(), definition.getRequiredBody() != null ? definition.getRequiredBody() : false, 132 definition.getRequiredQueryParameters(), definition.getRequiredHeaders()); 133 } 134 135 protected void setupJson(RestConfiguration config, String type, String outType, DataFormat json, DataFormat outJson) throws Exception { 136 Class<?> clazz = null; 137 if (type != null) { 138 String typeName = type.endsWith("[]") ? type.substring(0, type.length() - 2) : type; 139 clazz = camelContext.getClassResolver().resolveMandatoryClass(typeName); 140 } 141 if (clazz != null) { 142 camelContext.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(camelContext, json, "unmarshalType", clazz); 143 camelContext.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(camelContext, json, "useList", type.endsWith("[]")); 144 } 145 setAdditionalConfiguration(config, json, "json.in."); 146 147 Class<?> outClazz = null; 148 if (outType != null) { 149 String typeName = outType.endsWith("[]") ? outType.substring(0, outType.length() - 2) : outType; 150 outClazz = camelContext.getClassResolver().resolveMandatoryClass(typeName); 151 } 152 if (outClazz != null) { 153 camelContext.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(camelContext, outJson, "unmarshalType", outClazz); 154 camelContext.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(camelContext, outJson, "useList", outType.endsWith("[]")); 155 } 156 setAdditionalConfiguration(config, outJson, "json.out."); 157 } 158 159 protected void setupJaxb(RestConfiguration config, String type, String outType, DataFormat jaxb, DataFormat outJaxb) throws Exception { 160 Class<?> clazz = null; 161 if (type != null) { 162 String typeName = type.endsWith("[]") ? type.substring(0, type.length() - 2) : type; 163 clazz = camelContext.getClassResolver().resolveMandatoryClass(typeName); 164 } 165 if (clazz != null) { 166 JAXBContext jc = JAXBContext.newInstance(clazz); 167 setJaxbContext(jaxb, jc); 168 } 169 setAdditionalConfiguration(config, jaxb, "xml.in."); 170 171 Class<?> outClazz = null; 172 if (outType != null) { 173 String typeName = outType.endsWith("[]") ? outType.substring(0, outType.length() - 2) : outType; 174 outClazz = camelContext.getClassResolver().resolveMandatoryClass(typeName); 175 } 176 if (outClazz != null) { 177 JAXBContext jc = JAXBContext.newInstance(outClazz); 178 setJaxbContext(outJaxb, jc); 179 } else if (clazz != null) { 180 // fallback and use the context from the input 181 JAXBContext jc = JAXBContext.newInstance(clazz); 182 setJaxbContext(outJaxb, jc); 183 } 184 setAdditionalConfiguration(config, outJaxb, "xml.out."); 185 } 186 187 private void setJaxbContext(DataFormat jaxb, JAXBContext jc) throws Exception { 188 camelContext.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(camelContext, jaxb, "context", jc); 189 } 190 191 private void setAdditionalConfiguration(RestConfiguration config, DataFormat dataFormat, String prefix) throws Exception { 192 if (config.getDataFormatProperties() != null && !config.getDataFormatProperties().isEmpty()) { 193 // must use a copy as otherwise the options gets removed during 194 // introspection setProperties 195 Map<String, Object> copy = new HashMap<>(); 196 197 // filter keys on prefix 198 // - either its a known prefix and must match the prefix parameter 199 // - or its a common configuration that we should always use 200 for (Map.Entry<String, Object> entry : config.getDataFormatProperties().entrySet()) { 201 String key = entry.getKey(); 202 String copyKey; 203 boolean known = isKeyKnownPrefix(key); 204 if (known) { 205 // remove the prefix from the key to use 206 copyKey = key.substring(prefix.length()); 207 } else { 208 // use the key as is 209 copyKey = key; 210 } 211 if (!known || key.startsWith(prefix)) { 212 copy.put(copyKey, entry.getValue()); 213 } 214 } 215 216 PropertyBindingSupport.build().bind(camelContext, dataFormat, copy); 217 } 218 } 219 220 private boolean isKeyKnownPrefix(String key) { 221 return key.startsWith("json.in.") || key.startsWith("json.out.") || key.startsWith("xml.in.") || key.startsWith("xml.out."); 222 } 223 224}