/**
* Copyright 2014 Comcast Cable Communications Management, LLC
*
* This file is part of CATS.
*
* CATS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CATS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CATS. If not, see <http://www.gnu.org/licenses/>.
*/
package com.comcast.cats;
import static javax.xml.ws.Service.create;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import com.comcast.cats.configuration.ApplicationContextProvider;
import com.comcast.cats.configuration.StringsDMHandler;
import com.comcast.cats.domain.Environment;
import com.comcast.cats.domain.RFPlant;
import com.comcast.cats.domain.Server;
import com.comcast.cats.domain.Service;
import com.comcast.cats.domain.ServiceType;
import com.comcast.cats.domain.configuration.CatsProperties;
import com.comcast.cats.domain.exception.DomainNotFoundException;
import com.comcast.cats.domain.exception.ServiceInstantiationException;
import com.comcast.cats.domain.service.EnvironmentService;
import com.comcast.cats.domain.util.AssertUtil;
import com.comcast.cats.event.CatsEventDispatcher;
import com.comcast.cats.event.TraceEventDispatcher;
import com.comcast.cats.info.SettopInfo;
import com.comcast.cats.provider.exceptions.ProviderCreationException;
import com.comcast.cats.provider.factory.AudioProviderFactory;
import com.comcast.cats.provider.factory.ImageCompareProviderFactory;
import com.comcast.cats.provider.factory.OcrProviderFactory;
import com.comcast.cats.provider.factory.PowerProviderFactory;
import com.comcast.cats.provider.factory.RFControlProviderFactory;
import com.comcast.cats.provider.factory.RecorderProviderFactory;
import com.comcast.cats.provider.factory.RemoteProviderFactory;
import com.comcast.cats.provider.factory.TraceProviderFactory;
import com.comcast.cats.provider.factory.VideoProviderFactory;
import com.comcast.cats.provider.factory.impl.ImageCompareProviderFactoryImpl;
import com.comcast.cats.provider.factory.impl.PowerProviderFactoryImpl;
import com.comcast.cats.provider.factory.impl.RecorderProviderFactoryImpl;
import com.comcast.cats.provider.factory.impl.RemoteProviderFactoryImpl;
import com.comcast.cats.provider.factory.impl.VideoProviderFactoryImpl;
import com.comcast.cats.service.CatsWebService;
import com.comcast.cats.service.IRService;
import com.comcast.cats.service.IRServiceConstants;
import com.comcast.cats.service.PowerService;
import com.comcast.cats.service.PowerServiceConstants;
/**
* Represents a CATS <code>Environment</code> , which effectively owns a series
* of Racks and consequently a set of settops and servers. , which in turn
* contains a <code>List</code> of <code>Service</code>.
*
* Bean Scope is set to <code>prototype</code>, which means, Spring context will
* return a new instance of this bean for every lookup.
*
* @author subinsugunan
*
* @see Environment
* @see Server
* @see Service
* @see ApplicationContextProvider
*/
@Named
@Scope( "prototype" )
public class CatsEnvironment
{
private EnvironmentService envService;
private StringsDMHandler stringsDMHandler;
private TraceEventDispatcher traceDispatcher;
private CatsEventDispatcher dispatcher;
private TraceProviderFactory traceProviderFactory;
private VideoProviderFactory videoProviderFactory;
private ImageCompareProviderFactory imageCompareProviderFactory;
private RemoteProviderFactory remoteProviderFactory;
private PowerProviderFactory powerProviderFactory;
private AudioProviderFactory audioProviderFactory;
private OcrProviderFactory ocrProviderFactory;
private RecorderProviderFactory recorderProviderFactory;
private RFControlProviderFactory rfProviderFactory = null;
private CatsProperties catsProperties;
private static final String REQUEST_TIMEOUT_PROPERTY = "com.sun.xml.internal.ws.request.timeout";
private static final String CONNECT_TIMEOUT_PROPERTY = "com.sun.xml.internal.ws.connect.timeout";
protected static final int DEFAULT_REQUEST_TIMEOUT = 30 * 60 * 1000; //30 minutes
protected static final int DEFAULT_CONNECT_TIMEOUT = 10 * 60 * 1000; //10 minutes
private static final String CONNECT_TIMOEUT_TYPE = "connect";
private static final String REQUEST_TIMOEUT_TYPE = "request";
private String timeoutPropertyPattern = "cats.service.serviceType.timeoutType.timeout";
private boolean isProxyCreated = false;
private final Logger LOGGER = LoggerFactory.getLogger( getClass() );
/**
* Constructor
*
* @param envService
* {@linkplain EnvironmentService}
* @param stringsDMHandler
* {@linkplain StringsDMHandler}
* @param traceDispatcher
* {@linkplain TraceEventDispatcher}
* @param dispatcher
* {@linkplain CatsEventDispatcher}
*/
@Inject
public CatsEnvironment( EnvironmentService envService, StringsDMHandler stringsDMHandler,
TraceEventDispatcher traceDispatcher, CatsEventDispatcher dispatcher,
CatsProperties catsProperties)
{
this.envService = envService;
this.stringsDMHandler = stringsDMHandler;
this.traceDispatcher = traceDispatcher;
this.dispatcher = dispatcher;
this.catsProperties = catsProperties;
}
/**
* To wire the settop with minimal services.
*
* @param settop
* {@linkplain SettopImpl}
* @throws {@linkplain ProviderCreationException}
*/
public void wireSettop( SettopImpl settop ) throws ProviderCreationException
{
AssertUtil.isNullObject( settop );
AssertUtil.isNullObject( settop.getSettopInfo() );
AssertUtil.isNullOrEmpty( settop.getEnvironmentId() );
if ( !isProxyCreated() )
{
/**
* If control comes here it means that the environment was not
* initialized. Note: if there are 10 settops on a given
* environment, this will happen only once if the first try itself
* succeeds, else it will happen 10 times(worst case).
*
* also if the first init was only able to init minimal services,
* all subsequent settops also will get only minimal services.
*/
try
{
initEnvironment( settop.getEnvironmentId() );
}
catch ( ServiceInstantiationException e )
{
LOGGER.error( " Init of environment has errors " + e.getMessage() );
}
finally
{
// this check is to allow catsvision to come up if at least IR
// and video provider is initialized
// in the environment.
if ( remoteProviderFactory == null || videoProviderFactory == null )
{
throw new ProviderCreationException( "Could not initialize minimal services IR and Video" );
}
// if the exception is not thrown,it means we have minimal
// services.
setProxyCreated( true );
}
}
settop.setVideo( ( null == videoProviderFactory ) ? null : videoProviderFactory
.getProvider( ( SettopInfo ) settop ) );
settop.setImageCompareProvider( ( null == imageCompareProviderFactory ) ? null : imageCompareProviderFactory
.getProvider( ( SettopInfo ) settop ) );
settop.setRemote( ( null == remoteProviderFactory ) ? null : remoteProviderFactory
.getProvider( ( SettopInfo ) settop ) );
settop.setPower( ( null == powerProviderFactory ) ? null : powerProviderFactory
.getProvider( ( SettopInfo ) settop ) );
settop.setTrace( ( null == traceProviderFactory ) ? null : traceProviderFactory
.getProvider( ( SettopInfo ) settop ) );
settop.setAudio( ( null == audioProviderFactory ) ? null : audioProviderFactory
.getProvider( ( SettopInfo ) settop ) );
settop.setRfControl( ( null == rfProviderFactory ) ? null : rfProviderFactory.getProvider( settop ));
try
{
settop.setOCRProvider( ( null == ocrProviderFactory ) ? null : ocrProviderFactory.getProvider(
( SettopInfo ) settop, new URI( settop.getVideo().getVideoURL() ) ) );
}
catch ( URISyntaxException uriSyntaxException )
{
throw new ProviderCreationException( "Failed to override OCR service interface for [MAC]["
+ settop.getHostMacAddress() + "]. [URISyntaxException] Make sure videoURL is valid."
+ uriSyntaxException.getMessage() );
}
settop.setRecorderProvider( ( null == recorderProviderFactory ) ? null : recorderProviderFactory
.getProvider( ( SettopInfo ) settop ) );
}
private void initEnvironment( String environmentId ) throws ServiceInstantiationException
{
try
{
/**
* expecting no exception or errors in the following 3 object
* creations.
*/
videoProviderFactory = new VideoProviderFactoryImpl( dispatcher );
imageCompareProviderFactory = new ImageCompareProviderFactoryImpl( videoProviderFactory );
Environment environment = envService.findById( environmentId );
List< Server > servers = environment.getServers();
if ( ( null != servers ) && ( !servers.isEmpty() ) )
{
for ( Server server : servers )
{
List< Service > services = server.getServices();
if ( ( null != services ) && ( !services.isEmpty() ) )
{
for ( Service service : services )
{
createServiceProxy( service );
}
}
else
{
LOGGER.error( "No services found on server= " + server );
// throw new ServiceInstantiationException(
// "No services found in server= " + server );
}
createRestFullServiceProxies(server);
}
}
else
{
throw new ServiceInstantiationException( "No servers found in environment = " + environment );
}
}
catch ( DomainNotFoundException e )
{
throw new ServiceInstantiationException( e.getMessage() );
}
}
private void createRestFullServiceProxies(Server server)
{
if(server != null){
String catsServer = server.getHost();
if(catsServer != null && server.getPort() > 0 ){
catsServer += ":"+server.getPort();
}
LOGGER.info( "Creating proxy for RF Service: catsServer "+catsServer );
}
}
private void configureService( CatsWebService service, ServiceType serviceType )
{
LOGGER.trace( "configuring service "+service );
int requestTimeout = getTimeoutValue(serviceType.toString().toLowerCase(), REQUEST_TIMOEUT_TYPE);
int connectTimeout = getTimeoutValue(serviceType.toString().toLowerCase(), CONNECT_TIMOEUT_TYPE);
LOGGER.info( "Request Timeout for "+serviceType+" is "+requestTimeout);
LOGGER.info( "Connect Timeout for "+serviceType+" is "+connectTimeout );
((BindingProvider)service).getRequestContext().put(REQUEST_TIMEOUT_PROPERTY, requestTimeout );
((BindingProvider)service).getRequestContext().put(CONNECT_TIMEOUT_PROPERTY, connectTimeout );
}
/**
* Gets the timeoutValue by walking through the 'property tree' to find if any values has to be set
* to the webservices.
*
* Please see {@link https://rally1.rallydev.com/#/4843363647d/detail/task/10627019604}
*
* @param serviceType
* @param timeoutType
* @return
*/
private int getTimeoutValue (String serviceType, String timeoutType){
String timeoutProperty = timeoutPropertyPattern.replace( "serviceType", serviceType ).replace( "timeoutType", timeoutType );
String servicetTimeout = getPropertyValue(timeoutProperty, serviceType,timeoutType);
Integer timeout = null;
if(servicetTimeout != null && !servicetTimeout.isEmpty() ){
try{
timeout = Integer.parseInt( servicetTimeout.trim() ) * 1000; //convert to millis
}catch(NumberFormatException e){
LOGGER.warn( timeoutType+" Timeout Property for service "+serviceType+" is not in the right format "+e.getMessage() );
}
}
if(timeout == null){
if(timeoutType == CONNECT_TIMOEUT_TYPE){
timeout = DEFAULT_CONNECT_TIMEOUT;
}else if (timeoutType == REQUEST_TIMOEUT_TYPE){
timeout = DEFAULT_REQUEST_TIMEOUT;
}
}
return timeout;
}
/**
* Recursive method that will check for values that can be set as timeout value for the web service.
*
* Order in which properties are looked up:
* 1) cats.service.ir.request.timeout
* 2) cats.service.ir.timeout
* 3) cats.service.request.timeout
* 4) cats.service.timeout
*
* @param property
* @param serviceType
* @param timeoutType
* @return
*/
private String getPropertyValue(String property, String serviceType, String timeoutType ){
String propertyValueString = (String)catsProperties.getProperty( property);
if(propertyValueString == null){
String nextProperty = getNextTimeoutProperty(property,serviceType,timeoutType);
if(nextProperty != null && !nextProperty.isEmpty()){
propertyValueString = getPropertyValue(nextProperty,serviceType,timeoutType);
}else{
propertyValueString = null;
}
}
return propertyValueString;
}
/**
* Logic is very specific to the property definitions.
*
* Order in which properties are looked up:
* 1) cats.service.ir.request.timeout
* 2) cats.service.ir.timeout
* 3) cats.service.request.timeout
* 4) cats.service.timeout
*
* @param poperty
* @return
*/
private String getNextTimeoutProperty(String currentProperty, String serviceType, String timeoutType ){
String nextProperty= "";
if(currentProperty.contains( serviceType ) && currentProperty.contains( timeoutType )){
nextProperty = currentProperty.replace( timeoutType+".", "" );
}else if(currentProperty.contains( serviceType ) && !currentProperty.contains( timeoutType )){
nextProperty = currentProperty.replace( serviceType, timeoutType );
}else if (!currentProperty.contains( serviceType ) && currentProperty.contains( timeoutType )){
nextProperty = CatsProperties.SERVICE_TIMEOUT_PROPERTY;
}
return nextProperty;
}
private void createServiceProxy( Service domainService ) throws ServiceInstantiationException
{
try
{
AssertUtil.isNullObject( domainService, domainService.getServiceType(), domainService.getPath() );
switch ( domainService.getServiceType() )
{
case IR:
createRemoteServiceProxy( domainService.getPath() );
LOGGER.info( "IR proxy created at :" + domainService.getPath() );
break;
case POWER:
createPowerServiceProxy( domainService.getPath() );
LOGGER.info( "Power proxy created at :" + domainService.getPath() );
break;
// case RECORDER: // server side recording. Uses VLC at the server
// // side.
// createRecorderServiceProxy( domainService.getPath() );
// LOGGER.info( "Video Recorder proxy created at :" + domainService.getPath() );
// break;
default:
break;
}
}
catch ( Exception e )
{
/**
* if we throw exception here, further iteration on other servers
* will be compromised
*/
LOGGER.error( "Service instantiation failed " + e.getMessage() );
// throw new ServiceInstantiationException( e.getMessage() );
}
}
private void createRemoteServiceProxy( URL wsdlPath )
{
javax.xml.ws.Service service = create( wsdlPath, new QName( IRServiceConstants.NAMESPACE,
IRServiceConstants.IMPL_STRING ) );
IRService irService = service.getPort( IRService.class ) ;
configureService( irService, ServiceType.IR );
remoteProviderFactory = new RemoteProviderFactoryImpl( irService );
}
private void createPowerServiceProxy( URL wsdlPath )
{
javax.xml.ws.Service service = create( wsdlPath, new QName( PowerServiceConstants.NAMESPACE,
PowerServiceConstants.IMPL_STRING ) );
PowerService powerService = service.getPort( PowerService.class ) ;
configureService( powerService, ServiceType.POWER );
powerProviderFactory = new PowerProviderFactoryImpl( powerService );
}
private void createRecorderServiceProxy( URL serverURL )
{
recorderProviderFactory = new RecorderProviderFactoryImpl( serverURL.getHost() );
}
/**
* check for proxy creation
*
* @return boolean
*/
public boolean isProxyCreated()
{
return isProxyCreated;
}
/**
* set for proxy creation
*
* @param isProxyCreated
*/
public void setProxyCreated( boolean isProxyCreated )
{
this.isProxyCreated = isProxyCreated;
}
}