AngularJS+MongoDB+Spring [The complete trail]

Okay, here in this post, I would like to represent my complete application created with Spring,MongoDB and AngularJS. Here goes the project structure,
image

Now all the normal steps.

Step 1. The AngularMongoSpring/pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.anupam</groupId>
	<artifactId>AngularMongoSpring</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>AngularSpring Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<dependencies>
		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>4.2.2.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>4.2.2.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.2.2.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>4.2.2.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>4.2.2.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>4.2.2.RELEASE</version>
		</dependency>
		
		<!-- Spring + Mongo data -->
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-mongodb</artifactId>
			<version>1.8.0.RELEASE</version>
		</dependency>

		<!-- Jackson -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-core</artifactId>
			<version>2.6.3</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.6.3</version>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-annotations</artifactId>
			<version>2.6.3</version>
		</dependency>
		<!-- Apache File Upload -->
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.2.2</version>
		</dependency>
		<dependency>
			<groupId>commons-io</groupId>
			<artifactId>commons-io</artifactId>
			<version>2.4</version>
		</dependency>
	</dependencies>
	<build>
		<finalName>AngularMongoSpring</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

Step 2. AngularMongoSpring\src\main\webapp\WEB-INF\spring\app-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd">


	<!-- Annotation scan -->
	<context:annotation-config />
	<context:component-scan base-package="com.anupam.*" />

	<!-- MVC -->
	<mvc:annotation-driven />

	<!-- Multi Part -->
	<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />

</beans>

Step 3. AngularMongoSpring\src\main\webapp\WEB-INF\spring\hibernate-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mongo="http://www.springframework.org/schema/data/mongo" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.8.xsd">


	<mongo:db-factory id="factory" host="localhost" port="9999" dbname="mydb" />

	<bean id="template" class="org.springframework.data.mongodb.core.MongoTemplate">
		<constructor-arg ref="factory" />
	</bean>
</beans>

