I found this post about BitPay but it's not very clear how I can use it.
https://help.bitpay.com/development/how-do-i-use-the-bitpay-java-client-library
I implemented this code:
public void createInvoice() throws BitPayException
{
ECKey key = KeyUtils.createEcKey();
BitPay bitpay = new BitPay(key);
InvoiceBuyer buyer = new InvoiceBuyer();
buyer.setName("Satoshi");
buyer.setEmail("satoshi#bitpay.com");
Invoice invoice = new Invoice(100.0, "USD");
invoice.setBuyer(buyer);
invoice.setFullNotifications(true);
invoice.setNotificationEmail("satoshi#bitpay.com");
invoice.setPosData("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
Invoice createInvoice = bitpay.createInvoice(invoice);
}
How should I implement the private key?
That answer, I believe, is found in the following file: https://github.com/bitpay/java-bitpay-client/blob/master/src/main/java/controller/BitPay.java - that is to say, you would set your private key on the BitPay client instance. There you can find the appropriate constructor for your needs. You will want to use one or more of the following fields depending on your specific needs:
private ECKey _ecKey = null;
private String _identity = "";
private String _clientName = "";
private Hashtable<String, String> _tokenCache;
Edit: encryption and decryption of your private key exists here: https://github.com/bitpay/java-bitpay-client/blob/master/src/main/java/controller/KeyUtils.java
If, for instance, you used the following constructor:
public BitPay(URI privateKey) throws BitPayException, URISyntaxException, IOException {
this(KeyUtils.loadEcKey(privateKey), BITPAY_PLUGIN_INFO, BITPAY_URL);
}
You would pass in the URI for your private key.
Specific instructions on this available here: https://github.com/bitpay/java-bitpay-client/blob/master/GUIDE.md
Two very simple examples:
BitPay bitpay = new BitPay();
ECKey key = KeyUtils.createEcKey();
this.bitpay = new BitPay(key);
Number two:
// Create the private key external to the SDK, store it in a file, and inject the private key into the SDK.
String privateKey = KeyUtils.getKeyStringFromFile(privateKeyFile);
ECKey key = KeyUtils.createEcKeyFromHexString(privateKey);
this.bitpay = new BitPay(key);
After implementing the private key, you'd till need to initialize the client and connect to the server.
Related
I'm this is my first time making a project that needs JWT authentication, I'm following this tutorial, but I get the problem that my key must be higher than 256bits, I searched here and found this post, however I'm new in this topic so I don't really know how to make it 256bits. Below is my code:
#Service
public class JwtGeneration implements IJwtGeneration {
#Value("${jwt.secret}")
private String secret;
#Value("${app.jwttoken.message}")
private String message;
#Override
public Map<String, String> generateToken(User user) {
String jwtToken="";
jwtToken = Jwts.builder().setSubject(user.getUserName()).setIssuedAt(new Date()).signWith(SignatureAlgorithm.HS256, "secret").compact();
Map<String, String> jwtTokenGen = new HashMap<>();
jwtTokenGen.put("token", jwtToken);
jwtTokenGen.put("message", message);
return jwtTokenGen;
}
}
How can I make it 256 bits?
Searching more, I found that I could try this:
#Service
public class JwtGeneration implements IJwtGeneration {
#Value("${jwt.secret}")
private String secret;
byte[] decodedKey = secret.getBytes(StandardCharsets.UTF_8);
SecretKey key = new SecretKeySpec(decodedKey, 0, decodedKey.length, "HMACSHA256");
#Value("${app.jwttoken.message}")
private String message;
#Override
public Map<String, String> generateToken(User user) {
String jwtToken="";
jwtToken = Jwts.builder().setSubject(user.getUserName()).setIssuedAt(new Date()).signWith(key, SignatureAlgorithm.HS256).compact();
Map<String, String> jwtTokenGen = new HashMap<>();
jwtTokenGen.put("token", jwtToken);
jwtTokenGen.put("message", message);
return jwtTokenGen;
}
}
But I still get error, how can I fix it?
UPDATE
This is the error message I get: "io.jsonwebtoken.security.WeakKeyException: The signing key's size is 48 bits which is not secure enough for the HS256 algorithm. The JWT JWA Specification (RFC 7518, Section 3.2) states that keys used with HS256 MUST have a size >= 256 bits (the key size must be greater than or equal to the hash output size). Consider using the io.jsonwebtoken.security.Keys class's 'secretKeyFor(SignatureAlgorithm.HS256)' method to create a key guaranteed to be secure enough for HS256. See https://tools.ietf.org/html/rfc7518#section-3.2 for more information.
at com.authdemo.AuthDemo.config.JwtGeneration.generateToken(JwtGeneration.java:26) ~[classes/:na]
at com.authdemo.AuthDemo.controller.UserController.loginUser(UserController.java:44) ~[classes/:na]"
I tried SecretKey key = secretKeyFor(SignatureAlgorithm.HS256) but it didn't work.
I found the solution, I had to add to my pom the following dependencies: jjwt-impl and jjwt-jackson. Also I had to create my secret key as follows:
private String secret = "2D4A614E645267556B58703273357638792F423F4428472B4B6250655368566D";
#Value("${app.jwttoken.message}")
private String message;
#Override
public Map<String, String> generateToken(User user) {
String jwtToken="";
jwtToken = Jwts.builder().setSubject(user.getUserName()).setIssuedAt(new Date()).signWith(getSignInKey(),SignatureAlgorithm.HS256).compact();
Map<String, String> jwtTokenGen = new HashMap<>();
jwtTokenGen.put("token", jwtToken);
jwtTokenGen.put("message", message);
return jwtTokenGen;
}
private Key getSignInKey() {
byte[] keyBytes = Decoders.BASE64.decode(secret);
return Keys.hmacShaKeyFor(keyBytes);
}
Turns out I was using "secretkey" as my jwt.secret in application properties. And I was passing that variable alone. What I did now was use an online keygenerator and then created a function to convert it to a Key and then passed it to signWith() method.
I am trying to transfer a state from one owner to the other back and forth. So always end up having new state created with same values. But intend to pass the state even if it is consumed to another owner. Trying to achieve this with linear state. I have pasted the transfer flow that basically should use the same car state that was issued to transfer across owners. The same consumed car state should be possible to transfer back and forth with the same state being consumed agian and again. Is this possible in Corda. From atheory perspective I am trying to transfer the car back and forth between two or more partys.
State
#ConstructorForDeserialization
public CarState(String carMake, String carModel, int carYear, double carMileAge, String carVIN, Party issuer, Party owner,UniqueIdentifier linearId) {
this.carMake = carMake;
this.carModel = carModel;
this.carYear = carYear;
this.carMileAge = carMileAge;
this.carVIN = carVIN;
this.issuer = issuer;
this.owner = owner;
this.linearId = linearId;
}
Contract
if(!(inputState.getLinearId().getExternalId().equals(outputState.getLinearId().getExternalId()))){
throw new IllegalArgumentException("UUID of input state and output state must be same");
}
Flow
#InitiatingFlow
#StartableByRPC
public class CarTransferFlowInitiator extends FlowLogic<String> {
// private final String carMake;
// private final String carModel;
// private final int carYear;
private final String carVin;
// private final double carMileage;
private final Party carOwner;
private final UniqueIdentifier linearId;
private int input;
public CarTransferFlowInitiator(String carVin,Party carOwner,UniqueIdentifier linearId){
this.carVin = carVin;
this.carOwner = carOwner;
this.linearId = linearId;
}
private final ProgressTracker.Step RETRIEVING_NOTARY = new ProgressTracker.Step("Retrieving Notary");
private final ProgressTracker.Step CREATE_TRANSACTION_INPUT= new ProgressTracker.Step("Creating Transaction Input");
private final ProgressTracker.Step CREATE_TRANSACTION_OUTPUT= new ProgressTracker.Step("Creating Transaction Output");
private final ProgressTracker.Step CREATE_TRANSACTION_BUILDER= new ProgressTracker.Step("Creating transaction Builder");
private final ProgressTracker.Step SIGN_TRANSACTION = new ProgressTracker.Step("Signing Transaction");
private final ProgressTracker.Step INITIATE_SESSION = new ProgressTracker.Step("Initiating session with counterparty");
private final ProgressTracker.Step FINALIZE_FLOW = new ProgressTracker.Step("Finalizing the flow");
private final ProgressTracker progressTracker = new ProgressTracker(
RETRIEVING_NOTARY,
CREATE_TRANSACTION_OUTPUT,
CREATE_TRANSACTION_BUILDER,
SIGN_TRANSACTION,
INITIATE_SESSION,
FINALIZE_FLOW
);
#Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
public StateAndRef<CarState> checkForCarStates() throws FlowException {
//QueryCriteria generalCriteria = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED);
QueryCriteria generalCriteria = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL);
List<StateAndRef<CarState>> CarStates = getServiceHub().getVaultService().queryBy(CarState.class, generalCriteria).getStates();
boolean inputFound = false;
int t = CarStates.size();
input = 0;
for (int x = 0; x < t; x++) {
if (CarStates.get(x).getState().getData().getCarVIN().equals(carVin)) {
// if (CarStates.get(x).getState().getData().getLinearId().getExternalId().equals(linearId.getExternalId())) {
input = x;
inputFound = true;
}
}
if (inputFound) {
System.out.println("\n Input Found");
// System.out.println(CarStates.get(input).getState().getData().getCarMake());
// System.out.println(CarStates.get(input).getState().getData().getCarModel());
// System.out.println(CarStates.get(input).getState().getData().getCarYear());
// System.out.println(CarStates.get(input).getState().getData().getCarMileAge());
// System.out.println(CarStates.get(input).getState().getData().getCarVIN());
} else {
System.out.println("\n Input not found");
throw new FlowException();
}
return CarStates.get(input);
}
#Suspendable
public String call() throws FlowException {
//Retrieve the notary identity from the network map
progressTracker.setCurrentStep(RETRIEVING_NOTARY);
Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
//Create transaction components both input and output for this application
progressTracker.setCurrentStep(CREATE_TRANSACTION_OUTPUT);
StateAndRef<CarState> inputState = null;
inputState = checkForCarStates();
//Issuer is Toyota
//Owner is AutoSmart
Party issuer = inputState.getState().getData().getIssuer();
PublicKey issuerKey = issuer.getOwningKey();
//Create transaction components both input and output for this application
progressTracker.setCurrentStep(CREATE_TRANSACTION_OUTPUT);
//CarState outputState = new CarState(carMake,carModel,carYear,carMileage,carVin,issuer,carOwner);
String carMake = inputState.getState().getData().getCarMake();
String carModel = inputState.getState().getData().getCarModel();
int carYear = inputState.getState().getData().getCarYear();
double carMile = inputState.getState().getData().getCarMileAge();
String carVIN = inputState.getState().getData().getCarVIN();
Party carIssuer = inputState.getState().getData().getIssuer();
UniqueIdentifier carLinearId = inputState.getState().getData().getLinearId();
System.out.println(carLinearId);
CarState outputState = new CarState(carMake,carModel,carYear,carMile, carVin,carIssuer,carOwner,carLinearId);
List<PublicKey> requiresSigners = Arrays.asList(issuerKey,getOurIdentity().getOwningKey(),outputState.getOwner().getOwningKey());
// requiresSigners.add(outputState.getIssuer().getOwningKey());
// requiresSigners.add(outputState.getOwner().getOwningKey());
final Command<CarContract.Transfer> txCommand = new Command<>(
new CarContract.Transfer(),
requiresSigners
);
final TransactionBuilder txBuilder = new TransactionBuilder(notary)
.addInputState(inputState)
.addOutputState(outputState, CID)
.addCommand(txCommand);
// Create the transaction builder here and add compenents to it
progressTracker.setCurrentStep(CREATE_TRANSACTION_BUILDER);
//TransactionBuilder txB = new TransactionBuilder(notary);
// PublicKey issuerKey = getServiceHub().getMyInfo().getLegalIdentitiesAndCerts().get(0).getOwningKey();
// PublicKey ownerKey = carOwner.getOwningKey();
//List<PublicKey> requiredSigners = ImmutableList.of(issuerKey,ownerKey);
//ArrayList<PublicKey> requiredSigners = new ArrayList<PublicKey>();
//requiredSigners.add(issuerKey);
//requiredSigners.add(ownerKey);
// Command cmd = new Command(new CarContract.Register(), getOurIdentity().getOwningKey());
//txB.addOutputState(outputState, CID)
// .addCommand(cmd);
// Sign the transaction
progressTracker.setCurrentStep(SIGN_TRANSACTION);
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
// Create session with counterparty
progressTracker.setCurrentStep(INITIATE_SESSION);
FlowSession issuePartySession = initiateFlow((issuer));
FlowSession otherPartySession = initiateFlow(carOwner);
ArrayList<FlowSession> sessions = new ArrayList<>();
sessions.add(otherPartySession);
sessions.add(issuePartySession);
final SignedTransaction fullySignedTx = subFlow(
new CollectSignaturesFlow(signedTx, sessions, CollectSignaturesFlow.Companion.tracker()));
// SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
// signedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.tracker()));
//Finalizing the transaction
progressTracker.setCurrentStep(FINALIZE_FLOW);
subFlow(new FinalityFlow(fullySignedTx,sessions));
return "Transfer Completed";
}
}
You need to create a second constructor for your linear state which accepts as input parameter a linearId; you should mark the constructor with the annotation #ConstructorForDeserialization.
Otherwise, when your flow suspends (for any reason) and it serializes your linear state, when the flow will attempt to resume and deserialize the state; it will use your current constructor which generates a random linearId, so the flow will end up with a new/different state than yours (because it has a different linearId).
But when you create that second constructor and mark it with the annotation, the flow will use it to deserialize and create an identical state.
You can use that constructor as well when you create the output state instead of using the setter like you do now.
Edit (after you added the flow code):
You cannot use a consumed state as an input; that's what's known as the double-spend problem. Imagine you had a US Dollar state and you tried to use the same dollar twice to buy things; that cannot happen. The notary will throw and exception if you try to use a consumed state as an input.
That's why when you query, you should query for UNCONSUMED instead of ALL.
Your query solution is not correct and not efficient, imagine if you had 100,000 cars; you're going to fetch all 100,000 then loop through them until you find the car with the VIN that you want? First of all you need pagination (Corda will throw an error if your result set returns more than 200 records and you're not using pagination), second of all this will probably drain your Java heap from creating those 100,000 objects and crash your CorDapp.
Instead, you should create a custom schema for your car state; then use a custom query criteria to query by the VIN number. Have a look at the IOU example how they created a custom schema for IOU state (see here and here). Then you can use the custom schema in a VaultCustomQueryCriteria (read here).
I'm trying to use M2Doc programmatically, I managed to generate my .docx file without getting errors in the validation part but I'm getting the following Error in the generated document:
{m:self.Name} Couldn't find the 'aqlFeatureAccess(org.eclipse.emf.common.util.URI.Hierarchical,java.lang.String)' service
The "self.Name" part is what I wrote in my template.
I think I'm lacking some kind of reference to a service but I don't know how to fix it.
The self variable is a reference to a model based on a meta-model I created. But I'm not sure I imported it correctly in my code.
I based my code on the code I found on the M2Doc website + some code I found on their GitHub, especially concerning how to add a service in the queryEnvironment.
I searched in the source code of acceleo and M2Doc to see which services they add but it seems that they already import all the services I'm using.
As I said, the validation part is going well and doesn't generate a validation file.
public static void parseDocument(String templateName) throws Exception{
final URI templateURI = URI.createFileURI("Template/"+templateName+"."+M2DocUtils.DOCX_EXTENSION_FILE);
final IQueryEnvironment queryEnvironment =
org.eclipse.acceleo.query.runtime.Query.newEnvironmentWithDefaultServices(null);
final Map<String, String> options = new HashMap<>(); // can be empty
M2DocUtils.prepareEnvironmentServices(queryEnvironment, templateURI, options); // delegate to IServicesConfigurator
prepareEnvironmentServicesCustom(queryEnvironment, options);
final IClassProvider classProvider = new ClassProvider(ClassLoader.getSystemClassLoader()); // use M2DocPlugin.getClassProvider() when running inside Eclipse
try (DocumentTemplate template = M2DocUtils.parse(templateURI, queryEnvironment, classProvider)) {
ValidationMessageLevel validationLevel = validateDocument(template, queryEnvironment, templateName);
if(validationLevel == ValidationMessageLevel.OK){
generateDocument(template, queryEnvironment, templateName, "Model/ComplexKaosModel.kaos");
}
}
}
public static void prepareEnvironmentServicesCustom(IQueryEnvironment queryEnvironment, Map<String, String> options){
Set<IService> services = ServiceUtils.getServices(queryEnvironment, FilterService.class);
ServiceUtils.registerServices(queryEnvironment, services);
M2DocUtils.getConfigurators().forEach((configurator) -> {
ServiceUtils.registerServices(queryEnvironment, configurator.getServices(queryEnvironment, options));
});
}
public static void generateDocument(DocumentTemplate template, IQueryEnvironment queryEnvironment,
String templateName, String modelPath)throws Exception{
final Map<String, Object> variable = new HashMap<>();
variable.put("self", URI.createFileURI(modelPath));
final Monitor monitor = new BasicMonitor.Printing(System.out);
final URI outputURI = URI.createFileURI("Generated/"+templateName+".generated."+M2DocUtils.DOCX_EXTENSION_FILE);
M2DocUtils.generate(template, queryEnvironment, variable, outputURI, monitor);
}
The variable "self" contains an URI:
variable.put("self", URI.createFileURI(modelPath));
You have to load your model and set the value of self to an element from your model using something like:
final ResourceSet rs = new ResourceSetImpl();
final Resource r = rs.getResource(uri, true);
final EObject value = r.getContents()...;
variable.put("self", value);
You can get more details on resource loading in the EMF documentation.
I want to make an insert operation in an import set table through the web service from ServiceNow with axis2 version 1.6.4
I used wsdl2java with the wsdl file to create classes in my java project.
After it, i created a new class Request on which i would build my soap request to the webservice.
I believe I have 2 (might be more) major problems:
Unserstanding the difference between a stub and a proxy, respectively, the classes ServiceNowSoapStub and ServiceNowSoapProxy and what is the purpose of each of them.
The existing insert method needs a lot of arguments and i wish to make inserts with a selected number of arguments. do i need to add that specific insert method to the architecture?
Here is what I have:
public class Request {
public static void main(String[] args) throws RemoteException {
try{
HttpTransportProperties.Authenticator basicAuthentication = new HttpTransportProperties.Authenticator();
basicAuthentication.setUsername("xxxx");
basicAuthentication.setPassword("xxxx");
ServiceNowSoapStub proxy = new ServiceNowSoapStub();
proxy._setProperty(org.apache.axis2.transport.http.HTTPConstants.CHUNKED, Boolean.FALSE);
proxy._setProperty(org.apache.axis2.transport.http.HTTPConstants.AUTHENTICATE, basicAuthentication);
BigDecimal actuals = new BigDecimal("0.04");
BigInteger appId = new BigInteger("3495766");
String appNonApp = "ApNon";
BigInteger productId = new BigInteger("704217");
BigInteger serviceId = new BigInteger("1537");
String serviceName = "IT";
String bpName = "BNK-RSK";
String method = "N";
String bsCode = "ITDV";
String customerCostCode = "30973250";
String customerCountry = "US";
String customerGroup = "Wealth";
String customerLegalEntity = "HB";
String dsId = "EU56";
BigInteger supplierCostCode = new BigInteger("675136");
String supplierCountry = "UK";
String supplierLegalEntity = "BR";
BigInteger total = new BigInteger ("411");
ServiceNowSoapProxy request = new ServiceNowSoapProxy("https://dev34363.service-now.com/u_it_actuals_import_set");
request.insertTest(actuals, appId, appNonApp, productId, serviceId, serviceName, bpName, method, bsCode, customerCostCode,
customerCountry, customerGroup, customerLegalEntity, dsId, supplierCostCode, supplierCountry, supplierLegalEntity, total);
} catch (org.apache.axis.AxisFault e) {
e.printStackTrace();
}
}
}
What am I doing wrong? or can please anyone refer me to some helpful link?
The wiki page of servicenow addressing this subject is a bit out of date so i can't solve my problem through it.
Thanks!
Uses on-line decomentation I come up with the following code to terminate the current EC2 Instance:
public class Ec2Utility {
static private final String LOCAL_META_DATA_ENDPOINT = "http://169.254.169.254/latest/meta-data/";
static private final String LOCAL_INSTANCE_ID_SERVICE = "instance-id";
static public void terminateMe() throws Exception {
TerminateInstancesRequest terminateRequest = new TerminateInstancesRequest().withInstanceIds(getInstanceId());
AmazonEC2 ec2 = new AmazonEC2Client();
ec2.terminateInstances(terminateRequest);
}
static public String getInstanceId() throws Exception {
//SimpleRestClient, is an internal wrapper on http client.
SimpleRestClient client = new SimpleRestClient(LOCAL_META_DATA_ENDPOINT);
HttpResponse response = client.makeRequest(METHOD.GET, LOCAL_INSTANCE_ID_SERVICE);
return IOUtils.toString(response.getEntity().getContent(), "UTF-8");
}
}
My issue is that my EC2 instance is under an AutoScalingGroup which is under a CloudFormationStack, that is because of my organisation deployment standards though this single EC2 is all there is there for this feature.
So, I want to terminate the entire CloudFormationStack from the JavaSDK, keep in mind, I don't have the CloudFormation Stack Name in advance as I didn't have the EC2 Instance Id so I will have to get it from the code using the API calls.
How can I do that, if I can do it?
you should be able to use the deleteStack method from cloud formation sdk
DeleteStackRequest request = new DeleteStackRequest();
request.setStackName(<stack_name_to_be_deleted>);
AmazonCloudFormationClient client = new AmazonCloudFormationClient (<credentials>);
client.deleteStack(request);
If you don't have the stack name, you should be able to retrieve from the Tag of your instance
DescribeInstancesRequest request =new DescribeInstancesRequest();
request.setInstanceIds(instancesList);
DescribeInstancesResult disresult = ec2.describeInstances(request);
List <Reservation> list = disresult.getReservations();
for (Reservation res:list){
List <Instance> instancelist = res.getInstances();
for (Instance instance:instancelist){
List <Tag> tags = instance.getTags();
for (Tag tag:tags){
if (tag.getKey().equals("aws:cloudformation:stack-name")) {
tag.getValue(); // name of the stack
}
}
At the end I've achieved the desired behaviour using the set of the following util functions I wrote:
/**
* Delete the CloudFormationStack with the given name.
*
* #param stackName
* #throws Exception
*/
static public void deleteCloudFormationStack(String stackName) throws Exception {
AmazonCloudFormationClient client = new AmazonCloudFormationClient();
DeleteStackRequest deleteStackRequest = new DeleteStackRequest().withStackName("");
client.deleteStack(deleteStackRequest);
}
static public String getCloudFormationStackName() throws Exception {
AmazonEC2 ec2 = new AmazonEC2Client();
String instanceId = getInstanceId();
List<Tag> tags = getEc2Tags(ec2, instanceId);
for (Tag t : tags) {
if (t.getKey().equalsIgnoreCase(TAG_KEY_STACK_NAME)) {
return t.getValue();
}
}
throw new Exception("Couldn't find stack name for instanceId:" + instanceId);
}
static private List<Tag> getEc2Tags(AmazonEC2 ec2, String instanceId) throws Exception {
DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest().withInstanceIds(instanceId);
DescribeInstancesResult describeInstances = ec2.describeInstances(describeInstancesRequest);
List<Reservation> reservations = describeInstances.getReservations();
if (reservations.isEmpty()) {
throw new Exception("DescribeInstances didn't returned reservation for instanceId:" + instanceId);
}
List<Instance> instances = reservations.get(0).getInstances();
if (instances.isEmpty()) {
throw new Exception("DescribeInstances didn't returned instance for instanceId:" + instanceId);
}
return instances.get(0).getTags();
}
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// Example of usage from the code:
deleteCloudFormationStack(getCloudFormationStackName());
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX