/**
*
* Copyright
* 2009-2015 Jayway Products AB
* 2016-2017 Föreningen Sambruk
*
* Licensed under AGPL, Version 3.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.gnu.org/licenses/agpl.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package se.streamsource.streamflow.web.management;
import org.apache.solr.common.SolrException;
import org.joda.time.DateTime;
import org.joda.time.DateTimeComparator;
import org.joda.time.Duration;
import org.joda.time.format.PeriodFormat;
import org.qi4j.api.Qi4j;
import org.qi4j.api.common.Optional;
import org.qi4j.api.composite.Composite;
import org.qi4j.api.composite.TransientBuilder;
import org.qi4j.api.composite.TransientComposite;
import org.qi4j.api.constraint.Name;
import org.qi4j.api.injection.scope.Service;
import org.qi4j.api.injection.scope.Structure;
import org.qi4j.api.io.Input;
import org.qi4j.api.io.Inputs;
import org.qi4j.api.io.Outputs;
import org.qi4j.api.io.Transforms;
import org.qi4j.api.mixin.Mixins;
import org.qi4j.api.property.ComputedPropertyInstance;
import org.qi4j.api.property.GenericPropertyInfo;
import org.qi4j.api.property.Property;
import org.qi4j.api.service.Activatable;
import org.qi4j.api.service.ServiceReference;
import org.qi4j.api.unitofwork.UnitOfWorkCompletionException;
import org.qi4j.api.util.Function;
import org.qi4j.api.util.Iterables;
import org.qi4j.index.reindexer.Reindexer;
import org.qi4j.spi.entity.EntityState;
import org.qi4j.spi.entitystore.BackupRestore;
import org.qi4j.spi.entitystore.EntityStore;
import org.qi4j.spi.query.EntityFinder;
import org.qi4j.spi.structure.ModuleSPI;
import org.quartz.JobKey;
import org.quartz.UnableToInterruptJobException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import se.streamsource.infrastructure.index.elasticsearch.ElasticSearchIndexer;
import se.streamsource.infrastructure.index.elasticsearch.filesystem.ESFilesystemIndexQueryService;
import se.streamsource.streamflow.infrastructure.configuration.FileConfiguration;
import se.streamsource.streamflow.infrastructure.event.domain.TransactionDomainEvents;
import se.streamsource.streamflow.infrastructure.event.domain.factory.DomainEventFactory;
import se.streamsource.streamflow.infrastructure.event.domain.replay.DomainEventPlayer;
import se.streamsource.streamflow.infrastructure.event.domain.replay.EventReplayException;
import se.streamsource.streamflow.infrastructure.event.domain.source.EventSource;
import se.streamsource.streamflow.infrastructure.event.domain.source.EventStream;
import se.streamsource.streamflow.infrastructure.event.domain.source.TransactionListener;
import se.streamsource.streamflow.infrastructure.event.domain.source.TransactionVisitor;
import se.streamsource.streamflow.web.application.archival.ArchivalService;
import se.streamsource.streamflow.web.application.archival.ArchivalStartJob;
import se.streamsource.streamflow.web.application.defaults.AvailabilityService;
import se.streamsource.streamflow.web.application.dueon.DueOnNotificationJob;
import se.streamsource.streamflow.web.application.dueon.DueOnNotificationService;
import se.streamsource.streamflow.web.application.mail.ReceiveMailService;
import se.streamsource.streamflow.web.application.statistics.CaseStatistics;
import se.streamsource.streamflow.web.application.statistics.StatisticsStoreException;
import se.streamsource.streamflow.web.infrastructure.event.EventManagement;
import se.streamsource.streamflow.web.infrastructure.index.EmbeddedSolrService;
import se.streamsource.streamflow.web.infrastructure.index.SolrQueryService;
import se.streamsource.streamflow.web.infrastructure.plugin.StreetAddressLookupConfiguration;
import se.streamsource.streamflow.web.infrastructure.plugin.address.StreetAddressLookupService;
import se.streamsource.streamflow.web.infrastructure.plugin.ldap.LdapImportJob;
import se.streamsource.streamflow.web.infrastructure.plugin.ldap.LdapImporterService;
import se.streamsource.streamflow.web.infrastructure.scheduler.QuartzSchedulerService;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.zip.GZIPOutputStream;
import static org.qi4j.api.util.Iterables.*;
import static se.streamsource.streamflow.infrastructure.event.domain.source.helper.Events.*;
/**
* Implementation of Manager interface. All general JMX management methods
* should be put here for convenience.
*/
@Mixins(ManagerComposite.ManagerMixin.class)
public interface ManagerComposite
extends Manager, TransientComposite
{
void start()
throws Exception;
/**
* This is invoked on the service when the instance is being passivated
*
* @throws Exception if the service could not be passivated
*/
void stop()
throws Exception;
abstract class ManagerMixin
implements ManagerComposite
{
final Logger logger = LoggerFactory.getLogger(Manager.class.getName());
private static final long ONE_DAY = 1000 * 3600 * 24;
// private static final long ONE_DAY = 1000 * 60*10; // Ten minutes
@Structure
Qi4j api;
@Service
Reindexer reindexer;
@Service
BackupRestore backupRestore;
@Service
EventSource eventSource;
@Service
EntityFinder entityFinder;
@Service
EventManagement eventManagement;
@Service
FileConfiguration fileConfig;
@Service
EventStream stream;
@Service
DomainEventFactory domainEventFactory;
@Service
DomainEventPlayer eventPlayer;
@Service
ServiceReference<EntityStore> entityStore;
@Service
ServiceReference<EmbeddedSolrService> solr;
@Service
ServiceReference<SolrQueryService> solrIndexer;
@Service
ServiceReference<ESFilesystemIndexQueryService> esIndex;
@Service
CaseStatistics statistics;
@Service
LdapImporterService ldapImporterService;
@Structure
ModuleSPI module;
@Service
ArchivalService archivalService;
@Service
QuartzSchedulerService scheduler;
@Service
AvailabilityService availabilityService;
@Service
ReceiveMailService receiveMailService;
private int failedLogins;
public File exports;
public File backup;
public TransactionListener failedLoginListener;
private ArchivalStartJob archivalJob;
public void start() throws Exception
{
exports = new File(fileConfig.dataDirectory(), "exports");
if (!exports.exists() && !exports.mkdirs())
throw new IllegalStateException("Could not create directory for exports");
backup = new File(fileConfig.dataDirectory(), "backup");
if (!backup.exists() && !backup.mkdirs())
throw new IllegalStateException("Could not create directory for backups");
failedLoginListener = new TransactionListener()
{
public void notifyTransactions(Iterable<TransactionDomainEvents> transactions)
{
failedLogins += count(filter(withNames("failedLogin"), events(transactions)));
}
};
stream.registerListener( failedLoginListener );
}
public void stop() throws Exception
{
stream.unregisterListener( failedLoginListener );
}
// Operations
public void reindex() throws Exception
{
DateTime startDateTime = new DateTime( );
logger.info( "Starting reindex at " + startDateTime.toString() );
logger.info( "Remove ES index." );
// Delete current index
removeESIndexContent();
logger.info( "Remove Solr index." );
// Remove Lucene index contents
removeSolrLuceneIndexContents();
logger.info( "Reindexing ..." );
// Reindex state
reindexer.reindex();
// reindex street cache if plugin is enabled
StreetAddressLookupService streetLookup = (StreetAddressLookupService) module.serviceFinder().findService( StreetAddressLookupService.class ).get();
if( streetLookup != null && ((StreetAddressLookupConfiguration)streetLookup.configuration()).enabled().get() )
{
logger.info( "Reindexing StreetLookup." );
streetLookup.reindex();
}
logger.info( "Reindexing done in " + PeriodFormat.getDefault().print( new Duration( startDateTime, new DateTime( ) ).toPeriod() ) );
}
public String exportDatabase(boolean compress) throws IOException
{
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd_HHmmss");
File exportFile = new File(exports, "streamflow_data_" + format.format(new Date()) + (compress ? ".json.gz" : ".json"));
backupRestore.backup().transferTo(Outputs.text(exportFile));
return "Database exported to:" + exportFile.getAbsolutePath();
}
public String importDatabase(@Name("Filename") String name) throws IOException
{
DateTime startDateTime = new DateTime();
logger.info( "Starting db import at " + startDateTime.toString() );
File importFile = new File(name);
if (!importFile.isAbsolute())
importFile = new File(exports, name);
if (!importFile.exists())
return "No such import file:" + importFile.getAbsolutePath();
logger.info("Importing " + importFile);
try
{
Inputs.text(importFile).transferTo(Transforms.map(new Function<String, String>()
{
int count = 0;
public String map(String s)
{
count++;
if (count % 1000 == 0)
logger.info("Imported " + count + " entities");
return s;
}
}, backupRestore.restore()));
logger.info("Imported " + importFile);
} finally
{
logger.info( "Import db done in " + PeriodFormat.getDefault().print( new Duration( startDateTime, new DateTime( ) ).toPeriod() ) );
try
{
reindex();
} catch (Exception e)
{
throw new RuntimeException("Could not reindex", e);
}
}
return "Data imported successfully";
}
/* public String importEvents( @Name("Filename") String name ) throws IOException
{
File importFile = new File( exports, name );
if (!importFile.exists())
return "No such import file:" + importFile.getAbsolutePath();
InputStream in1 = new FileInputStream( importFile );
if (importFile.getName().endsWith( "gz" ))
{
in1 = new GZIPInputStream( in1 );
}
Reader in = new InputStreamReader( in1, "UTF-8" );
try
{
eventManagement.importEvents( in );
} finally
{
in.close();
}
return "Data imported successfully";
}*/
public String exportEvents(@Name("Compress") boolean compress) throws IOException
{
File exportFile = exportEvents0( compress );
return "Events exported to:" + exportFile.getAbsolutePath();
}
public String exportEventsRange(@Name("Compress") boolean compress, @Name("From") String fromDate, @Name("To") String toDate) throws IOException, ParseException
{
SimpleDateFormat parseFormat = new SimpleDateFormat("yyyyMMdd:HHmmss");
Date from = parseFormat.parse(fromDate);
Date to;
if (toDate == null)
{
// Set "to"-date to "now"
to = new Date();
} else
{
to = parseFormat.parse(toDate);
}
File exportFile = exportEventsRange( compress, from.getTime(), to.getTime() );
return "Events exported to:" + exportFile.getAbsolutePath();
}
// Backup management operations
public String backup() throws IOException, ParseException
{
DateTime startDateTime = new DateTime( );
logger.info( "Started backup at " + startDateTime.toString() );
String backupResult = backupEvents();
backupResult += backupDatabase();
logger.info( "Backup done successfully in: " + PeriodFormat.getDefault().print( new Duration(startDateTime, new DateTime( ) ).toPeriod() ) );
return backupResult;
}
public String restore( @Name("IncludeEventRestore") boolean includeEventRestore ) throws Exception
{
turnOffAccessibilityAndMailReceiver();
try
{
DateTime startDateTime = new DateTime( );
logger.info( "Starting restore at " + startDateTime.toString() );
// Restore data from latest backup in /backup
File latestBackup = getLatestBackup();
// Check if a backup actually exists
if (latestBackup == null)
{
return "Error: no backup to restore";
}
logger.info( "Fetching latest backup and start import database and reindex." );
// contains already a call to reindex
importDatabase( latestBackup.getAbsolutePath() );
logger.info( "Import database and reindex done." );
if( includeEventRestore )
{
DateTime clearEventStart = new DateTime();
logger.info( "Clearing event database and restore start at " + clearEventStart.toString() );
// Add events from backup files
eventManagement.removeAll();
File[] eventFiles = getBackupEventFiles();
// Replay events from time of snapshot backup
Date latestBackupDate = latestBackup == null ? new Date(0) : getBackupDate(latestBackup);
Inputs.combine(Iterables.map(new Function<File, Input<String, IOException>>()
{
public Input<String, IOException> map(File file)
{
return Inputs.text(file);
}
}, Arrays.asList(eventFiles))).transferTo(eventManagement.restore());
logger.info( "Restore event backup done in: " + PeriodFormat.getDefault().print( new Duration(clearEventStart, new DateTime( ) ).toPeriod() ) );
DateTime eventReplayStart = new DateTime();
logger.info( "Starting event replay at " + eventReplayStart.toString() );
{
// Replay transactions
final EventReplayException[] ex = new EventReplayException[1];
eventSource.transactionsAfter(latestBackupDate.getTime() - 60000, new TransactionVisitor()
{
public boolean visit(TransactionDomainEvents transactionDomain)
{
try
{
eventPlayer.playTransaction(transactionDomain);
return true;
} catch (EventReplayException e)
{
ex[0] = e;
return false;
}
}
});
if (ex[0] != null)
throw ex[0];
}
logger.info( "Event replay done in: " + PeriodFormat.getDefault().print( new Duration(eventReplayStart, new DateTime( ) ).toPeriod() ) );
}
logger.info( "Restore done successfully in: " + PeriodFormat.getDefault().print( new Duration(startDateTime, new DateTime( ) ).toPeriod() ) );
turnOnAccessibilityAndMailReceiver();
return "Backup restored successfully";
} catch (Exception ex)
{
logger.error("Backup restore failed:", ex);
return "Backup restore failed:" + ex.getMessage();
}
}
private String backupDatabase()
throws ParseException, IOException
{
if (shouldBackupDatabase())
{
String result = exportDatabase(true);
String fileName = result.substring(result.indexOf(':') + 1);
File backupFile = moveToBackup(new File(fileName));
return ", Backup created:" + backupFile.getAbsolutePath();
} else
return "";
}
private String backupEvents()
throws IOException, ParseException
{
File[] eventBackups = getBackupEventFiles();
if (eventBackups.length == 0)
{
// Make complete event export
File backupFile = moveToBackup(exportEvents0(true));
return "Event backup created:" + backupFile.getAbsolutePath();
} else
{
// Export events since last backup
Date lastBackup = getEventBackupDate(eventBackups[eventBackups.length - 1]);
File exportFile = moveToBackup(exportEventsRange(true, lastBackup.getTime(), System.currentTimeMillis()));
return "Event diff backup created:" + exportFile.getAbsolutePath();
}
}
private File moveToBackup(File file)
{
File backupFile = new File(backup, file.getName());
file.renameTo(backupFile);
return backupFile;
}
private Date getEventBackupDate(File eventBackup) throws ParseException
{
String name = eventBackup.getName().substring("streamflow_events_".length());
if (isDiffEventBackup(eventBackup))
{
// Range
name = name.substring(name.indexOf("-") + 1, name.indexOf("."));
} else
{
// Complete backup
name = name.substring(0, name.indexOf("."));
}
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd_HHmmss");
Date backupDate = format.parse(name);
return backupDate;
}
private boolean isDiffEventBackup(File eventBackup)
{
return eventBackup.getName().substring("streamflow_events_".length()).contains("-");
}
// Backup the database if no backups exist yet,
// or if the existing one was not made today
private boolean shouldBackupDatabase()
throws ParseException
{
boolean exportDatabase = false;
File lastBackup = getLatestBackup();
//Date twentyFourHoursAgo = new Date(System.currentTimeMillis() - ONE_DAY);
if (lastBackup != null)
{
DateTime lastDate = new DateTime( getBackupDate(lastBackup) );
//if (lastDate.before(twentyFourHoursAgo))
if( DateTimeComparator.getDateOnlyInstance().compare( lastDate, new DateTime( ) ) != 0 )
{
exportDatabase = true;
}
} else
{
exportDatabase = true;
}
return exportDatabase;
}
private void removeRdfRepository()
throws Exception
{
((Activatable) api.getModule((Composite) entityFinder)).passivate();
try
{
removeDirectory(new File(fileConfig.dataDirectory(), "rdf-repository"));
} finally
{
((Activatable) api.getModule((Composite) entityFinder)).activate();
}
}
private void removeESIndexContent()
throws Exception
{
esIndex.get().emptyIndex();
}
private void removeSolrLuceneIndexContents()
throws Exception
{
try{
solr.get().getSolrServer( "sf-core" ).deleteByQuery( "*:*" );
} catch (SolrException se )
{
// do nothing
}
}
private void removeApplicationDatabase()
throws Exception
{
((Activatable) api.getModule((Composite) entityStore.get())).passivate();
try
{
removeDirectory(new File(fileConfig.dataDirectory(), "data"));
} finally
{
((Activatable) api.getModule((Composite) entityStore.get())).activate();
}
}
public String databaseSize()
{
Transforms.Counter<EntityState> counter = new Transforms.Counter<EntityState>();
entityStore.get().entityStates(module).transferTo( Transforms.map( counter, Outputs.<EntityState>noop() ) );
return "Database contains " + counter.getCount() + " objects";
}
public void refreshStatistics() throws StatisticsStoreException
{
logger.info("Start refreshing statistics");
statistics.refreshStatistics();
logger.info("Finished refreshing statistics");
}
public String performArchivalCheck()
{
logger.info("Start archival check");
if( archivalJob == null )
{
TransientBuilder<? extends ArchivalStartJob> newJobBuilder = module.transientBuilderFactory().newTransientBuilder(ArchivalStartJob.class);
archivalJob = newJobBuilder.newInstance();
}
String result = archivalJob.performArchivalCheck();
logger.info("Finished archival check");
return result;
}
public void performArchival()
{
try
{
logger.info("Start archival");
if( archivalJob == null )
{
TransientBuilder<? extends ArchivalStartJob> newJobBuilder = module.transientBuilderFactory().newTransientBuilder(ArchivalStartJob.class);
archivalJob = newJobBuilder.newInstance();
}
archivalJob.performArchival();
logger.info("Finished archival");
} catch (UnitOfWorkCompletionException e)
{
logger.warn("Could not perform archival", e);
}
}
public String interruptArchival()
{
String result = "Sending interrupt: \r\n";
JobKey jobKey = JobKey.jobKey( "archivalstartjob", "archivalgroup" );
try
{
// make sure that even transient instance is stopped!
if( archivalJob != null )
{
logger.info( "Interrupting manual archival" );
archivalJob.interrupt();
result += "Sent interrupt request to manual archival\r\n";
}
// if started by Quartz let it handle interruption
if( scheduler.isExecuting(jobKey ) )
{
scheduler.interruptJob( jobKey );
result += "Sent interruptJob to Quartz scheduler";
}
}catch ( Exception e )
{
logger.error( "Could not interrupt archival", e);
return "Interrupt archival failed: " + e.getMessage();
}
return result;
}
public void sendDueOnNotifications()
{
try
{
logger.info("Start to send dueOn notifications");
TransientBuilder<? extends DueOnNotificationJob> newJobBuilder = module.transientBuilderFactory().newTransientBuilder( DueOnNotificationJob.class );
DueOnNotificationJob dueOnNotificationJob = newJobBuilder.newInstance();
dueOnNotificationJob.performNotification();
logger.info("Finished sending dueOn notifications");
} catch (UnitOfWorkCompletionException e)
{
logger.warn("Could not send dueOn notifications", e);
}
}
public String importUserAndGroupsFromLdap()
{
try
{
if( ldapImporterService.getConfiguration().configuration().enabled().get() )
{
logger.info("Start to import users and groups");
TransientBuilder<? extends LdapImportJob> newJobBuilder = module.transientBuilderFactory().newTransientBuilder( LdapImportJob.class )
.use( ldapImporterService.getConfiguration() );
LdapImportJob ldapImportJob = newJobBuilder.newInstance();
ldapImportJob.importUsers();
logger.info("Finished importing users");
ldapImportJob.importGroups();
logger.info("Finished importing groups.");
return "Import done successfully.";
} else
{
logger.warn( "LdapImporterService is not available." );
return "Service not available. Check LdapImporterService configuration!";
}
} catch (Exception e)
{
logger.warn("Could not complete import", e);
return e.getMessage();
}
}
private File getLatestBackup() throws ParseException
{
File latest = null;
Date latestDate = null;
for (File file : backup.listFiles(new FileFilter()
{
public boolean accept(File pathname)
{
return pathname.getName().startsWith("streamflow_data_");
}
}))
{
// See if backup is newer than currently found backup file
if (latest == null || getBackupDate(file).after(latestDate))
{
latestDate = getBackupDate(file);
latest = file;
}
}
return latest;
}
private Date getBackupDate(File file) throws ParseException
{
String name = file.getName().substring("streamflow_data_".length());
name = name.substring(0, name.indexOf("."));
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd_HHmmss");
Date backupDate = format.parse(name);
return backupDate;
}
private File[] getBackupEventFiles()
{
File[] files = backup.listFiles(new FileFilter()
{
public boolean accept(File pathname)
{
return pathname.getName().startsWith("streamflow_events");
}
});
Arrays.sort(files, new Comparator<File>()
{
public int compare(File o1, File o2)
{
String o1Name = o1.getName();
o1Name = o1Name.substring(0, o1Name.indexOf('.'));
String o2Name = o2.getName();
o2Name = o2Name.substring(0, o2Name.indexOf('.'));
return o1Name.compareTo(o2Name);
}
});
return files;
}
private void removeDirectory(File dir)
throws IOException
{
if (dir == null || !dir.exists())
return;
for (File file : dir.listFiles())
{
if (file.isDirectory())
{
removeDirectory(file);
} else
{
if (!file.delete())
throw new IOException("Could not delete file:" + file.getAbsolutePath());
}
}
if (!dir.delete())
throw new IOException("Could not delete directory:" + dir.getAbsolutePath());
}
private File exportEvents0(boolean compress)
throws IOException
{
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd_HHmmss");
File exportFile = new File(exports, "streamflow_events_" + format.format(new Date()) + (compress ? ".json.gz" : ".json"));
OutputStream out = new FileOutputStream(exportFile);
if (compress)
{
out = new GZIPOutputStream(out);
}
final Writer writer = new OutputStreamWriter(out, "UTF-8");
final IOException[] ex = new IOException[1];
eventSource.transactionsAfter(0, new TransactionVisitor()
{
public boolean visit(TransactionDomainEvents transactionDomain)
{
try
{
writer.write(transactionDomain.toJSON() + "\n");
} catch (IOException e)
{
ex[0] = e;
return false;
}
return true;
}
});
writer.close();
if (ex[0] != null)
{
exportFile.delete();
throw ex[0];
}
return exportFile;
}
private File exportEventsRange(boolean compress, long from, final long to)
throws IOException
{
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd_HHmmss");
File exportFile = new File(exports, "streamflow_events_" + format.format(from) + "-" + format.format(to) + (compress ? ".json.gz" : ".json"));
OutputStream out = new FileOutputStream(exportFile);
if (compress)
{
out = new GZIPOutputStream(out);
}
final Writer writer = new OutputStreamWriter(out, "UTF-8");
final IOException[] ex = new IOException[1];
eventSource.transactionsAfter(from, new TransactionVisitor()
{
public boolean visit(TransactionDomainEvents transactionDomain)
{
if (transactionDomain.timestamp().get() > to)
return false;
try
{
writer.write(transactionDomain.toJSON() + "\n");
} catch (IOException e)
{
ex[0] = e;
return false;
}
return true;
}
});
writer.close();
if (ex[0] != null)
{
exportFile.delete();
throw ex[0];
}
return exportFile;
}
// Attributes
public Property<Integer> failedLogins()
{
return new ComputedPropertyInstance<Integer>(new GenericPropertyInfo(Manager.class, "failedLogins"))
{
public Integer get()
{
return failedLogins;
}
};
}
private void turnOffAccessibilityAndMailReceiver()
{
availabilityService.getCircuitBreaker().trip();
if( receiveMailService.getCircuitBreaker().isOn() )
{
receiveMailService.getCircuitBreaker().trip();
}
}
private void turnOnAccessibilityAndMailReceiver()
throws Exception
{
if( !receiveMailService.getCircuitBreaker().isOn() )
{
receiveMailService.getCircuitBreaker().turnOn();
}
availabilityService.getCircuitBreaker().turnOn();
}
}
}