Step 4. AngularMongoSpring\src\main\webapp\WEB-INF\web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
	<servlet>
		<servlet-name>myspring</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet
		</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/app-config.xml,/WEB-INF/spring/hibernate-config.xml
			</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>myspring</servlet-name>
		<url-pattern>/post/*</url-pattern>
	</servlet-mapping>
	<welcome-file-list>
		<welcome-file>upload.html</welcome-file>
	</welcome-file-list>
</web-app>

Step 5. AngularMongoSpring\src\main\webapp\upload.html

<html>
<head>
<title>Angular JS Directive.</title>
<script src="js/angular.min.js"></script>
<script src="js/main.js"></script>
</head>

<body ng-app="coreApp" ng-controller="myController">

<h2>AngularJS Sample Application</h2>


<div>

<form ng-submit="doSubmit()">
			Scrap:
			<textarea ng-model="data.scrap" style="width: 393px; height: 93px"></textarea>
			

			 Image: <input type="file" file-upload /> 
</br> <input type="submit" value="Submit" />
		</form>


	</div>


<div>


File Selected

		{{data.name}}
	</div>

</body>
</html>

Step 6. AngularMongoSpring\src\main\webapp\js\main.js

var coreApp = angular.module("coreApp", []);

coreApp.directive('fileUpload', function()
{
	return {
	scope : false,
	link : function(scope, elem, attr)
	{
		elem.on('change', function(event)
		{
			var file = event.target.files[0];
			scope.$apply(function()
			{
				scope.data.file = file;
			});

		});
	}
	}
});
coreApp.service('PostService', function($http)
{

	// Upload file data.
	this.uploadFile = function(formData, onSaved)
	{

		$http.post('post/upload', formData, {
		transformRequest : angular.identity,
		headers : {
			'Content-Type' : undefined
		}
		}).success(function()
		{
			onSaved(data);
		}).error(function()
		{
		});
	};

});
coreApp.controller('myController', function($scope, PostService)
{
	$scope.data = {
	scrap : 'hey buddy',
	file : null
	};

	$scope.doSubmit = function()
	{
		var formData = new FormData();
		
		// JSON Object.
		var scrap = {
		scrap : $scope.data.scrap,
		date : '2015-11-03'
		};
		
		// Send the json object scrap in String.
		formData.append("scrap", JSON.stringify(scrap));
		formData.append("file", $scope.data.file);

		PostService.uploadFile(formData, function(data)
		{
			alert('uploaded...');
		});
	};

});

Step 7. My Custom Annotation

com.anupam.annotation.MyCascade

package com.anupam.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface MyCascade
{

}

com.anupam.annotation.MyCascadeHandler

package com.anupam.annotation;

import java.lang.reflect.Field;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;

@Component
public class MyCascadeHandler extends AbstractMongoEventListener<Object>
{
    @Autowired
    private MongoTemplate template;
    
    @Override
    public void onBeforeConvert(final Object source)
    {
	ReflectionUtils.doWithFields(source.getClass(), new FieldCallback()
	{

	    @Override
	    public void doWith(
		    Field field) throws IllegalArgumentException, IllegalAccessException
	    {
		ReflectionUtils.makeAccessible(field);
		if (field.isAnnotationPresent(DBRef.class) && field.isAnnotationPresent(MyCascade.class))
		{
		    final Object fieldValue = field.get(source);
		    template.save(fieldValue);
		}
		else
		{
		    System.out.println(field.getName());
		}
	    }
	});
    }
}

Step 8. Controller

com.anupam.controller.PostController

package com.anupam.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import com.anupam.service.PostSRV;
import com.anupam.uivo.PostUiVO;
import com.anupam.vo.TbUserVO;
import com.fasterxml.jackson.databind.ObjectMapper;

@Controller
public class PostController
{
    @Autowired
    private PostSRV postSRV;

    @RequestMapping("/hello")
    @ResponseBody
    public TbUserVO sayHello()
    {
	TbUserVO user = new TbUserVO();
	user.setFirstName("Anupam");
	user.setLastName("Gogoi");
	return user;
    }

    @RequestMapping(path = "/list", method = RequestMethod.GET)
    @ResponseBody
    public List<PostUiVO> getPostByUser() throws Exception
    {
	return postSRV.getPostByUser();
    }

    @RequestMapping(path = "/remove/{id}", method = RequestMethod.DELETE)
    @ResponseBody
    public List<PostUiVO> deletePost(
	    @PathVariable("id") Integer postID) throws Exception
    {
	postSRV.deletePost(postID);
	return postSRV.getPostByUser();
    }

    @RequestMapping(path = "/upload", method = RequestMethod.POST)
    @ResponseBody
    public String uploadFile(@RequestParam(name = "scrap") String scrap,
	    @RequestParam(name = "file") MultipartFile file) throws Exception
    {

	ObjectMapper mapper = new ObjectMapper();
	PostUiVO postUiVO = mapper.readValue(scrap, PostUiVO.class);
	postSRV.uploadFile(postUiVO, file);
	return "";
    }
}

Step 9. DAOs

com.anupam.dao.CommonDAO

package com.anupam.dao;

public interface CommonDAO
{

    public Long getNextID(String key)throws Exception;
}

com.anupam.dao.CommonDAOImpl

package com.anupam.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;

import com.anupam.vo.Counter;

@Repository
public class CommonDAOImpl implements CommonDAO
{

    @Autowired
    private MongoTemplate template;

    @Override
    public Long getNextID(String key) throws Exception
    {
	Query query = new Query(Criteria.where("_id").is(key));
	Update update = new Update();
	update.inc("seq", 1);
	FindAndModifyOptions options = new FindAndModifyOptions();
	options.returnNew(true);

	Counter counter = template.findAndModify(query, update, Counter.class);

	return counter.getSeq();
    }

}

com.anupam.dao.PostDAO

package com.anupam.dao;

import java.util.List;

import com.anupam.vo.TbPostVO;

public interface PostDAO
{

    public Integer savePost(TbPostVO postVO) throws Exception;
    public TbPostVO getPostByID(Integer postID)throws Exception;
    public List<TbPostVO> getAllPosts() throws Exception;
    public List<TbPostVO> getPostByUser(Integer userID)throws Exception;
    public void deletePost(TbPostVO postVO) throws Exception;
}

com.anupam.dao.PostDAOImpl

package com.anupam.dao;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Repository;

import com.anupam.vo.TbPostVO;

@Repository
public class PostDAOImpl implements PostDAO
{

    @Autowired
    private MongoTemplate template;

    @Override
    public Integer savePost(TbPostVO postVO) throws Exception
    {
	template.save(postVO);
	return postVO.getPostID();
    }

    @Override
    public List<TbPostVO> getAllPosts() throws Exception
    {
	List<TbPostVO> list = template.findAll(TbPostVO.class);
	return list;
    }

    @Override
    public List<TbPostVO> getPostByUser(Integer userID) throws Exception
    {
	return null;
    }

    @Override
    public TbPostVO getPostByID(Integer postID) throws Exception
    {
	return null;
    }

    @Override
    public void deletePost(TbPostVO postVO) throws Exception
    {

    }

}

com.anupam.dao.UserDAO

package com.anupam.dao;

import com.anupam.vo.TbUserVO;

public interface UserDAO
{
    public TbUserVO getUserByID(Integer userID) throws Exception;
    public Integer saveUser(TbUserVO userVO) throws Exception;
}

com.anupam.dao.UserDAOImpl

package com.anupam.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Repository;

import com.anupam.vo.TbUserVO;

@Repository
public class UserDAOImpl implements UserDAO
{

    @Autowired
    private MongoTemplate template;

    @Override
    public TbUserVO getUserByID(Integer userID) throws Exception
    {
	Query query = new Query(Criteria.where("_id").is(userID));
	TbUserVO userVO = template.findOne(query, TbUserVO.class);
	return userVO;
    }

    @Override
    public Integer saveUser(TbUserVO userVO) throws Exception
    {
	template.save(userVO);
	System.out.println("User saved...");
	return userVO.getUserID();
    }

}

Step 10. The Services

com.anupam.service.PostSRV

package com.anupam.service;

import java.util.List;

import org.springframework.web.multipart.MultipartFile;

import com.anupam.uivo.PostUiVO;

public interface PostSRV
{

    public List<PostUiVO> getPostByUser() throws Exception;

  

    public void deletePost(Integer postID) throws Exception;

    public void uploadFile(PostUiVO postUiVO,
	    MultipartFile file) throws Exception;
}

com.anupam.service.PostSRVImpl

package com.anupam.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.anupam.dao.CommonDAO;
import com.anupam.dao.PostDAO;
import com.anupam.dao.UserDAO;
import com.anupam.uivo.PostUiVO;
import com.anupam.vo.TbObjectVO;
import com.anupam.vo.TbPostVO;

@Service
public class PostSRVImpl implements PostSRV
{
    @Autowired
    private CommonDAO cd;

    @Autowired
    private UserDAO userDAO;

    @Autowired
    private PostDAO postDAO;

    @Override
    public List<PostUiVO> getPostByUser() throws Exception
    {
	List<PostUiVO> listPost = new ArrayList<>();
	try
	{
	    List<TbPostVO> list = postDAO.getPostByUser(7);
	    if (list != null && !list.isEmpty())
	    {
		for (TbPostVO p : list)
		{
		    listPost.add(new PostUiVO(p.getPostID(), p.getScrap(), p.getDate(), p.getUserVO().getEmail()));
		}
	    }
	}
	catch (Exception e)
	{
	    e.printStackTrace();
	}

	return listPost;
    }

  

    @Override
    public void deletePost(Integer postID) throws Exception
    {
	postDAO.deletePost(postDAO.getPostByID(postID));

    }

    @Override
    public void uploadFile(PostUiVO postUiVO,
	    MultipartFile file) throws Exception
    {
	TbPostVO postVO = new TbPostVO();
	postVO.setPostID(cd.getNextID("posts").intValue());
	postVO.setScrap(postUiVO.getScrap());
	postVO.setDate(new Date());
	postVO.setUserVO(userDAO.getUserByID(1));

	TbObjectVO objVO = new TbObjectVO();
	objVO.setObjectID(cd.getNextID("objects").intValue());
	objVO.setType("jpg");
	objVO.setObjectData(file.getBytes());
	postVO.setObjVO(objVO);

	postDAO.savePost(postVO);
	
    }

}

com.anupam.service.UserSRV

package com.anupam.service;

public interface UserSRV
{

    void saveUser(String firstName, String lastName, String login,
	    String password, String email) throws Exception;

}

com.anupam.service.UserSRVImpl

package com.anupam.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.anupam.dao.CommonDAO;
import com.anupam.dao.UserDAO;
import com.anupam.vo.TbUserVO;

@Service
public class UserSRVImpl implements UserSRV
{
    @Autowired
    private CommonDAO cd;
    @Autowired
    private UserDAO userDAO;

    @Override
    public void saveUser(String firstName, String lastName, String login,
	    String password, String email) throws Exception
    {
	TbUserVO userVO = new TbUserVO();
	userVO.setUserID(cd.getNextID("users").intValue());
	userVO.setFirstName(firstName);
	userVO.setLastName(lastName);
	userVO.setLogin(login);
	userVO.setPassword(password);
	userVO.setEmail(email);
	userDAO.saveUser(userVO);
    }
}

Step 11. The UiVO
com.anupam.uivo.PostUiVO

package com.anupam.uivo;

import java.io.Serializable;
import java.util.Date;

import com.fasterxml.jackson.annotation.JsonProperty;

public class PostUiVO implements Serializable
{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    private Integer postID;
    private String scrap;
    private Date date;
    private String userPosted;

    public PostUiVO()
    {
	super();
    }

    public PostUiVO(Integer postID, String scrap, Date date, String userPosted)
    {
	super();
	this.postID = postID;
	this.scrap = scrap;
	this.date = date;
	this.userPosted = userPosted;
    }

    public Integer getPostID()
    {
	return postID;
    }

    public void setPostID(Integer postID)
    {
	this.postID = postID;
    }

    public String getScrap()
    {
	return scrap;
    }

    public void setScrap(String scrap)
    {
	this.scrap = scrap;
    }

    public Date getDate()
    {
	return date;
    }

    public void setDate(Date date)
    {
	this.date = date;
    }

    public String getUserPosted()
    {
	return userPosted;
    }

    public void setUserPosted(String userPosted)
    {
	this.userPosted = userPosted;
    }

}

Step 12. The VOs
com.anupam.vo.Counter

package com.anupam.vo;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

@Document(collection = "counters")
public class Counter
{
    @Id
    private String id;

    @Field
    private Long seq;

    public String getId()
    {
	return id;
    }

    public void setId(String id)
    {
	this.id = id;
    }

    public Long getSeq()
    {
	return seq;
    }

    public void setSeq(Long seq)
    {
	this.seq = seq;
    }

}

com.anupam.vo.TbObjectVO

package com.anupam.vo;

import java.io.Serializable;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

@Document(collection = "objects")
public class TbObjectVO implements Serializable
{

    private static final long serialVersionUID = 1L;

    @Id
    private Integer objectID;

    @Field
    private String type;

    @Field
    private byte[] objectData;

    public Integer getObjectID()
    {
	return objectID;
    }

    public void setObjectID(Integer objectID)
    {
	this.objectID = objectID;
    }

    public String getType()
    {
	return type;
    }

    public void setType(String type)
    {
	this.type = type;
    }

    public byte[] getObjectData()
    {
	return objectData;
    }

    public void setObjectData(byte[] objectData)
    {
	this.objectData = objectData;
    }

}

com.anupam.vo.TbPostVO

package com.anupam.vo;

import java.io.Serializable;
import java.util.Date;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import com.anupam.annotation.MyCascade;

@Document(collection = "posts")
public class TbPostVO implements Serializable
{

    private static final long serialVersionUID = 1L;
    @Id
    private Integer postID;

    @Field
    private String scrap;

    @Field
    private Date date;

    @DBRef
    private TbUserVO userVO;

    @DBRef
    @MyCascade
    private TbObjectVO objVO;

    public Integer getPostID()
    {
	return postID;
    }

    public void setPostID(Integer postID)
    {
	this.postID = postID;
    }

    public String getScrap()
    {
	return scrap;
    }

    public void setScrap(String scrap)
    {
	this.scrap = scrap;
    }

    public Date getDate()
    {
	return date;
    }

    public void setDate(Date date)
    {
	this.date = date;
    }

    public TbUserVO getUserVO()
    {
	return userVO;
    }

    public void setUserVO(TbUserVO userVO)
    {
	this.userVO = userVO;
    }

    public TbObjectVO getObjVO()
    {
        return objVO;
    }

    public void setObjVO(TbObjectVO objVO)
    {
        this.objVO = objVO;
    }

    

}

com.anupam.vo.TbUserVO

package com.anupam.vo;

import java.io.Serializable;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

@Document(collection="users")
public class TbUserVO implements Serializable
{

    private static final long serialVersionUID = 1L;

    @Id
    private Integer userID;
    @Field
    private String firstName;
    @Field
    private String lastName;
    @Field
    private String login;
    @Field
    private String password;
    @Field
    private String email;

    public Integer getUserID()
    {
	return userID;
    }

    public void setUserID(Integer userID)
    {
	this.userID = userID;
    }

    public String getFirstName()
    {
	return firstName;
    }

    public void setFirstName(String firstName)
    {
	this.firstName = firstName;
    }

    public String getLastName()
    {
	return lastName;
    }

    public void setLastName(String lastName)
    {
	this.lastName = lastName;
    }

    public String getLogin()
    {
	return login;
    }

    public void setLogin(String login)
    {
	this.login = login;
    }

    public String getPassword()
    {
	return password;
    }

    public void setPassword(String password)
    {
	this.password = password;
    }

    public String getEmail()
    {
	return email;
    }

    public void setEmail(String email)
    {
	this.email = email;
    }

}

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s