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; 021import javax.xml.bind.JAXBContext; 022 023import org.apache.camel.CamelContext; 024import org.apache.camel.ExtendedCamelContext; 025import org.apache.camel.model.rest.RestBindingDefinition; 026import org.apache.camel.processor.RestBindingAdvice; 027import org.apache.camel.spi.DataFormat; 028import org.apache.camel.spi.RestConfiguration; 029import org.apache.camel.spi.RouteContext; 030import org.apache.camel.support.PropertyBindingSupport; 031 032public class RestBindingReifier { 033 034 private final RestBindingDefinition definition; 035 036 public RestBindingReifier(RestBindingDefinition definition) { 037 this.definition = definition; 038 } 039 040 public RestBindingAdvice createRestBindingAdvice(RouteContext routeContext) throws Exception { 041 042 CamelContext context = routeContext.getCamelContext(); 043 RestConfiguration config = context.getRestConfiguration(definition.getComponent(), true); 044 045 // these options can be overridden per rest verb 046 String mode = config.getBindingMode().name(); 047 if (definition.getBindingMode() != null) { 048 mode = definition.getBindingMode().name(); 049 } 050 boolean cors = config.isEnableCORS(); 051 if (definition.getEnableCORS() != null) { 052 cors = definition.getEnableCORS(); 053 } 054 boolean skip = config.isSkipBindingOnErrorCode(); 055 if (definition.getSkipBindingOnErrorCode() != null) { 056 skip = definition.getSkipBindingOnErrorCode(); 057 } 058 boolean validation = config.isClientRequestValidation(); 059 if (definition.getClientRequestValidation() != null) { 060 validation = definition.getClientRequestValidation(); 061 } 062 063 // cors headers 064 Map<String, String> corsHeaders = config.getCorsHeaders(); 065 066 if (mode == null || "off".equals(mode)) { 067 // binding mode is off, so create a off mode binding processor 068 return new RestBindingAdvice(context, null, null, null, null, definition.getConsumes(), definition.getProduces(), mode, skip, validation, cors, corsHeaders, 069 definition.getDefaultValues(), definition.getRequiredBody() != null ? definition.getRequiredBody() : false, 070 definition.getRequiredQueryParameters(), definition.getRequiredHeaders()); 071 } 072 073 // setup json data format 074 DataFormat json = null; 075 DataFormat outJson = null; 076 if (mode.contains("json") || "auto".equals(mode)) { 077 String name = config.getJsonDataFormat(); 078 if (name != null) { 079 // must only be a name, not refer to an existing instance 080 Object instance = context.getRegistry().lookupByName(name); 081 if (instance != null) { 082 throw new IllegalArgumentException("JsonDataFormat name: " + name + " must not be an existing bean instance from the registry"); 083 } 084 } else { 085 name = "json-jackson"; 086 } 087 // this will create a new instance as the name was not already 088 // pre-created 089 json = context.resolveDataFormat(name); 090 outJson = context.resolveDataFormat(name); 091 092 if (json != null) { 093 setupJson(context, config, definition.getType(), definition.getOutType(), json, outJson); 094 } 095 } 096 097 // setup xml data format 098 DataFormat jaxb = null; 099 DataFormat outJaxb = null; 100 if (mode.contains("xml") || "auto".equals(mode)) { 101 String name = config.getXmlDataFormat(); 102 if (name != null) { 103 // must only be a name, not refer to an existing instance 104 Object instance = context.getRegistry().lookupByName(name); 105 if (instance != null) { 106 throw new IllegalArgumentException("XmlDataFormat name: " + name + " must not be an existing bean instance from the registry"); 107 } 108 } else { 109 name = "jaxb"; 110 } 111 // this will create a new instance as the name was not already 112 // pre-created 113 jaxb = context.resolveDataFormat(name); 114 outJaxb = context.resolveDataFormat(name); 115 116 // is xml binding required? 117 if (mode.contains("xml") && jaxb == null) { 118 throw new IllegalArgumentException("XML DataFormat " + name + " not found."); 119 } 120 121 if (jaxb != null) { 122 setupJaxb(context, config, definition.getType(), definition.getOutType(), jaxb, outJaxb); 123 } 124 } 125 126 return new RestBindingAdvice(context, json, jaxb, outJson, outJaxb, definition.getConsumes(), definition.getProduces(), mode, skip, validation, cors, corsHeaders, 127 definition.getDefaultValues(), definition.getRequiredBody() != null ? definition.getRequiredBody() : false, 128 definition.getRequiredQueryParameters(), definition.getRequiredHeaders()); 129 } 130 131 protected void setupJson(CamelContext context, RestConfiguration config, String type, String outType, DataFormat json, DataFormat outJson) throws Exception { 132 Class<?> clazz = null; 133 if (type != null) { 134 String typeName = type.endsWith("[]") ? type.substring(0, type.length() - 2) : type; 135 clazz = context.getClassResolver().resolveMandatoryClass(typeName); 136 } 137 if (clazz != null) { 138 context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, json, "unmarshalType", clazz); 139 context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, json, "useList", type.endsWith("[]")); 140 } 141 setAdditionalConfiguration(config, context, json, "json.in."); 142 143 Class<?> outClazz = null; 144 if (outType != null) { 145 String typeName = outType.endsWith("[]") ? outType.substring(0, outType.length() - 2) : outType; 146 outClazz = context.getClassResolver().resolveMandatoryClass(typeName); 147 } 148 if (outClazz != null) { 149 context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, outJson, "unmarshalType", outClazz); 150 context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, outJson, "useList", outType.endsWith("[]")); 151 } 152 setAdditionalConfiguration(config, context, outJson, "json.out."); 153 } 154 155 protected void setupJaxb(CamelContext context, RestConfiguration config, String type, String outType, DataFormat jaxb, DataFormat outJaxb) throws Exception { 156 Class<?> clazz = null; 157 if (type != null) { 158 String typeName = type.endsWith("[]") ? type.substring(0, type.length() - 2) : type; 159 clazz = context.getClassResolver().resolveMandatoryClass(typeName); 160 } 161 if (clazz != null) { 162 JAXBContext jc = JAXBContext.newInstance(clazz); 163 context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, jaxb, "context", jc); 164 } 165 setAdditionalConfiguration(config, context, jaxb, "xml.in."); 166 167 Class<?> outClazz = null; 168 if (outType != null) { 169 String typeName = outType.endsWith("[]") ? outType.substring(0, outType.length() - 2) : outType; 170 outClazz = context.getClassResolver().resolveMandatoryClass(typeName); 171 } 172 if (outClazz != null) { 173 JAXBContext jc = JAXBContext.newInstance(outClazz); 174 context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, outJaxb, "context", jc); 175 } else if (clazz != null) { 176 // fallback and use the context from the input 177 JAXBContext jc = JAXBContext.newInstance(clazz); 178 context.adapt(ExtendedCamelContext.class).getBeanIntrospection().setProperty(context, outJaxb, "context", jc); 179 } 180 setAdditionalConfiguration(config, context, outJaxb, "xml.out."); 181 } 182 183 private void setAdditionalConfiguration(RestConfiguration config, CamelContext context, DataFormat dataFormat, String prefix) throws Exception { 184 if (config.getDataFormatProperties() != null && !config.getDataFormatProperties().isEmpty()) { 185 // must use a copy as otherwise the options gets removed during 186 // introspection setProperties 187 Map<String, Object> copy = new HashMap<>(); 188 189 // filter keys on prefix 190 // - either its a known prefix and must match the prefix parameter 191 // - or its a common configuration that we should always use 192 for (Map.Entry<String, Object> entry : config.getDataFormatProperties().entrySet()) { 193 String key = entry.getKey(); 194 String copyKey; 195 boolean known = isKeyKnownPrefix(key); 196 if (known) { 197 // remove the prefix from the key to use 198 copyKey = key.substring(prefix.length()); 199 } else { 200 // use the key as is 201 copyKey = key; 202 } 203 if (!known || key.startsWith(prefix)) { 204 copy.put(copyKey, entry.getValue()); 205 } 206 } 207 208 PropertyBindingSupport.build().bind(context, dataFormat, copy); 209 } 210 } 211 212 private boolean isKeyKnownPrefix(String key) { 213 return key.startsWith("json.in.") || key.startsWith("json.out.") || key.startsWith("xml.in.") || key.startsWith("xml.out."); 214 } 215 216}