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