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 */
017 package org.apache.servicemix.cxfse;
018
019 import java.io.IOException;
020 import java.lang.reflect.Field;
021 import java.lang.reflect.Method;
022 import java.util.HashMap;
023 import java.util.List;
024 import java.util.Map;
025 import java.util.concurrent.CopyOnWriteArrayList;
026
027 import javax.annotation.PostConstruct;
028 import javax.annotation.PreDestroy;
029 import javax.jbi.component.ComponentContext;
030 import javax.jbi.management.DeploymentException;
031 import javax.jbi.messaging.DeliveryChannel;
032 import javax.jbi.messaging.ExchangeStatus;
033 import javax.jbi.messaging.InOnly;
034 import javax.jbi.messaging.MessageExchange;
035 import javax.jbi.messaging.NormalizedMessage;
036 import javax.jbi.messaging.RobustInOnly;
037 import javax.wsdl.WSDLException;
038 import javax.wsdl.factory.WSDLFactory;
039 import javax.xml.namespace.QName;
040 import javax.xml.parsers.ParserConfigurationException;
041 import javax.xml.transform.TransformerException;
042 import javax.xml.ws.WebServiceRef;
043 import org.w3c.dom.Element;
044 import org.xml.sax.SAXException;
045 import org.apache.cxf.Bus;
046 import org.apache.cxf.aegis.databinding.AegisDatabinding;
047 import org.apache.cxf.binding.soap.SoapMessage;
048 import org.apache.cxf.binding.soap.SoapVersion;
049 import org.apache.cxf.binding.soap.model.SoapBindingInfo;
050 import org.apache.cxf.endpoint.Server;
051 import org.apache.cxf.frontend.ServerFactoryBean;
052 import org.apache.cxf.interceptor.Fault;
053 import org.apache.cxf.interceptor.Interceptor;
054 import org.apache.cxf.interceptor.InterceptorProvider;
055 import org.apache.cxf.jaxws.EndpointImpl;
056 import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
057 import org.apache.cxf.jaxws.ServiceImpl;
058 import org.apache.cxf.jaxws.support.JaxWsImplementorInfo;
059 import org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean;
060 import org.apache.cxf.message.Message;
061 import org.apache.cxf.service.model.BindingOperationInfo;
062 import org.apache.cxf.service.model.EndpointInfo;
063 import org.apache.cxf.transport.ConduitInitiatorManager;
064 import org.apache.cxf.transport.jbi.JBIDestination;
065 import org.apache.cxf.transport.jbi.JBIDispatcherUtil;
066 import org.apache.cxf.transport.jbi.JBITransportFactory;
067 import org.apache.cxf.wsdl11.ServiceWSDLBuilder;
068 import org.apache.servicemix.common.endpoints.ProviderEndpoint;
069 import org.apache.servicemix.cxfse.interceptors.AttachmentInInterceptor;
070 import org.apache.servicemix.cxfse.interceptors.AttachmentOutInterceptor;
071 import org.apache.servicemix.cxfse.support.ReflectionUtils;
072 import org.apache.servicemix.id.IdGenerator;
073 import org.apache.servicemix.jbi.jaxp.SourceTransformer;
074 import org.apache.servicemix.soap.util.DomUtil;
075 import org.springframework.util.ReflectionUtils.FieldCallback;
076
077
078 /**
079 *
080 * @author gnodet
081 * @org.apache.xbean.XBean element="endpoint" description="an endpoint using
082 * CXF's JAX-WS frontend"
083 */
084 public class CxfSeEndpoint extends ProviderEndpoint implements
085 InterceptorProvider {
086
087 private static final IdGenerator ID_GENERATOR = new IdGenerator();
088
089 private Object pojo;
090
091 private EndpointImpl endpoint;
092
093 private String address;
094
095 private ServerFactoryBean sf;
096
097 private List<Interceptor> in = new CopyOnWriteArrayList<Interceptor>();
098
099 private List<Interceptor> out = new CopyOnWriteArrayList<Interceptor>();
100
101 private List<Interceptor> outFault = new CopyOnWriteArrayList<Interceptor>();
102
103 private List<Interceptor> inFault = new CopyOnWriteArrayList<Interceptor>();
104
105 private Map properties;
106
107 private Server server;
108
109 private boolean mtomEnabled;
110
111 private boolean schemaValidationEnabled;
112
113 private boolean useJBIWrapper = true;
114
115 private boolean useSOAPEnvelope = true;
116
117 private boolean useAegis;
118
119 private QName pojoService;
120 private String pojoEndpoint;
121 private QName pojoInterfaceName;
122
123 private Server soapBindingServer;
124 /**
125 * Returns the object implementing the endpoint's functionality. It is
126 * returned as a generic Java <code>Object</code> that can be cast to the
127 * proper type.
128 *
129 * @return the pojo
130 */
131 public Object getPojo() {
132 return pojo;
133 }
134
135 /**
136 * Specifies the object implementing the endpoint's functionality. This
137 * object should use the JAX-WS annotations.
138 *
139 * @param pojo
140 * a JAX-WS annotated object
141 *
142 * @org.apache.xbean.Property description="a bean configuring the JAX-WS
143 * annotated implementation for the endpoint"
144 */
145 public void setPojo(Object pojo) {
146 this.pojo = pojo;
147 }
148
149 /**
150 * Returns the list of interceptors used to process fault messages being
151 * sent back to the consumer.
152 *
153 * @return a list of <code>Interceptor</code> objects
154 */
155 public List<Interceptor> getOutFaultInterceptors() {
156 return outFault;
157 }
158
159 /**
160 * Returns the list of interceptors used to process fault messages being
161 * recieved by the endpoint.
162 *
163 * @return a list of <code>Interceptor</code> objects
164 */
165 public List<Interceptor> getInFaultInterceptors() {
166 return inFault;
167 }
168
169 /**
170 * Returns the list of interceptors used to process messages being recieved
171 * by the endpoint.
172 *
173 * @return a list of <code>Interceptor</code> objects
174 */
175 public List<Interceptor> getInInterceptors() {
176 return in;
177 }
178
179 /**
180 * Returns the list of interceptors used to process responses being sent
181 * back to the consumer.
182 *
183 * @return a list of <code>Interceptor</code> objects
184 */
185 public List<Interceptor> getOutInterceptors() {
186 return out;
187 }
188
189 /**
190 * Specifies a list of interceptors used to process requests recieved by the
191 * endpoint.
192 *
193 * @param interceptors
194 * a list of <code>Interceptor</code> objects
195 * @org.apache.xbean.Property description="a list of beans configuring
196 * interceptors that process incoming requests"
197 */
198 public void setInInterceptors(List<Interceptor> interceptors) {
199 in.addAll(interceptors);
200 }
201
202 /**
203 * Specifies a list of interceptors used to process faults recieved by the
204 * endpoint.
205 *
206 * @param interceptors
207 * a list of <code>Interceptor</code> objects
208 * @org.apache.xbean.Property description="a list of beans configuring
209 * interceptors that process incoming faults"
210 */
211 public void setInFaultInterceptors(List<Interceptor> interceptors) {
212 inFault.addAll(interceptors);
213 }
214
215 /**
216 * Specifies a list of interceptors used to process responses sent by the
217 * endpoint.
218 *
219 * @param interceptors
220 * a list of <code>Interceptor</code> objects
221 * @org.apache.xbean.Property description="a list of beans configuring
222 * interceptors that process response messages"
223 */
224 public void setOutInterceptors(List<Interceptor> interceptors) {
225 out.addAll(interceptors);
226 }
227
228 /**
229 * Specifies a list of interceptors used to process faults sent by the
230 * endpoint.
231 *
232 * @param interceptors
233 * a list of <code>Interceptor</code> objects
234 * @org.apache.xbean.Property description="a list of beans configuring
235 * interceptors that process fault messages being
236 * returned to the consumer"
237 */
238 public void setOutFaultInterceptors(List<Interceptor> interceptors) {
239 outFault.addAll(interceptors);
240 }
241
242 public Map getProperties() {
243 return properties;
244 }
245
246 public void setProperties(Map properties) {
247 this.properties = properties;
248 }
249
250 /*
251 * (non-Javadoc)
252 *
253 * @see org.apache.servicemix.common.Endpoint#validate()
254 */
255 @Override
256 public void validate() throws DeploymentException {
257 if (getPojo() == null) {
258 throw new DeploymentException("pojo must be set");
259 }
260 if (isUseAegis()) {
261 sf = new ServerFactoryBean();
262 sf.setServiceBean(getPojo());
263 sf.setAddress("jbi://" + ID_GENERATOR.generateSanitizedId());
264 sf.getServiceFactory().setDataBinding(new AegisDatabinding());
265 sf.getServiceFactory().setPopulateFromClass(true);
266 sf.setStart(false);
267 if (isUseJBIWrapper()) {
268 sf.setBindingId(org.apache.cxf.binding.jbi.JBIConstants.NS_JBI_BINDING);
269 }
270 server = sf.create();
271 server.getEndpoint().getInInterceptors().addAll(getInInterceptors());
272 server.getEndpoint().getInFaultInterceptors().addAll(getInFaultInterceptors());
273 server.getEndpoint().getOutInterceptors().addAll(getOutInterceptors());
274 server.getEndpoint().getOutFaultInterceptors().addAll(getOutFaultInterceptors());
275 if (isMtomEnabled()) {
276 server.getEndpoint().getInInterceptors().add(new AttachmentInInterceptor());
277 server.getEndpoint().getOutInterceptors().add(new AttachmentOutInterceptor());
278 }
279
280 if (sf.getServiceFactory().getServiceQName() != null) {
281 setPojoService(sf.getServiceFactory().getServiceQName());
282 if (getService() == null) {
283 setService(sf.getServiceFactory().getServiceQName());
284 }
285 }
286 if (sf.getServiceFactory().getEndpointInfo().getName() != null) {
287 setPojoEndpoint(sf.getServiceFactory().getEndpointInfo().getName().getLocalPart());
288 if (getEndpoint() == null) {
289 setEndpoint(sf.getServiceFactory().getEndpointInfo().getName().getLocalPart());
290 }
291 }
292 if (sf.getServiceFactory().getInterfaceName() != null) {
293 setPojoInterfaceName(sf.getServiceFactory().getInterfaceName());
294 if (getInterfaceName() == null) {
295 setInterfaceName(sf.getServiceFactory().getInterfaceName());
296 }
297 }
298 } else {
299 JaxWsServiceFactoryBean serviceFactory = new JaxWsServiceFactoryBean();
300 serviceFactory.setPopulateFromClass(true);
301 endpoint = new EndpointImpl(getBus(), getPojo(),
302 new JaxWsServerFactoryBean(serviceFactory));
303 if (isUseJBIWrapper()) {
304 endpoint
305 .setBindingUri(org.apache.cxf.binding.jbi.JBIConstants.NS_JBI_BINDING);
306 }
307 endpoint.setInInterceptors(getInInterceptors());
308 endpoint.setInFaultInterceptors(getInFaultInterceptors());
309 endpoint.setOutInterceptors(getOutInterceptors());
310 endpoint.setOutFaultInterceptors(getOutFaultInterceptors());
311 if (isMtomEnabled()) {
312 endpoint.getInInterceptors().add(new AttachmentInInterceptor());
313 endpoint.getOutInterceptors().add(new AttachmentOutInterceptor());
314 }
315 if (isSchemaValidationEnabled()) {
316 if (endpoint.getProperties() == null) {
317 endpoint.setProperties(new HashMap<String, Object>());
318 }
319 endpoint.getProperties().put(org.apache.cxf.message.Message.SCHEMA_VALIDATION_ENABLED, true);
320 }
321
322 JaxWsImplementorInfo implInfo = new JaxWsImplementorInfo(getPojo()
323 .getClass());
324 setPojoService(implInfo.getServiceName());
325 setPojoInterfaceName(implInfo.getInterfaceName());
326 setPojoEndpoint(implInfo.getEndpointName().getLocalPart());
327 if (getService() == null) {
328 setService(implInfo.getServiceName());
329 }
330 if (getInterfaceName() == null) {
331 setInterfaceName(implInfo.getInterfaceName());
332 }
333 if (getEndpoint() == null) {
334 setEndpoint(implInfo.getEndpointName().getLocalPart());
335 }
336
337 }
338 super.validate();
339 }
340
341 private void removeInterceptor(List<Interceptor> interceptors,
342 String whichInterceptor) {
343 for (Interceptor interceptor : interceptors) {
344 if (interceptor.getClass().getName().endsWith(whichInterceptor)) {
345 interceptors.remove(interceptor);
346 }
347 }
348 }
349
350 /*
351 * (non-Javadoc)
352 *
353 * @see org.apache.servicemix.common.endpoints.ProviderEndpoint#process(javax.jbi.messaging.MessageExchange)
354 */
355 @Override
356 public void process(MessageExchange exchange) throws Exception {
357
358 if (exchange.getStatus() != ExchangeStatus.ACTIVE) {
359 return;
360 }
361 QName opeName = exchange.getOperation();
362 EndpointInfo ei = server.getEndpoint().getEndpointInfo();
363
364 if (opeName == null) {
365 // if interface only have one operation, may not specify the opeName
366 // in MessageExchange
367 if (ei.getBinding().getOperations().size() == 1) {
368 opeName = ei.getBinding().getOperations().iterator().next()
369 .getName();
370 exchange.setOperation(opeName);
371 } else {
372 NormalizedMessage nm = exchange.getMessage("in");
373 if (soapBindingServer == null) {
374 ServerFactoryBean sfForSoapBinding = new ServerFactoryBean();
375 sfForSoapBinding.setServiceBean(getPojo());
376 sfForSoapBinding.getServiceFactory().setPopulateFromClass(true);
377 sfForSoapBinding.setStart(false);
378 sfForSoapBinding.setAddress("local://dummy");
379 soapBindingServer = sfForSoapBinding.create();
380 }
381 Message message = soapBindingServer.getEndpoint().getBinding().createMessage();
382 opeName = findOperation(nm, message, exchange);
383 exchange.setOperation(opeName);
384 }
385 }
386 JBITransportFactory jbiTransportFactory = (JBITransportFactory) getBus()
387 .getExtension(ConduitInitiatorManager.class)
388 .getConduitInitiator(CxfSeComponent.JBI_TRANSPORT_ID);
389
390 exchange.setService(getPojoService());
391 exchange.setInterfaceName(getPojoInterfaceName());
392 JBIDestination jbiDestination = jbiTransportFactory
393 .getDestination(getPojoService().toString()
394 + getPojoInterfaceName().toString());
395 DeliveryChannel dc = getContext().getDeliveryChannel();
396 jbiTransportFactory.setDeliveryChannel(dc);
397
398 jbiDestination.setDeliveryChannel(dc);
399 if (exchange.getStatus() == ExchangeStatus.ACTIVE) {
400 jbiDestination.getJBIDispatcherUtil().dispatch(exchange);
401 }
402 if (exchange instanceof InOnly || exchange instanceof RobustInOnly) {
403 exchange.setStatus(ExchangeStatus.DONE);
404 dc.send(exchange);
405 }
406 }
407
408 private QName findOperation(NormalizedMessage nm,
409 Message message, MessageExchange exchange)
410 throws TransformerException, ParserConfigurationException,
411 IOException, SAXException {
412 // try to figure out the operationName based on the incoming message
413 // payload and wsdl if use doc/literal/wrapped
414 Element element = new SourceTransformer().toDOMElement(nm.getContent());
415
416 if (!useJBIWrapper) {
417 SoapVersion soapVersion = ((SoapMessage) message).getVersion();
418 if (element != null) {
419 Element bodyElement = (Element) element.getElementsByTagNameNS(
420 element.getNamespaceURI(),
421 soapVersion.getBody().getLocalPart()).item(0);
422 if (bodyElement != null) {
423 element = (Element) bodyElement.getFirstChild();
424 }
425 }
426 } else {
427 element = DomUtil.getFirstChildElement(DomUtil
428 .getFirstChildElement(element));
429 }
430
431 QName opeName = new QName(element.getNamespaceURI(), element
432 .getLocalName());
433 SoapBindingInfo binding = (SoapBindingInfo) soapBindingServer.getEndpoint().getEndpointInfo().getBinding();
434 for (BindingOperationInfo op : binding.getOperations()) {
435 String style = binding.getStyle(op.getOperationInfo());
436 if (style == null) {
437 style = binding.getStyle();
438 }
439 if ("document".equals(style)) {
440 if (op.getName().getLocalPart().equals(opeName.getLocalPart())) {
441 return new QName(getPojoService().getNamespaceURI(),
442 opeName.getLocalPart());
443 }
444 } else {
445 throw new Fault(
446 new Exception(
447 "Operation must bound on this MessageExchange if use rpc mode"));
448 }
449 }
450 throw new Fault(new Exception("Operation not bound on this MessageExchange"));
451
452 }
453
454 /*
455 * (non-Javadoc)
456 *
457 * @see org.apache.servicemix.common.endpoints.ProviderEndpoint#start()
458 */
459 @Override
460 public void start() throws Exception {
461 super.start();
462 address = "jbi://" + ID_GENERATOR.generateSanitizedId();
463 try {
464 if (isUseAegis()) {
465 server.start();
466 } else {
467 endpoint.publish(address);
468 server = endpoint.getServer();
469 }
470 } catch (Exception e) {
471 e.printStackTrace();
472 }
473 if (getService() == null) {
474 setService(server.getEndpoint().getService().getName());
475 }
476 if (getEndpoint() == null) {
477 setEndpoint(server.getEndpoint().getEndpointInfo()
478 .getName().getLocalPart());
479 }
480 setPojoService(server.getEndpoint().getService().getName());
481 setPojoEndpoint(server.getEndpoint().getEndpointInfo()
482 .getName().getLocalPart());
483 if (!isUseJBIWrapper() && !isUseSOAPEnvelope()) {
484 removeInterceptor(server.getEndpoint().getBinding()
485 .getInInterceptors(), "ReadHeadersInterceptor");
486 removeInterceptor(server.getEndpoint().getBinding()
487 .getInFaultInterceptors(), "ReadHeadersInterceptor");
488 removeInterceptor(server.getEndpoint().getBinding()
489 .getOutInterceptors(), "SoapOutInterceptor");
490 removeInterceptor(server.getEndpoint().getBinding()
491 .getOutFaultInterceptors(), "SoapOutInterceptor");
492 removeInterceptor(server.getEndpoint().getBinding()
493 .getOutInterceptors(), "StaxOutInterceptor");
494 }
495
496 try {
497 definition = new ServiceWSDLBuilder(getBus(), server
498 .getEndpoint().getService().getServiceInfos().iterator()
499 .next()).build();
500 description = WSDLFactory.newInstance().newWSDLWriter()
501 .getDocument(definition);
502 } catch (WSDLException e) {
503 throw new DeploymentException(e);
504 }
505 ReflectionUtils.doWithFields(getPojo().getClass(), new FieldCallback() {
506 public void doWith(Field field) throws IllegalArgumentException,
507 IllegalAccessException {
508 if (field.getAnnotation(WebServiceRef.class) != null) {
509 ServiceImpl s = new ServiceImpl(getBus(), null, null, field
510 .getType());
511 s.addPort(new QName("port"),
512 JBITransportFactory.TRANSPORT_ID, "jbi://"
513 + ID_GENERATOR.generateSanitizedId());
514 Object o = s.getPort(new QName("port"), field.getType());
515 field.setAccessible(true);
516 field.set(getPojo(), o);
517 }
518 }
519 });
520 ReflectionUtils.callLifecycleMethod(getPojo(), PostConstruct.class);
521 injectPojo();
522 }
523
524 /*
525 * (non-Javadoc)
526 *
527 * @see org.apache.servicemix.common.endpoints.ProviderEndpoint#stop()
528 */
529 @Override
530 public void stop() throws Exception {
531 if (isUseAegis()) {
532 server.stop();
533 } else {
534 endpoint.stop();
535 }
536 ReflectionUtils.callLifecycleMethod(getPojo(), PreDestroy.class);
537 JBIDispatcherUtil.clean();
538 JBITransportFactory jbiTransportFactory = (JBITransportFactory) getBus()
539 .getExtension(ConduitInitiatorManager.class)
540 .getConduitInitiator(CxfSeComponent.JBI_TRANSPORT_ID);
541 jbiTransportFactory.setDeliveryChannel(null);
542 jbiTransportFactory.removeDestination(getPojoService().toString()
543 + getPojoInterfaceName().toString());
544 super.stop();
545 }
546
547 protected Bus getBus() {
548 return ((CxfSeComponent) getServiceUnit().getComponent()).getBus();
549 }
550
551 @PostConstruct
552 protected void injectPojo() {
553 try {
554 ComponentContext context = getContext();
555 Method mth = pojo.getClass().getMethod("setContext",
556 new Class[] {ComponentContext.class});
557 if (mth != null) {
558 mth.invoke(pojo, new Object[] {context});
559 }
560 } catch (Exception e) {
561 //setContext is optional for the pojo
562 }
563
564 }
565
566 /**
567 * Specifies if the endpoint can process messages with binary data.
568 *
569 * @param mtomEnabled
570 * a <code>boolean</code>
571 * @org.apache.xbean.Property description="Specifies if the service can
572 * consume MTOM formatted binary data. The
573 * default is <code>false</code>."
574 */
575 public void setMtomEnabled(boolean mtomEnabled) {
576 this.mtomEnabled = mtomEnabled;
577 }
578
579 public boolean isMtomEnabled() {
580 return mtomEnabled;
581 }
582
583 public boolean isSchemaValidationEnabled() {
584 return schemaValidationEnabled;
585 }
586
587 public void setSchemaValidationEnabled(boolean schemaValidationEnabled) {
588 this.schemaValidationEnabled = schemaValidationEnabled;
589 }
590
591 /**
592 * Specifies if the endpoint expects messages that are encased in the JBI
593 * wrapper used for SOAP messages.
594 *
595 * @param mtomEnabled
596 * a <code>boolean</code>
597 * @org.apache.xbean.Property description="Specifies if the endpoint expects
598 * to receive the JBI wrapper in the message
599 * received from the NMR. The default is
600 * <code>true</code>."
601 */
602 public void setUseJBIWrapper(boolean useJBIWrapper) {
603 this.useJBIWrapper = useJBIWrapper;
604 }
605
606 public boolean isUseJBIWrapper() {
607 return useJBIWrapper;
608 }
609
610 /**
611 * Specifies if the endpoint expects soap messages when useJBIWrapper is
612 * false, if useJBIWrapper is true then ignore useSOAPEnvelope
613 *
614 * @org.apache.xbean.Property description="Specifies if the endpoint expects
615 * soap messages when useJBIWrapper is false, if
616 * useJBIWrapper is true then ignore
617 * useSOAPEnvelope. The default is
618 * <code>true</code>.
619 */
620 public void setUseSOAPEnvelope(boolean useSOAPEnvelope) {
621 this.useSOAPEnvelope = useSOAPEnvelope;
622 }
623
624 public boolean isUseSOAPEnvelope() {
625 return useSOAPEnvelope;
626 }
627
628 /**
629 * Specifies if the endpoint use aegis databinding to marshell/unmarshell message
630 *
631 * @org.apache.xbean.Property description="Specifies if the endpoint use aegis databinding to marshell/unmarshell message.
632 * The default is <code>false</code>.
633 */
634 public void setUseAegis(boolean useAegis) {
635 this.useAegis = useAegis;
636 }
637
638
639 public boolean isUseAegis() {
640 return useAegis;
641 }
642
643 protected void setPojoService(QName pojoService) {
644 this.pojoService = pojoService;
645 }
646
647 protected QName getPojoService() {
648 return pojoService;
649 }
650
651 protected void setPojoEndpoint(String pojoEndpoint) {
652 this.pojoEndpoint = pojoEndpoint;
653 }
654
655 protected String getPojoEndpoint() {
656 return pojoEndpoint;
657 }
658
659 protected void setPojoInterfaceName(QName pojoInterfaceName) {
660 this.pojoInterfaceName = pojoInterfaceName;
661 }
662
663 protected QName getPojoInterfaceName() {
664 return pojoInterfaceName;
665 }
666
667 }